/* LibSAP * Version 0.9.4 * Copyright (C) 2000-2003 Eric Botcazou * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include "libsap.h" int sap_errno; #define FILENAME_LENGTH 128 #define MAX_ARCHIVE 8 #define SAP_MAGIC_NUM 0xB3 #define SAP_HEADER_SIZE 66 static const char sap_header[]="\0SYSTEME D'ARCHIVAGE PUKALL S.A.P. " "(c) Alexandre PUKALL Avril 1998"; static const int sap_format_table[][3] = { { SAP_NTRACKS1, SAP_SECTSIZE1, SAP_TRACKSIZE1 }, { SAP_NTRACKS2, SAP_SECTSIZE2, SAP_TRACKSIZE2 } }; #define SAP_NTRACKS(f) sap_format_table[f-SAP_FORMAT1][0] #define SAP_SECTSIZE(f) sap_format_table[f-SAP_FORMAT1][1] #define SAP_TRACKSIZE(f) sap_format_table[f-SAP_FORMAT1][2] #define SAP_EXTSECTSIZE(f) (SAP_SECTSIZE(f) + sizeof(sapsector_t) - SAP_SECTSIZE1) static short int crcpuk_temp; static short int puktable[]={ 0x0000, 0x1081, 0x2102, 0x3183, 0x4204, 0x5285, 0x6306, 0x7387, 0x8408, 0x9489, 0xa50a, 0xb58b, 0xc60c, 0xd68d, 0xe70e, 0xf78f }; #define NO_ARCHIVE 0 #define EMPTY_ARCHIVE 1 #define FILLED_ARCHIVE 2 #define FULL_ARCHIVE 3 static struct { int format; int state; int ntracks; FILE *file; } archive[MAX_ARCHIVE+1]; #define ID_FORMAT(id) archive[id].format #define ID_STATE(id) archive[id].state #define ID_NTRACKS(id) archive[id].ntracks #define ID_FILE(id) archive[id].file /* constants for the TO logical disk format */ #define TO_NSECTS 16 #define TO_SECTOR_PER_BLOCK 8 #define TO_SECTSIZE1 255 /* not 256 */ #define TO_FAT_START1 257 #define TO_NBLOCKS1 160 #define TO_BLOCKSIZE1 (TO_SECTOR_PER_BLOCK*TO_SECTSIZE1) #define TO_FILESIZE_MAX1 (TO_NBLOCKS1-2) #define TO_DIR_START1 512 #define TO_SECTSIZE2 127 /* not 128 */ #define TO_FAT_START2 129 #define TO_NBLOCKS2 80 #define TO_BLOCKSIZE2 (TO_SECTOR_PER_BLOCK*TO_SECTSIZE2) #define TO_FILESIZE_MAX2 (TO_NBLOCKS2-2) #define TO_DIR_START2 256 #define TO_FILLER_BYTE 0xE5 #define TO_TAG_RESERVED 0xFE #define TO_TAG_FREE 0xFF #define TO_END_BLOCK_OFFSET 0xC0 #define TO_DIRENTRY_LENGTH 32 #define TO_NAME 0 #define TO_NAME_LENGTH 8 #define TO_EXT 8 #define TO_EXT_LENGTH 3 #define TO_FILE_TYPE 11 #define TO_DATA_TYPE 12 #define TO_FIRST_BLOCK 13 #define TO_END_SIZE 14 #define TO_COMMENT 16 #define TO_COMMENT_LENGTH 8 #define TO_DATE_DAY 24 #define TO_DATE_MONTH 25 #define TO_DATE_YEAR 26 #define TO_CHG_MODE 30 #define TO_CHG_CHECKSUM 31 #define TO_DIRENTRY_PER_SECTOR1 8 #define TO_NDIRENTRIES1 (TO_NSECTS-2)*TO_DIRENTRY_PER_SECTOR1 #define TO_DIRENTRY_PER_SECTOR2 4 #define TO_NDIRENTRIES2 (TO_NSECTS-2)*TO_DIRENTRY_PER_SECTOR2 static const int to_format_table[][8] = { { TO_SECTSIZE1, TO_FAT_START1, TO_NBLOCKS1, TO_BLOCKSIZE1, TO_FILESIZE_MAX1, TO_DIR_START1, TO_DIRENTRY_PER_SECTOR1, TO_NDIRENTRIES1 }, { TO_SECTSIZE2, TO_FAT_START2, TO_NBLOCKS2, TO_BLOCKSIZE2, TO_FILESIZE_MAX2, TO_DIR_START2, TO_DIRENTRY_PER_SECTOR2, TO_NDIRENTRIES2 } }; #define TO_SECTSIZE(f) to_format_table[f-SAP_FORMAT1][0] #define TO_FAT_START(f) to_format_table[f-SAP_FORMAT1][1] #define TO_NBLOCKS(f) to_format_table[f-SAP_FORMAT1][2] #define TO_BLOCKSIZE(f) to_format_table[f-SAP_FORMAT1][3] #define TO_FILESIZE_MAX(f) to_format_table[f-SAP_FORMAT1][4] #define TO_DIR_START(f) to_format_table[f-SAP_FORMAT1][5] #define TO_DIRENTRY_PER_SECTOR(f) to_format_table[f-SAP_FORMAT1][6] #define TO_NDIRENTRIES(f) to_format_table[f-SAP_FORMAT1][7] /* constants for the TO memory layout */ #define TO_BANKSIZE 16384 /* file types */ enum { FTYPE_BASIC, FTYPE_DATA, FTYPE_MACHINE, FTYPE_ASM, FTYPE_OTHERS, FTYPE_PARAGRAPHE = 0xA }; static char ftype_sym[5] = { 'B', 'D', 'M', 'A', '*' }; enum { DTYPE_BINARY, DTYPE_ASCII = 0xFF, DTYPE_OTHERS = 2 }; static char dtype_sym[3] = { 'B', 'A', '*' }; struct extension_t { char tag[TO_EXT_LENGTH+1]; unsigned char ftype; unsigned char dtype; }; enum { EXT_TYPE_BAS, EXT_TYPE_DAT, EXT_TYPE_ASC, EXT_TYPE_ASM, EXT_TYPE_BIN, EXT_TYPE_MAP, EXT_TYPE_CHG, EXT_TYPE_CFG, EXT_TYPE_BAT, EXT_TYPE_CAR, EXT_TYPE_PAR, EXT_TYPE_UNKNOWN, EXT_TYPE_MAX }; static struct extension_t ext_type[EXT_TYPE_MAX] = { { "BAS", FTYPE_BASIC, DTYPE_BINARY }, { "DAT", FTYPE_DATA, DTYPE_ASCII }, { "ASC", FTYPE_DATA, DTYPE_ASCII }, { "ASM", FTYPE_ASM, DTYPE_ASCII }, { "BIN", FTYPE_MACHINE, DTYPE_BINARY }, { "MAP", FTYPE_MACHINE, DTYPE_BINARY }, { "CHG", FTYPE_DATA, DTYPE_ASCII }, { "CFG", FTYPE_MACHINE, DTYPE_BINARY }, { "BAT", FTYPE_BASIC, DTYPE_BINARY }, { "CAR", FTYPE_DATA, DTYPE_ASCII }, { "PAR", FTYPE_PARAGRAPHE, DTYPE_BINARY }, { "???", FTYPE_MACHINE, DTYPE_BINARY } }; #define MIN(x,y) (((x) < (y)) ? (x) : (y)) /************************************************/ /*** private functions: helper routines ***/ /************************************************/ /* get_id: * Finds the first empty id, returns 0 if has failed. */ static int get_id(void) { int i; for (i=MAX_ARCHIVE; i>0; i--) { if (archive[i].state == NO_ARCHIVE) break; } return i; } /* clean_string: * Helper function to clean up character strings. */ static inline void clean_string(unsigned char *str) { while (*str) { if ((*str<32) || (*str>127)) *str = '#'; str++; } } /************************************************/ /*** private functions: SAP format support ***/ /************************************************/ /* crc_pukall: * Computes the new CRC from the c data. */ static void crc_pukall(short int c) { short int index; index = (crcpuk_temp ^ c) & 0xf; crcpuk_temp = ((crcpuk_temp>>4) & 0xfff) ^ puktable[index]; c >>= 4; index = (crcpuk_temp ^ c) & 0xf; crcpuk_temp = ((crcpuk_temp>>4) & 0xfff) ^ puktable[index]; } /* do_crc: * Computes the CRC for the specified SAP sector. */ static void do_crc(sapsector_t *sapsector, int format) { int i; crcpuk_temp = 0xffff; crc_pukall(sapsector->format); crc_pukall(sapsector->protection); crc_pukall(sapsector->track); crc_pukall(sapsector->sector); for (i=0; idata[i]); } /* do_read_sector: * Performs the low-level read operation for the specified sector. */ static void do_read_sector(sapID id, sapsector_t *sapsector) { unsigned char buffer[sizeof(sapsector_t)]; int format = ID_FORMAT(id); int i; i=fread(buffer, sizeof(char), SAP_EXTSECTSIZE(format), ID_FILE(id)); sapsector->format = buffer[0]; sapsector->protection = buffer[1]; sapsector->track = buffer[2]; sapsector->sector = buffer[3]; for (i=0; idata[i] = buffer[4+i]^SAP_MAGIC_NUM; sapsector->data[SAP_SECTSIZE(format)] = buffer[4+i]; sapsector->data[SAP_SECTSIZE(format)+1] = buffer[4+i+1]; } /* do_write_sector: * Performs the low-level write operation for the specified sector. */ static void do_write_sector(sapID id, sapsector_t *sapsector) { unsigned char buffer[sizeof(sapsector_t)]; int format = ID_FORMAT(id); int i; /* fill in the write buffer */ buffer[0] = sapsector->format; buffer[1] = sapsector->protection; buffer[2] = sapsector->track; buffer[3] = sapsector->sector; for (i=0; idata[i]^SAP_MAGIC_NUM; /* compute the CRC */ do_crc(sapsector, format); buffer[4+i] = sapsector->data[SAP_SECTSIZE(format)] = (crcpuk_temp>>8)&0xff; buffer[4+i+1] = sapsector->data[SAP_SECTSIZE(format)+1] = crcpuk_temp&0xff; fwrite(buffer, sizeof(char), SAP_EXTSECTSIZE(format), ID_FILE(id)); } /* seek_pos: * Seeks the specified position in the SAP archive. */ static void seek_pos(sapID id, int track, int sect) { int pos; pos = SAP_HEADER_SIZE + (track*SAP_NSECTS + sect-1) * SAP_EXTSECTSIZE(ID_FORMAT(id)); fseek(ID_FILE(id), pos, SEEK_SET); } /************************************************/ /*** private functions: Thomson DOS support ***/ /************************************************/ /* get_file_size: * Returns the size in blocks of the specified file on disk. */ static int get_file_size(int format, int n, const unsigned char trk20_data[]) { const unsigned char *fat_data, *entry_data; int block, size = 0; fat_data = trk20_data + TO_FAT_START(format); entry_data = trk20_data + TO_DIR_START(format) + n*TO_DIRENTRY_LENGTH; block = entry_data[TO_FIRST_BLOCK]; while ((block 3 ? ftype_sym[4] : ftype_sym[ftype]); /* data type */ dtype = entry_data[TO_DATA_TYPE]; dtype = (dtype == 0 ? dtype_sym[0] : (dtype == 0xFF ? dtype_sym[1] : dtype_sym[2])); /* size */ size = get_file_size(format, n, trk20_data) * ((TO_BLOCKSIZE(format) + 1024) / 1024); /* date */ if ((entry_data[TO_DATE_DAY] >= 1) && (entry_data[TO_DATE_DAY] <= 31) && (entry_data[TO_DATE_MONTH] >= 1) && (entry_data[TO_DATE_MONTH] <= 12) && (entry_data[TO_DATE_YEAR] <= 99)) sprintf(date, "%2d-%2d-%2d", entry_data[TO_DATE_DAY], entry_data[TO_DATE_MONTH], entry_data[TO_DATE_YEAR]); else date[0] = 0; /* comment */ comment[0] = 0; if (entry_data[TO_COMMENT]) strncat(comment,(char*)(entry_data+TO_COMMENT), TO_COMMENT_LENGTH); /* display the entry */ i = sprintf(buffer, "%-8s %-3s %c %c %-3d %-8s %s\n", name, ext, ftype, dtype, size, date, comment); free(comment); return i; (void) buffer_size; /* DJGPP 2.03 lacks snprintf() */ } /* get_filename: * Detects whether the file entry is valid and returns the filename if so. */ static int get_filename(char filename[], const unsigned char entry_data[]) { int i, j; if ((entry_data[TO_NAME] == 0) || (entry_data[TO_NAME] == TO_TAG_FREE)) return -1; /* name */ strncpy(filename,(char*)(entry_data+TO_NAME), TO_NAME_LENGTH); for (i=0; i=filename) && (*p != '/')) p--; shortname[0] = 0; strncat(shortname, p+1, sizeof(shortname) - 1); /* find extension */ len = strlen(shortname); p = shortname + len - 1; while ((p>=shortname) && (*p != '.')) p--; if (p < shortname) { /* no extension? */ memcpy(entry_data+TO_NAME, shortname, MIN(TO_NAME_LENGTH, len)); type = EXT_TYPE_MAX-1; } else { /* name */ memcpy(entry_data+TO_NAME, shortname, MIN(TO_NAME_LENGTH, p - shortname)); /* extension */ ext_len = MIN(TO_EXT_LENGTH, shortname + len - 1 - p); memcpy(entry_data+TO_EXT, p+1, ext_len); /* build upper case extension */ ext[0] = 0; strncat(ext, p+1, ext_len); for (i=0; i= 0) { s1 = data[index].s1; s2 = data[index].s2; c1 = *s1; c2 = *s2; switch (data[index].type) { case WCD_MATCH_TRY: if (c2 == 0) { /* pattern exhausted */ if (c1 == 0) return 0; else index--; } else if (c1 == 0) { /* string exhausted */ while (*s2 == '*') s2++; if (*s2 == 0) return 0; else index--; } else if (c2 == '*') { /* try to match the rest of pattern with empty string */ data[index++].type = WCD_MATCH_ANY; data[index].s1 = s1end; data[index].s2 = s2 + 1; data[index].type = WCD_MATCH_TRY; } else if ((c2 == '?') || (c1 == c2)) { /* try to match the rest */ data[index++].type = WCD_MATCH_ONE; data[index].s1 = s1 + 1; data[index].s2 = s2 + 1; data[index].type = WCD_MATCH_TRY; } else index--; break; case WCD_MATCH_ONE: /* the rest of string did not match, try earlier */ index--; break; case WCD_MATCH_ANY: /* rest of string did not match, try add more chars to string tail */ if (--data[index + 1].s1 >= s1) { data[index + 1].type = WCD_MATCH_TRY; index++; } else index--; break; default: /* this is a bird? This is a plane? No it's a bug!!! */ return 1; } } return 1; } /* seek_wilcard: * Seeks a wildcard in the specified directory and returns its index number if found. */ static int seek_wildcard(int format, char filename[], const char pattern[], const unsigned char dir_data[]) { char entry_name[TO_NAME_LENGTH + 1 + TO_EXT_LENGTH + 1]; static int i = 0; int j; while (i= 0) { if (fat_data[block] == TO_TAG_FREE) break; block--; } } else { while (block < TO_NBLOCKS(format)) { if (fat_data[block] == TO_TAG_FREE) break; block++; } } if ((block<0) || (block>=TO_NBLOCKS(format))) block = find_free_block_sym(format, fat_data); return block; } /* do_add_file: * Performs the low-level add operation for the specified file entry. */ static void do_add_file(sapID id, FILE *file, int file_size, unsigned char entry_data[], int n, unsigned char trk20_data[]) { sapsector_t sapsector; unsigned char *fat_data; unsigned int block, next_block; int sect; int dummy; /* find the first block */ fat_data = trk20_data + TO_FAT_START(ID_FORMAT(id)); block = find_free_block_sym(ID_FORMAT(id), fat_data); entry_data[TO_FIRST_BLOCK] = block; sapsector.format = 0; sapsector.protection = 0; /* write full blocks */ while (file_size > TO_BLOCKSIZE(ID_FORMAT(id))) { sapsector.track = block/2; sapsector.sector = 1 + (block%2 ? TO_SECTOR_PER_BLOCK : 0); seek_pos(id, sapsector.track, sapsector.sector); for (sect=0; sect TO_SECTSIZE(ID_FORMAT(id))) { dummy=fread(sapsector.data, sizeof(char), TO_SECTSIZE(ID_FORMAT(id)), file); do_write_sector(id, &sapsector); sapsector.sector++; fat_data[block]++; file_size -= TO_SECTSIZE(ID_FORMAT(id)); } /* write remaining individual bytes */ dummy=fread(sapsector.data, sizeof(char), TO_SECTSIZE(ID_FORMAT(id)), file); do_write_sector(id, &sapsector); entry_data[TO_END_SIZE] = file_size>>8; entry_data[TO_END_SIZE+1] = file_size; /* write the new entry into the directory */ memcpy(trk20_data + TO_DIR_START(ID_FORMAT(id)) + n*TO_DIRENTRY_LENGTH, entry_data, TO_DIRENTRY_LENGTH); } /* do_delete_file: * Performs the low-level delete operation for the specified file entry and * returns the size of file. */ static int do_delete_file(sapID id, const char filename[], int n, unsigned char trk20_data[]) { unsigned char *fat_data, *entry_data; int block, old_block, size = 0; (void) filename; fat_data = trk20_data + TO_FAT_START(ID_FORMAT(id)); entry_data = trk20_data + TO_DIR_START(ID_FORMAT(id)) + n*TO_DIRENTRY_LENGTH; /* delete the file entry */ entry_data[TO_NAME] = 0; /* read start block */ block = entry_data[TO_FIRST_BLOCK]; /* mark FAT entries with the TO_TAG_FREE tag */ while ((block TO_END_BLOCK_OFFSET+1) size += (block - TO_END_BLOCK_OFFSET - 1) * TO_SECTSIZE(ID_FORMAT(id)); size += (entry_data[TO_END_SIZE]<<8) + entry_data[TO_END_SIZE+1]; return size; } /* do_extract_file: * Performs the low-level extract operation for the specified file entry and * returns the size of file. */ static int do_extract_file(sapID id, const char filename[], int n, unsigned char trk20_data[]) { sapsector_t sapsector; unsigned char *fat_data, *entry_data; int sect, end_size, size = 0; unsigned int block; FILE *file; fat_data = trk20_data + TO_FAT_START(ID_FORMAT(id)); entry_data = trk20_data + TO_DIR_START(ID_FORMAT(id)) + n*TO_DIRENTRY_LENGTH; /* read start block */ block = entry_data[TO_FIRST_BLOCK]; if ((file=fopen(filename, "wb")) == NULL) { sap_errno = SAP_EPERM; return 0; } /* extract full blocks */ while ((fat_data[block]0) { pos += len; lines++; } } return lines; } /* _ForEachFile: * Executes the specified callback funtion on each file matching the pattern. */ int _ForEachFile(sapID id, const char pattern[], sapfilecb_t callback, int save_back) { char filename[TO_NAME_LENGTH + 1 + TO_EXT_LENGTH + 1]; unsigned char trk20_data[SAP_TRACKSIZE1], *dir_data; int n, ret = 0; switch (ID_STATE(id)) { case NO_ARCHIVE: sap_errno = SAP_EINVAL; return 0; case EMPTY_ARCHIVE: sap_errno = SAP_EBUSY; return 0; case FILLED_ARCHIVE: sap_errno = SAP_EBUSY; return 0; case FULL_ARCHIVE: break; } /* read track 20 */ sap_ReadSectorEx(id, 20, 1, SAP_NSECTS, trk20_data); dir_data = trk20_data + TO_DIR_START(ID_FORMAT(id)); if (strpbrk(pattern, "?*")) { /* wildcards? */ while ((n=seek_wildcard(ID_FORMAT(id), filename, pattern, dir_data)) >= 0) ret += callback(id, filename, n, trk20_data); } else { n = seek_file(ID_FORMAT(id), pattern, dir_data); if (n<0) sap_errno = SAP_ENOENT; else ret = callback(id, pattern, n, trk20_data); } if (save_back) { /* save track 20 */ sap_WriteSectorEx(id, 20, 1, SAP_NSECTS, trk20_data); } return ret; } /************************************************/ /*** API functions: physical format support ***/ /************************************************/ /* OpenArchive: * Opens an existing archive, * returns the sapID on success or SAP_ERROR on error. */ sapID sap_OpenArchive(const char filename[], int *format) { char header[SAP_HEADER_SIZE]; sapID id; int dummy; /* seek for an empty id */ if (!(id=get_id())) { sap_errno = SAP_ETOOMANY; return SAP_ERROR; } if ((ID_FILE(id)=fopen(filename, "rb+")) == NULL) { sap_errno = SAP_ENOENT; return SAP_ERROR; } /* read the header */ dummy=fread(header, sizeof(char), SAP_HEADER_SIZE, ID_FILE(id)); /* find the format */ if ((header[0] != SAP_FORMAT1) && (header[0] != SAP_FORMAT2)) { fclose(ID_FILE(id)); sap_errno = SAP_EBADF; return SAP_ERROR; } *format = header[0]; /* check the header */ header[0] = 0; if (strncmp(header, sap_header, SAP_HEADER_SIZE) != 0) { fclose(ID_FILE(id)); sap_errno = SAP_EBADF; return SAP_ERROR; } ID_FORMAT(id) = *format; ID_STATE(id) = FULL_ARCHIVE; ID_NTRACKS(id) = SAP_NTRACKS(*format); return id; } /* CreateArchive: * Creates an archive skeleton and writes the header, * returns the sapID on success or SAP_ERROR on error. */ sapID sap_CreateArchive(const char filename[], int format) { char header[SAP_HEADER_SIZE]; sapID id; if ((format != SAP_FORMAT1) && (format != SAP_FORMAT2)) { sap_errno = SAP_EINVAL; return SAP_ERROR; } /* seek for an empty id */ if (!(id=get_id())) { sap_errno = SAP_ETOOMANY; return SAP_ERROR; } if ((ID_FILE(id)=fopen(filename, "wb")) == NULL) { sap_errno = SAP_EPERM; return SAP_ERROR; } /* write the header */ memcpy(header, sap_header, SAP_HEADER_SIZE); header[0] = format; fwrite(header, sizeof(char), SAP_HEADER_SIZE, ID_FILE(id)); ID_FILE(id) = freopen(filename, "rb+", ID_FILE(id)); seek_pos(id, 0 ,1); ID_FORMAT(id) = format; ID_STATE(id) = EMPTY_ARCHIVE; ID_NTRACKS(id) = 0; return id; } /* CloseArchive: * Closes the archive, filling it up with empty tracks if needed, * returns SAP_OK on success or SAP_ERROR on error. */ int sap_CloseArchive(sapID id) { sapsector_t sapsector; int format = ID_FORMAT(id); int track, sect; switch (ID_STATE(id)) { case NO_ARCHIVE: sap_errno = SAP_EINVAL; return SAP_ERROR; case EMPTY_ARCHIVE: case FILLED_ARCHIVE: sapsector.format = 0; sapsector.protection = 0; memset(sapsector.data, TO_FILLER_BYTE, SAP_SECTSIZE(format)); for (track=ID_NTRACKS(id); trackformat != 0) flag |= SAP_NO_STD_FMT; if (sapsector->protection != 0) flag |= SAP_PROTECTED; if ((sapsector->track != track) || (sapsector->sector != sect)) flag |= SAP_BAD_SECTOR; do_crc(sapsector, format); if ((sapsector->data[SAP_SECTSIZE(format)] != ((crcpuk_temp>>8)&0xff)) || (sapsector->data[SAP_SECTSIZE(format)] != (crcpuk_temp&0xff))) flag |= SAP_CRC_ERROR; return flag; } /* ReadSectorEx: * Reads one or more sectors from the same track, * returns SAP_OK on success or SAP_ERROR on error. */ int sap_ReadSectorEx(sapID id, int track, int sect, int nsects, unsigned char data[]) { sapsector_t sapsector; int format = ID_FORMAT(id); int i; switch (ID_STATE(id)) { case NO_ARCHIVE: sap_errno = SAP_EINVAL; return SAP_ERROR; case EMPTY_ARCHIVE: sap_errno = SAP_EEMPTY; return SAP_ERROR; case FILLED_ARCHIVE: sap_errno = SAP_EBUSY; return SAP_ERROR; case FULL_ARCHIVE: break; } seek_pos(id, track, sect); for (i=0; i=0) dskf += get_file_size(ID_FORMAT(id), prev_n, trk20_data); if ((dskf*TO_BLOCKSIZE(ID_FORMAT(id))) < file_size) { sap_errno = SAP_EFBIG; fclose(file); return 0; } /* delete previous entry */ if (prev_n >= 0) { do_delete_file(id, filename, prev_n, trk20_data); free_n = prev_n; } /* phew! we can finally add the file... */ do_add_file(id, file, file_size, entry_data, free_n, trk20_data); /* update directory and FAT */ sap_WriteSectorEx(id, 20, 1, SAP_NSECTS, trk20_data); fclose(file); return file_size; } /* DeleteFile: * Deletes the specified files from the archive, * returns the total size of the files in bytes on success or 0 on error. */ int sap_DeleteFile(sapID id, const char pattern[]) { return _ForEachFile(id, pattern, do_delete_file, 1); } /* ExtractFile: * Extracts the specified files from the archive, * returns the total size of the files in bytes on success or 0 on error. */ int sap_ExtractFile(sapID id, const char pattern[]) { return _ForEachFile(id, pattern, do_extract_file, 0); } /* GetFileInfo: * Fills in a structure with the info for the specified file * returns SAP_OK on success or SAP_ERROR on error. */ int sap_GetFileInfo(sapID id, const char filename[], sapfileinfo_t *info) { unsigned char trk20_data[SAP_TRACKSIZE1], *dir_data, *fat_data, *entry_data; struct tm tim; int n, block, i=0; switch (ID_STATE(id)) { case NO_ARCHIVE: sap_errno = SAP_EINVAL; return 0; case EMPTY_ARCHIVE: sap_errno = SAP_EEMPTY; return 0; case FILLED_ARCHIVE: sap_errno = SAP_EBUSY; return 0; case FULL_ARCHIVE: break; } /* read track 20 */ sap_ReadSectorEx(id, 20, 1, SAP_NSECTS, trk20_data); dir_data = trk20_data + TO_DIR_START(ID_FORMAT(id)); n = seek_file(ID_FORMAT(id), filename, dir_data); if (n<0) { sap_errno = SAP_ENOENT; return SAP_ERROR; } fat_data = trk20_data + TO_FAT_START(ID_FORMAT(id)); entry_data = trk20_data + TO_DIR_START(ID_FORMAT(id)) + n*TO_DIRENTRY_LENGTH; /* block chain */ info->nblocks = get_file_size(ID_FORMAT(id), n, trk20_data); info->block = malloc(info->nblocks*sizeof(int)); block = entry_data[TO_FIRST_BLOCK]; while ((blockblock[i++] = block; block = fat_data[block]; } /* common attributes */ info->size = info->nblocks*TO_BLOCKSIZE(ID_FORMAT(id)) + entry_data[TO_END_SIZE]; info->file_type = entry_data[TO_FILE_TYPE]; info->data_type = entry_data[TO_DATA_TYPE]; /* date */ if ((entry_data[TO_DATE_DAY] >= 1) && (entry_data[TO_DATE_DAY] <= 31) && (entry_data[TO_DATE_MONTH] >= 1) && (entry_data[TO_DATE_MONTH] <= 12) && (entry_data[TO_DATE_YEAR] <= 99)) { memset(&tim, 0, sizeof(struct tm)); tim.tm_mday = entry_data[TO_DATE_DAY]; tim.tm_mon = entry_data[TO_DATE_MONTH]; tim.tm_year = entry_data[TO_DATE_YEAR]; info->date = mktime(&tim); } else { info->date = 0; } /* comment */ info->comment[0] = '\0'; if (entry_data[TO_COMMENT]) strncat(info->comment,(char*)( entry_data+TO_COMMENT), TO_COMMENT_LENGTH); return SAP_OK; }