/* * File-in-a-directory library routines * */ #include "v9t9_common.h" #include "fiad.h" #define _L LOG_EMUDISK #define _LL _L | LOG_USER #if BEWORKS_FS OSFileType osV99Type = { 0666, "x/v9t9-file-image" }; OSFileType osTIFILESType = { 0666, "x/tifiles-image" }; #elif POSIX_FS OSFileType osV99Type = { 0666 }; OSFileType osTIFILESType = { 0666 }; #elif WIN32_FS OSFileType osV99Type = 0; OSFileType osTIFILESType = 0; #elif MAC_FS OSFileType osV99Type = 'FI99'; OSFileType osTIFILESType = 'TI99'; #endif /* * Suitable logger for no-nag situations */ static void fiad_no_logger(u32 srcflags, const char *format, ...) { if (srcflags & LOG_FATAL) exit(234); } static void (*fiad_logger)(u32, const char *, ...) = fiad_no_logger; /* * Install a new function to log warnings and errors * from the fiad_xxx routines. Passing 'NULL' uses * no logging (the default). */ fiad_logger_func fiad_set_logger(fiad_logger_func nw) { fiad_logger_func old = fiad_logger; fiad_logger = nw == 0L ? fiad_no_logger : nw; return old; } /* Convert a TI filename to a DOS 8.3 filename. */ void fiad_filename_ti2dos(const char *tiname, int len, char *dosname) { int max = 10; int ptr = 0; int dptr = 0; while (len-- && max--) { char cur; cur = tiname[ptr]; /* forced end-of-filename? */ if (cur == ' ' || cur == 0) break; if (ptr == 8) dosname[dptr++] = '.'; /* offset illegal chars */ if (strchr(DOS_illegalchars, cur) != NULL) cur |= 0x80; /* force uppercase */ if (cur >= 'a' && cur <= 'z') cur -= 0x20; dosname[dptr++] = cur; ptr++; } dosname[dptr] = 0; fiad_logger(_L | L_2, "fiad_filename_ti2dos: incoming = '%.*s', outgoing = '%s'\n", 10 - max, tiname, dosname); } /* Convert a TI filename to the host OS. We convert illegal chars in FIAD_illegalchars into HTML-like encodings (&#xx;) so all possible filenames can be stored. */ void fiad_filename_ti2host(const char *tiname, int len, char *hostname) { int max = 10; int hptr = 0, tptr = 0; while (len-- && max--) { char cur = tiname[tptr++]; /* force lowercase */ if (cur >= 'A' && cur <= 'Z') cur += 0x20; else // illegal chars if (cur == FIAD_esc || strchr(FIAD_illegalchars, cur) != NULL) { u8 hex; hostname[hptr++] = '&'; hostname[hptr++] = '#'; hex = (cur & 0xf0) >> 4; if (hex > 9) hex += 'A' - 10; else hex += '0'; hostname[hptr++] = hex; hex = (cur & 0xf); if (hex > 9) hex += 'A' - 10; else hex += '0'; hostname[hptr++] = hex; cur = ';'; //hostname[hptr++] = ';'; } hostname[hptr++] = cur; } hostname[hptr] = 0; fiad_logger(_L | L_2, "fiad_filename_ti2host: incoming = '%.*s', outgoing = '%s'\n", 10 - max, tiname, hostname); } /* Rename an old DOS-mangled V9t9 filename to the new format name, len: TI-format name spec: spec for existing file in an old format If successful, OS_NOERR is returned and 'spec' is transformed into the current filename. */ OSError fiad_filename_fixup_old_filename(const char *name, int len, OSSpec *spec) { char newname[OS_NAMESIZE]; OSSpec newspec; OSError err; fiad_filename_ti2host(name, len, newname); if ((err = OS_MakeSpecWithPath(&spec->path, newname, !mswp_noRelative, &newspec)) == OS_NOERR && (err = OS_Status(&newspec)) != OS_NOERR) { fiad_logger(_LL | 0, "FIAD server: renaming old-style file '%s' to '%s'\n", OS_NameSpecToString1(&spec->name), newname); if ((err = OS_Rename(spec, &newspec)) != OS_NOERR) fiad_logger(_LL | 0, "FIAD server: could not rename file (%s)\n", OS_GetErrText(err)); else *spec = newspec; } return err; } /* Create a full path given a TI filename and an OS path. */ OSError fiad_filename_to_spec(const OSPathSpec * path, const char *name, int len, OSSpec * spec) { OSError err; char osname[OS_NAMESIZE]; /* Try the old-style 8.3 name first */ fiad_filename_ti2dos(name, len, osname); if ((err = OS_MakeSpecWithPath(path, osname, !mswp_noRelative, spec)) != OS_NOERR || (err = OS_Status(spec)) != OS_NOERR || generateoldv9t9filenames) { /* if they wants it, they gots it */ if (generateoldv9t9filenames) { return err; } /* Now try the new-style name */ fiad_filename_ti2host(name, len, osname); /* if it fails, it fails... */ return OS_MakeSpecWithPath(path, osname, !mswp_noRelative, spec); } /* rename it to the new format? */ else if (fixupoldv9t9filenames) { /* * We can be reasonably sure this is a v9t9 file. */ fiad_filename_fixup_old_filename(name, len, spec); } return OS_NOERR; } /* Convert a filename to TI format, return length of filename */ int fiad_filename_host2ti(const char *hostname, char *tiname) { int hptr = 0, tptr = 0, max = 10; memset(tiname, ' ', 10); while (hostname[hptr] && max) { char cur = hostname[hptr]; if (cur != '.') { /* force uppercase */ if (islower(cur)) cur = toupper(cur); else if (cur == '&' && hostname[hptr + 1] == '#' && isxdigit(hostname[hptr + 2]) && isxdigit(hostname[hptr + 3]) && hostname[hptr + 4] == ';') { u8 val; val = hostname[hptr + 2] - '0'; if (val > 9) val -= 7; cur = hostname[hptr + 3] - '0'; if (cur > 9) cur -= 7; cur |= val << 4; hptr += 4; } else if ((cur & 0x80) && strchr(DOS_illegalchars, (cur & 0x7f)) != NULL) { cur ^= 0x80; } tiname[tptr] = cur; tptr++; max--; } hptr++; } fiad_logger(_L | L_2, "fiad_filename_host2ti: incoming: '%s', outgoing: '%.10s'\n", hostname, tiname); return 10 - max + strlen(hostname + hptr); } /* Return length of filename in FDR */ int fiad_filename_strlen(const char *tf) { int len = 0; char *ptr = (char *)tf; while (len < 10 && ptr[len] != ' ') { len++; } return len; } /* Convert a directory leaf to a TI name at 'name' and return length. */ int fiad_path_disk2ti(const OSPathSpec *path, char *name) { char pth[OS_PATHSIZE]; const char *nptr; int len; OS_PathSpecToString2(path, pth); pth[strlen(pth) - 1] = 0; nptr = OS_GetFileNamePtr(pth); len = 0; memset(name, ' ', 10); // clear field while (len < 10 && *nptr) { char ch = *nptr++; if (ch == '.') ch = '_'; // no periods allowed else if (ch >= 'a' && ch <= 'z') ch -= 0x20; // no lowercase either *name++ = ch; len++; } return len; } /*********************************/ /* Verify a V9t9 FDR as a real v9t9 FDR. Perform sanity checks to assert that a v9t9 file is really a v9t9 file and not a text file. */ bool fiad_fdr_matches_v9t9_fdr(struct v9t9_fdr *v9f, const char *filename, OSSize filesize) { // check for invalid filetype flags if (v9f->flags & ~FF_VALID_FLAGS) { fiad_logger(_L | LOG_ERROR, "FIAD server: invalid flags %02x " "for file '%s'\n", v9f->flags, filename); return false; } // check for invalid file size: // do not allow file to be more than one sector larger than FDR says, // but allow it to be up to 64 sectors smaller: // this is a concession for files copied with "direct output to file", // which must write FDR changes before writing data. else if ((long)TI2HOST(v9f->secsused) < (long)((filesize - FDRSIZE) / 256 - 1) || TI2HOST(v9f->secsused) > (filesize - FDRSIZE) / 256 + 64) { fiad_logger(_L | LOG_ERROR, "FIAD server: invalid number of sectors %d " "for data size %d in file '%s'\n", TI2HOST(v9f->secsused), filesize - FDRSIZE, filename); return false; } // fixed files have 256/reclen records per sector else if (!(v9f->flags & ff_program) && !(v9f->flags & ff_variable)) { if (!v9f->reclen || (256 / v9f->reclen != v9f->recspersec)) { fiad_logger(_L | LOG_ERROR, "FIAD server: record length %d / records per sector %d invalid\n" "for FIXED file '%s'\n", v9f->reclen, v9f->recspersec, filename); return false; } } // variable files have 255/(reclen+1) records per sector else if (!(v9f->flags & ff_program)) { if (!v9f->reclen || (255 / (v9f->reclen + 1) != v9f->recspersec) // known problem that older v9t9s used this calculation && (256 / v9f->reclen != v9f->recspersec)) { fiad_logger(_L | LOG_ERROR, "FIAD server: record length %d / records per sector %d invalid\n" "for VARIABLE file '%s'\n", v9f->reclen, v9f->recspersec, filename); return false; } } // program files have 0 else if (v9f->reclen != 0 && v9f->recspersec != 0) { fiad_logger(_LL | LOG_ERROR, "FIAD server: record length %d / records per sector %d invalid\n" "for PROGRAM file '%s'\n", v9f->reclen, v9f->recspersec, filename); return false; } return true; } /* Setup the various flags in an FDR according to the minimum filetype info */ void fiad_fdr_setup(fdrrec *fdr, bool program, u8 flags, u8 reclen, u32 size) { if (program) { fdr->flags = ff_program; fdr->recspersec = 0; fdr->reclen = 0; } else { fdr->reclen = reclen ? reclen : 80; fdr->flags = flags & FF_VALID_FLAGS; fdr->recspersec = (flags & ff_variable) ? (255 / (fdr->reclen + 1)) : (256 / fdr->reclen); } fdr->secsused = (size + 255) >> 8; fdr->byteoffs = size & 0xff; if (fdr->flags & ff_variable) { fdr->numrecs = fdr->secsused; } else if (!(fdr->flags & ff_program)) { fdr->numrecs = size / fdr->reclen; } else { fdr->numrecs = 0; } } /* Repair various fields of the FDR according to known bugs or common file-closing problems. Returns true if fdr was changed. */ bool fiad_fdr_repair(fdrrec *fdr, OSSize datasize) { fdrrec orig = *fdr; // fix invalid record length... if (!(fdr->flags & ff_program) && fdr->reclen == 0) { fdr->reclen = 80; } // fixup for buggy older versions // which didn't calculate recspersec right // for variable or fixed files if (fdr->flags & ff_variable) { fdr->recspersec = 256 / (fdr->reclen + 1); // was 256 / reclen } else if (!(fdr->flags & ff_program)) { fdr->recspersec = 255 / fdr->reclen; // was 256 / reclen } else if (fdr->flags & ff_program) { fdr->recspersec = 0; fdr->reclen = 0; } fdr->secsused = (datasize + 255) / 256; // fix for number of records used in variable file if (fdr->flags & ff_variable) { fdr->numrecs = fdr->secsused; } return (memcmp((void *)&orig, (void *)fdr, sizeof(fdrrec)) != 0); } /*********************************/ /* Initialize a tifile */ void fiad_tifile_clear(fiad_tifile *tf) { tf->open = false; tf->readonly = false; tf->changed = false; tf->changedfdr = false; tf->handle = 0; tf->error = OS_NOERR; memset((void *)&tf->fdr, 0, sizeof(fdrrec)); } /* Setup the FDR with the file path. */ OSError fiad_tifile_setup_spec_with_file(fiad_tifile *tf, const OSPathSpec *path, const char *fname, int len) { if (len > 10) { return OS_FNTLERR; } // copy filename into FDR memset(tf->fdr.filenam, ' ', 10); memcpy(tf->fdr.filenam, fname, len); fiad_logger(_L | L_2, "setup FDR name as '%.10s'\n", tf->fdr.filenam); return fiad_filename_to_spec(path, fname, len, &tf->spec); } /* Setup the FDR with the OSSpec. */ OSError fiad_tifile_setup_spec_with_spec(fiad_tifile *tf, OSSpec *spec) { char tiname[10]; int len; len = fiad_filename_host2ti(OS_NameSpecToString1(&spec->name), tiname); if (len > 10) { return OS_FNTLERR; } tf->spec = *spec; /* Since that was successful, now backtrack and see if the file was in the old format */ if (fixupoldv9t9filenames) { /* * We can't assume that 'tiname' is the actual name * of the file due to weird extended ASCII mangling * by the filesystem or unzip, so, if the file really exists, * read its FDR and get the name from there. */ if (fiad_tifile_get_info(tf)) { if (tf->format == F_V9t9) { memcpy(tiname, tf->fdr.filenam, 10); len = fiad_filename_strlen(tf->fdr.filenam); fiad_filename_fixup_old_filename(tiname, len, &tf->spec); } else if (tf->format == F_TIFILES) { /* * There's no information about the original filename. * Blast! Hope/pray/assume nothing screwed up the * extended characters. */ fiad_filename_fixup_old_filename(tiname, len, &tf->spec); } } } // copy filename into FDR memset(tf->fdr.filenam, ' ', 10); memcpy(tf->fdr.filenam, tiname, len); fiad_logger(_L | L_2, "setup FDR name as '%.10s'\n", tf->fdr.filenam); return OS_NOERR; } /* Read the FDR from a file, return 0 if it's bad. */ int fiad_tifile_read_fdr(fiad_tifile * tf) { OSError err; u8 fdrsec[FDRSIZE]; OSSize len = FDRSIZE, flen; fdrrec *fdr = &tf->fdr; /* can we read the FDR? */ if ((err = OS_GetSize(tf->handle, &flen)) != OS_NOERR || (err = OS_Seek(tf->handle, OSSeekAbs, 0)) != OS_NOERR || (err = OS_Read(tf->handle, (void *) fdrsec, &len)) != OS_NOERR) { tf->error = err; fiad_logger(_L|LOG_ERROR, "%s: could not read FDR\n", OS_SpecToString1(&tf->spec)); return 0; } /* is it big enough? */ else if (len < FDRSIZE && !unknownfileistext) { fiad_logger(_L|LOG_ERROR, "%s: FDR is short\n", OS_SpecToString1(&tf->spec)); return 0; } /* is it a TIFILES or V9t9 file, or text? */ else { v9t9_fdr *v9f = (v9t9_fdr *) fdrsec; tifiles_fdr *tif = (tifiles_fdr *) fdrsec; char filename[OS_NAMESIZE]; /* Figure out what kind it is and convert. */ if (memcmp(tif->sig, "\007TIFILES", 8) == 0) { // TIFILES fdr has no filename tf->format = F_TIFILES; OS_NameSpecToString2(&tf->spec.name, filename); fiad_filename_host2ti(filename, fdr->filenam); fdr->secsused = TI2HOST(tif->secsused); fdr->flags = tif->flags; fdr->recspersec = tif->recspersec; fdr->byteoffs = tif->byteoffs; fdr->reclen = tif->reclen; fdr->numrecs = SWAPTI(tif->numrecs); fiad_logger(_L | L_3, "TIFILES read> secsused=%04X, flags=%x, recspersec=%x, byteoffs=%x, reclen=%d, numrecs=%04X\n", tif->secsused, tif->flags, tif->recspersec, tif->byteoffs, tif->reclen, tif->numrecs); } else if (len == FDRSIZE && fiad_fdr_matches_v9t9_fdr(v9f, OS_SpecToString1(&tf->spec), flen)) { // assume V9t9 tf->format = F_V9t9; memcpy(fdr->filenam, v9f->filenam, 10); fdr->flags = v9f->flags; fdr->recspersec = v9f->recspersec; fdr->secsused = TI2HOST(v9f->secsused); fdr->byteoffs = v9f->byteoffs; fdr->reclen = v9f->reclen; fdr->numrecs = SWAPTI(v9f->numrecs); fiad_logger(_L | L_3, "V9t9 read> secsused=%04X, flags=%x, recspersec=%x, byteoffs=%x, reclen=%d, numrecs=%04X\n", v9f->secsused, v9f->flags, v9f->recspersec, v9f->byteoffs, v9f->reclen, v9f->numrecs); } else if (unknownfileistext) { // treat file as text fiad_logger(_L | L_2, "Treating '%s' as text\n", OS_SpecToString1(&tf->spec)); tf->format = F_TEXT; OS_NameSpecToString2(&tf->spec.name, filename); fiad_filename_host2ti(filename, fdr->filenam); fiad_fdr_setup(fdr, false /*program*/, ff_variable, 80, flen); } else { fiad_logger(_L | LOG_ERROR, "File '%s' does not appear to be a V9t9 file\n", OS_SpecToString1(&tf->spec)); return 0; } tf->changedfdr = false; return 1; } } /* Write FDR to file, return 0 and report error if failed. */ int fiad_tifile_write_fdr(fiad_tifile * tf) { OSError err; u8 fdrsec[FDRSIZE]; OSSize len = FDRSIZE; fdrrec *fdr = &tf->fdr; if (tf->readonly || !tf->open) { fiad_logger(_LL|LOG_ERROR, "%s: Trying to write FDR on a closed or read-only file\n", OS_SpecToString1(&tf->spec)); return 0; } memset(fdrsec, 0, FDRSIZE); // Decide what format to write the FDR in. // don't write one for text or for an unknown file if (tf->format == F_TEXT || tf->format == F_UNKNOWN) { tf->changedfdr = false; return 1; } // write FDR in given format if (tf->format == F_V9t9) { v9t9_fdr *v9f = (v9t9_fdr *) fdrsec; tf->format = F_V9t9; memcpy(v9f->filenam, fdr->filenam, 10); v9f->flags = fdr->flags; v9f->recspersec = fdr->recspersec; v9f->secsused = HOST2TI(fdr->secsused); v9f->byteoffs = fdr->byteoffs; v9f->reclen = fdr->reclen; v9f->numrecs = SWAPTI(fdr->numrecs); } else { tifiles_fdr *tif = (tifiles_fdr *) fdrsec; tf->format = F_TIFILES; memcpy(tif->sig, "\007TIFILES", 8); tif->flags = fdr->flags; tif->recspersec = fdr->recspersec; tif->secsused = HOST2TI(fdr->secsused); tif->byteoffs = fdr->byteoffs; tif->reclen = fdr->reclen; tif->numrecs = SWAPTI(fdr->numrecs); /* fiad_logger(_LL, "TIFILES write> secsused=%04X, flags=%x, recspersec=%x, byteoffs=%x, reclen=%d, numrecs=%04X\n", tif->secsused, tif->flags, tif->recspersec, tif->byteoffs, tif->reclen, tif->numrecs); */ } if ((err = OS_Seek(tf->handle, OSSeekAbs, 0)) != OS_NOERR || (err = OS_Write(tf->handle, (void *) fdrsec, &len)) != OS_NOERR) { tf->error = err; fiad_logger(_LL|LOG_ERROR, "%s: could not write FDR\n", OS_SpecToString1(&tf->spec)); return 0; } else if (len < FDRSIZE) { fiad_logger(_LL|LOG_ERROR, "%s: wrote short FDR\n", OS_SpecToString1(&tf->spec)); return 0; } else { tf->changedfdr = false; return 1; } } /* Verify a file, by classifying its type and checking invariants */ int fiad_tifile_verify(fiad_tifile * tf, bool fixup) { OSSize sz; OSError err; if (!fiad_tifile_read_fdr(tf)) { fiad_logger(_LL | LOG_ERROR, "FIAD server: can't read FDR from file '%s'\n", OS_SpecToString1(&tf->spec)); return 0; } if (tf->format == F_TEXT) return 1; if (tf->format == F_UNKNOWN) return 0; err = OS_GetSize(tf->handle, &sz); if (err != OS_NOERR || sz < FDRSIZE) { fiad_logger(_LL | LOG_ERROR, "FIAD server: FDR is short in file '%s'\n", OS_SpecToString1(&tf->spec)); return 0; } if (tf->format == F_V9t9) { sz -= FDRSIZE; } if (fixup && repairbadfiles) { tf->changedfdr = fiad_fdr_repair(&tf->fdr, sz); fiad_logger(_LL, "FIAD server: repaired fields of FDR in '%s'\n", OS_SpecToString1(&tf->spec)); } return 1; } OSFileType * fiad_get_file_type(int newfileformat) { return newfileformat == F_V9t9 ? &osV99Type : newfileformat == F_TIFILES ? &osTIFILESType : &OS_TEXTTYPE; } /* Initialize file pointers which keep track of current record */ int fiad_tifile_init_file_pointers(fiad_tifile * tf) { tf->cursec = tf->curoffs = tf->curnrecs = tf->currec = 0; tf->changed = false; tf->changedfdr = false; return 1; } /* Read a sector from the file at tf->cursec. If tf points to the last empty sector of file, this is not an error. Return 0 if sector not found. */ int fiad_tifile_read_sector(fiad_tifile * tf) { OSPos pos; OSSize sz = 256; OSError err; if (!fiad_tifile_flush(tf)) return 0; pos = tf->cursec * 256 + (tf->format != F_TEXT ? FDRSIZE : 0); // Try to read it... if ((err = OS_Seek(tf->handle, OSSeekAbs, pos)) != OS_NOERR || (err = OS_Read(tf->handle, tf->sector, &sz)) != OS_NOERR) { // bad error tf->error = err; return 0; } else if (sz < 256) { if (tf->cursec == tf->fdr.secsused && sz == 0) { // last sector can be empty return 1; } else if (tf->format != F_TEXT || tf->cursec != tf->fdr.secsused - 1) { // last sector can be short, but not the other ones return 0; } else return 1; } else { return 1; } } /* Write a sector to the file at tf->cursec. Return 0 if sector not written. */ int fiad_tifile_write_sector(fiad_tifile * tf) { OSPos pos; OSSize sz = 256; OSError err; pos = tf->cursec * 256 + (tf->format != F_TEXT ? FDRSIZE : 0); if ((err = OS_Seek(tf->handle, OSSeekAbs, pos)) != OS_NOERR || (err = OS_Write(tf->handle, tf->sector, &sz)) != OS_NOERR) { // bad error tf->error = err; return 0; } else if (sz < 256) { // no sector can be written short tf->error = OS_MEMERR; // "out of space" return 0; } else { tf->changed = false; return 1; } } /* Set file size according to cursec and curoffs */ int fiad_tifile_set_file_size(fiad_tifile *tf) { OSPos pos; OSSize sz = 0; OSError err; pos = tf->cursec * 256 + (tf->format != F_TEXT ? FDRSIZE : 0); if ((err = OS_SetSize(tf->handle, pos)) != OS_NOERR) { // bad error tf->error = err; return 0; } else { return 1; } } /* Open or create the file, either r/w or r/o. We expect the spec to have been set up. create && always means, delete existing file. create means, create file if not existing. */ OSError fiad_tifile_open_file(fiad_tifile *tf, int newfileformat, bool create, bool always, bool readonly) { OSError err; bool creating = false; fiad_logger(_L | L_3, "fiad_tifile_open_file: '%s', create: %d, always: %d, readonly: %d\n", OS_SpecToString1(&tf->spec), create, always, readonly); tf->open = false; tf->readonly = readonly; tf->format = F_UNKNOWN; fiad_tifile_init_file_pointers(tf); if (create) { // go ahead and delete if we want a clean slate if (always) { OS_Delete(&tf->spec); } // see if file exists err = OS_Status(&tf->spec); // if not (or the file is broken), try to create it if (err != OS_NOERR) { err = OS_Create(&tf->spec, fiad_get_file_type(newfileformat)); // if we couldn't create, error if (err != OS_NOERR) { return err; } if (err == OS_NOERR) { fiad_logger(_L | L_2, "created FIAD file '%s'\n", OS_SpecToString1(&tf->spec)); } tf->format = newfileformat; creating = true; } } /* Try to open file; it should exist by now */ err = OS_Open(&tf->spec, readonly ? OSReadOnly : OSReadWrite, &tf->handle); tf->open = (err == OS_NOERR); tf->changed = false; tf->changedfdr = false; /* Permission error? */ if (!readonly && err == OS_PERMERR) { return err; } /* It was opened, verify it. If verification fails, but we can create, then do so. */ if (!creating && err == OS_NOERR) { if (!fiad_tifile_verify(tf, !readonly /*fixup*/)) { if (create) { fiad_logger(_L|L_2, "Failed to verify, creating new file\n"); creating = true; tf->format = newfileformat; err = OS_NOERR; } else { fiad_tifile_close_file(tf); err = OS_FNFERR; } } } /* Existing but wanting to create? */ if (create && creating && err == OS_NOERR) { fiad_logger(_L|L_2, "Truncating file\n"); err = OS_SetSize(tf->handle, 0); /* don't write FDR here; caller uses this to determine if we created the file or not */ tf->open = (err == OS_NOERR); tf->changed = true; tf->changedfdr = true; } /* Change FDR format? */ if (tf->format != newfileformat && !keepfileformat && tf->format != F_TEXT && newfileformat != F_TEXT) { tf->format = newfileformat; tf->changedfdr = true; } return err; } /* * Reopen a file, keeping open file information intact. */ OSError fiad_tifile_reopen_file(fiad_tifile *tf, int newfileformat, bool readonly) { OSError err; fiad_tifile copy = *tf; err = fiad_tifile_open_file(tf, newfileformat, false /*create*/, false /*always*/, readonly); tf->cursec = copy.cursec; tf->curoffs = copy.curoffs; tf->curnrecs = copy.curnrecs; tf->currec = copy.currec; fiad_tifile_read_sector(tf); return err; } /* * Close a tifile. */ void fiad_tifile_close_file(fiad_tifile * tf) { if (!tf || !tf->open) return; /* ignore errors */ fiad_tifile_flush(tf); OS_Close(tf->handle); tf->open = false; tf->handle = (OSRef) -1; /* if FDR is still different, file was either readonly or we changed something we should write now */ if (tf->changedfdr) { fiad_tifile_rewrite_fdr(tf); } } /* Rewrite FDR of closed file This may be used to change the format (change tf->format) or rewrite a fixed FDR on a readonly file. */ void fiad_tifile_rewrite_fdr(fiad_tifile *tf) { int newfileformat = tf->format; if (tf->open) fiad_logger(LOG_FATAL, "fiad_tifile_rewrite_fdr: File is open!\n"); if (fiad_tifile_open_file(tf, F_UNKNOWN, false /*create*/, false /*always*/, false /*readonly*/) == OS_NOERR) { if (fiad_tifile_read_fdr(tf)) { if ((newfileformat == F_V9t9 || newfileformat == F_TIFILES) && newfileformat != tf->format) { fiad_logger(_L|L_2, "%s: changing file format to %s\n", OS_SpecToString1(&tf->spec), newfileformat == F_V9t9 ? "V9t9" : "TIFILES"); tf->format = newfileformat; } else if (!fiad_tifile_verify(tf, true /*fixup*/) && repairbadfiles) { fiad_logger(_L|L_2, "%s: rewriting damaged FDR\n", OS_SpecToString1(&tf->spec)); } else { goto dont_write; } fiad_tifile_write_fdr(tf); } dont_write: fiad_tifile_close_file(tf); } } /* Flush dirty buffers */ int fiad_tifile_flush(fiad_tifile *tf) { if (!tf->open) return 0; if (tf->readonly) return 1; if (tf->changed) { if (!fiad_tifile_write_sector(tf)) return 0; } if (tf->changedfdr) { if (!fiad_tifile_write_fdr(tf)) return 0; } return 1; } /* Change the current sector */ int fiad_tifile_seek_to_sector(fiad_tifile *tf, int secnum) { if (!fiad_tifile_flush(tf)) return 0; tf->cursec = secnum; tf->curoffs = 0; return 1; } /* Seek, logically, to the end of file */ int fiad_tifile_seek_to_end(fiad_tifile * tf) { if (!fiad_tifile_seek_to_sector(tf, FDR_LASTSEC(&tf->fdr))) return 0; tf->curoffs = tf->fdr.byteoffs; tf->curnrecs = 0; tf->currec = 0; return fiad_tifile_read_sector(tf); } /* Read a record from a file (all but PROGRAM files) Return 0 for success, 1 for EOF, and -1 for hardware failure */ int fiad_tifile_read_record(fiad_tifile *tf, u8 *data, u8 *reclen) { if ((tf->fdr.flags & ff_variable) && tf->format != F_TEXT) { /* read variable data record */ u8 len; retry_var_read: // Get a new sector? if (!tf->curoffs) { // any more sectors? if (tf->cursec < tf->fdr.secsused) { if (!fiad_tifile_read_sector(tf)) { return -1; } } else { return 1; // EOF } } len = tf->sector[tf->curoffs]; // 0xff means end of sector if (len == 0xff) { if (!fiad_tifile_seek_to_sector(tf, tf->cursec+1)) { return -1; } goto retry_var_read; } if (len > tf->fdr.reclen) { //fiad_logger(_L | "FIAD ERROR: length of record on disk is longer than maximum"); // this is legal; we only set tf->fdr.reclen bytes, // but advance the pointer 'len' bytes. } if ((u32) tf->curoffs + (u32) len >= 256) { fiad_logger(_LL | LOG_ERROR, "FIAD ERROR: file appears corrupt [curoffs=%d, len=%d]\n\n\n", tf->curoffs, len); len = 255 - tf->curoffs; tf->cursec++; } *reclen = len > tf->fdr.reclen ? tf->fdr.reclen : len; memcpy(data, tf->sector + tf->curoffs + 1, *reclen); tf->curoffs += len + 1; // length byte } else if ((tf->fdr.flags & ff_variable) && tf->format == F_TEXT) { /* read variable data from text file */ int len; int lastbyte, strln, cpyln; char str255[256]; bool at_eoln; fiad_logger(_L | L_3, "Variable read from F_TEXT: #%d/%d %d/%d\n", tf->cursec, tf->fdr.secsused, tf->curoffs, tf->fdr.byteoffs); *str255 = 0; strln = 0; retry_textvar_read: lastbyte = (tf->cursec < tf->fdr.secsused - 1 ? 255 : tf->fdr.byteoffs); fiad_logger(_L | L_3, "lastbyte=%d\n", lastbyte); if (tf->curoffs >= lastbyte) { tf->curoffs = 0; tf->cursec++; } // Get a new sector? if (!tf->curoffs) { // any more sectors? if (tf->cursec < tf->fdr.secsused) { if (!fiad_tifile_read_sector(tf)) { return -1; } } else { return 1; // EOF } } /* Read until newline */ len = 0; at_eoln = false; while (tf->curoffs + len <= lastbyte) { len++; if (tf->sector[tf->curoffs + len - 1] == '\r' || tf->sector[tf->curoffs + len - 1] == '\n') { at_eoln = true; break; } } /* * We can only copy up to 255 bytes into a record (and even then, * it will be truncated according to the record size in the PAB). * Perhaps add an option to split long lines into smaller records? */ if (strln > 255) cpyln = 0; else if (strln + (len - (at_eoln == true)) > 255) cpyln = 255 - strln; else cpyln = len - (at_eoln == true); memcpy(str255 + strln, tf->sector + tf->curoffs, cpyln); tf->curoffs += len; strln += cpyln; str255[strln] = 0; fiad_logger(_L | L_2, "F_TEXT: '%s'\n", str255); /* Skip newline(s) */ if (tf->curoffs) { if (tf->sector[tf->curoffs] == '\r' && (tf->curoffs <= lastbyte - 1 && tf->sector[tf->curoffs + 1] == '\n')) tf->curoffs++; } /* Get new sector? */ if (!tf->curoffs) { if (!fiad_tifile_seek_to_sector(tf, tf->cursec+1)) { return -1; } goto retry_textvar_read; } len = strln; if (len > tf->fdr.reclen) { //fiad_logger(_L | "FIAD ERROR: length of record on disk is longer than maximum"); // this is legal; we only set tf->fdr.reclen bytes, // but advance the pointer 'len' bytes. } *reclen = len > tf->fdr.reclen ? tf->fdr.reclen : len; memcpy(data, str255, *reclen); } else { /* read fixed data record */ u16 newsec; fiad_logger(_L | L_3, "fixed read, wanted %d, max is %d\n\n", tf->currec, tf->fdr.numrecs); if (tf->currec >= tf->fdr.numrecs) { return 1; // EOF } newsec = tf->currec / tf->fdr.recspersec; // different sector? if (newsec != tf->cursec) { if (!fiad_tifile_seek_to_sector(tf, newsec)) { return -1; } // Get a new sector if (!fiad_tifile_read_sector(tf)) { return -1; } } tf->curnrecs = tf->currec % tf->fdr.recspersec; tf->curoffs = tf->curnrecs * tf->fdr.reclen; // fiad_logger(_L|LOG_USER, "%p, %04X, %04X, %d\n", tf->pab, PABTOVDP(tf->pab), tf->pab.addr, tf->fdr.reclen); *reclen = tf->fdr.reclen; memcpy(data, tf->sector + tf->curoffs, *reclen); tf->currec++; } return 0; } /* Write a record to the file (all but PROGRAM files) Return 0 for success, -1 for hardware failure, 1 for disk full */ int fiad_tifile_write_record(fiad_tifile *tf, u8 *data, u8 reclen) { if ((tf->fdr.flags & ff_variable) && tf->format != F_TEXT) { /* write variable data record */ u8 len; retry_var_write: // write a new sector? if (!tf->curoffs) { // clip file to current position if (tf->cursec + 1 != tf->fdr.secsused) { tf->fdr.secsused = tf->cursec + 1; tf->fdr.numrecs = tf->cursec + 1; if (!fiad_tifile_set_file_size(tf)) { return -1; // hardware failure } } } len = reclen; // I think this is standard if (len > tf->fdr.reclen) len = tf->fdr.reclen; // need room for record, length, and 0xff eos byte if (len + 1 + tf->curoffs >= 255) { if (!fiad_tifile_seek_to_sector(tf, tf->cursec+1)) { return tf->error == OS_MEMERR /*disk full*/ ? 1 : -1; } goto retry_var_write; } tf->sector[tf->curoffs] = len; memcpy(tf->sector + tf->curoffs + 1, data, len); tf->changed = true; tf->changedfdr = true; tf->curoffs += len + 1; // update EOF ptr tf->sector[tf->curoffs] = 0xff; // end of sector marker tf->fdr.byteoffs = tf->curoffs; // update FDR } else if ((tf->fdr.flags & ff_variable) && tf->format == F_TEXT) { /* write variable data to text file */ u8 len; int strln, cpyln; len = reclen; // I think this is standard if (len > tf->fdr.reclen) len = tf->fdr.reclen; strln = len + 1; // to copy retry_textvar_write: // write a new sector? if (!tf->curoffs) { // clip file to current position if (tf->cursec + 1 != tf->fdr.secsused) { tf->fdr.secsused = tf->cursec + 1; tf->fdr.numrecs = tf->cursec + 1; if (!fiad_tifile_set_file_size(tf)) { return -1; // hardware failure } } } // fill up sector to the end, // keep one byte for newline cpyln = tf->curoffs + strln < 256 ? strln : 255 - strln; memcpy(tf->sector + tf->curoffs, data + len - strln, cpyln); tf->changed = true; tf->changedfdr = true; strln -= cpyln; if (!strln) #ifndef UNDER_MACOS tf->sector[tf->curoffs + cpyln - 1] = '\n'; #else tf->sector[tf->curoffs + cpyln - 1] = '\r'; #endif tf->curoffs += cpyln; if (!tf->curoffs) { if (!fiad_tifile_seek_to_sector(tf, tf->cursec+1)) { return tf->error == OS_MEMERR /*disk full*/ ? 1 : -1; } goto retry_textvar_write; } tf->fdr.byteoffs = tf->curoffs; // update FDR } else { /* write fixed data record */ u16 newsec; fiad_logger(_L | L_3, "fixed write, at %d, cur is %d\n", tf->currec, tf->fdr.numrecs); newsec = tf->currec / tf->fdr.recspersec; // different sector? if (newsec != tf->cursec || tf->fdr.secsused == 0) { if (!fiad_tifile_seek_to_sector(tf, newsec)) { return tf->error == OS_MEMERR /*disk full*/ ? 1 : -1; } // Get the current sector, growing file if needed if (tf->fdr.secsused <= tf->cursec) { // update sector count tf->fdr.secsused = tf->cursec + 1; tf->changedfdr = true; } else { // read old sector if (!fiad_tifile_read_sector(tf)) { return -1; } } } tf->curnrecs = tf->currec % tf->fdr.recspersec; tf->curoffs = tf->curnrecs * tf->fdr.reclen; memcpy(tf->sector + tf->curoffs, data, reclen); memset(tf->sector + tf->curoffs + reclen, 0, tf->fdr.reclen - reclen); tf->changed = true; tf->currec++; // update # records if needed if (tf->currec > tf->fdr.numrecs) { tf->fdr.numrecs = tf->currec; tf->fdr.byteoffs = 0; // for fixed files tf->changedfdr = true; } } return 0; } /* Seek to a given record. For variable files, only 0 is accepted. Return 0 for success, -1 for hardware failure, 1 for disk full */ int fiad_tifile_seek_to_record(fiad_tifile *tf, u16 recnum) { /* Variable files always RESTORE to beginning of file */ if (tf->fdr.flags & ff_variable) { if (!fiad_tifile_seek_to_sector(tf, 0)) { return tf->error == OS_MEMERR /*disk full*/ ? 1 : -1; } fiad_tifile_init_file_pointers(tf); } else { if (!fiad_tifile_flush(tf)) { return tf->error == OS_MEMERR /*disk full*/ ? 1 : -1; } tf->currec = recnum; } return 0; } /* Read binary data from file at current position. Returns 0 for success, -1 for hardware failure, 1 for EOF. */ int fiad_tifile_read_binary_image(fiad_tifile *tf, u8 *data, u16 maxread, u16 *gotread) { u16 toread; /* take minimum size */ toread = maxread; fiad_logger(_L | L_2, "file has >%04X or %d sectors\n\n", tf->fdr.secsused, tf->fdr.secsused); fiad_logger(_L | L_2, "going to read at most %d bytes from file with size %d\n", toread, FDR_FILESIZE(&tf->fdr)); /* bytes read */ *gotread = 0; while (toread) { u16 copy = toread < 256 ? toread : 256; if (tf->cursec >= tf->fdr.secsused) { // end of file return 1; } if (!fiad_tifile_read_sector(tf)) { return -1; } memcpy(data, tf->sector, copy); data += copy; *gotread += copy; toread -= copy; if (!fiad_tifile_seek_to_sector(tf, tf->cursec+1)) { return -1; } } return 0; } /* Write binary data to a file at the current position Return 0 for success, -1 for hardware failure, 1 for disk full */ int fiad_tifile_write_binary_image(fiad_tifile *tf, u8 *data, u16 towrite, u16 *written) { u16 secs = 0; fiad_logger(_L | L_2, "trying to write %d bytes to file\n\n", towrite); *written = 0; while (*written < towrite) { u16 copy = towrite - (*written) < 256 ? towrite - (*written) : 256; if (tf->cursec >= tf->fdr.secsused) { tf->fdr.secsused = tf->cursec + 1; tf->fdr.byteoffs = 0; tf->changedfdr = true; } memcpy(tf->sector, data, copy); tf->changed = true; if (!fiad_tifile_seek_to_sector(tf, tf->cursec+1)) return tf->error == OS_MEMERR /*disk full*/ ? 1 : -1; data += copy; *written += copy; } // last sector may be partial tf->fdr.byteoffs = *written & 0xff; return 0; } /* Get info about a file in tf, return 1 for success */ int fiad_tifile_get_info(fiad_tifile *tf) { OSError err; int ret; err = fiad_tifile_open_file(tf, F_UNKNOWN, false /*create*/, false /*always*/, true /*readonly*/); if ((tf->error = err) != OS_NOERR) return 0; // don't return right away in case we're // repairing the FDR ret = fiad_tifile_verify(tf, false /*fixup*/); fiad_tifile_close_file(tf); return ret; } /* Free a catalog */ void fiad_catalog_free_catalog(fiad_catalog *cat) { int x; if (!cat->filenames) { return; } for (x = 0; x < cat->entries; x++) { xfree(cat->filenames[x]); } xfree(cat->filenames); cat->filenames = 0L; } static int sort_filename(const void *a, const void *b) { return strcasecmp(*(const char **)a, *(const char **)b); } /* Read a catalog from a directory */ OSError fiad_catalog_read_catalog(fiad_catalog *cat, const char *wildcard) { char wildpath[OS_PATHSIZE]; OSSpec spec, *sptr; OSError err; OSSize blocksize, totalblocks, freeblocks; bool iswild; int x = 0; fiad_tifile tf; fiad_catalog_free_catalog(cat); /* Get disk info */ err = OS_MakeSpec(wildcard, &spec, &iswild); if (err != OS_NOERR) return err; cat->path = spec.path; if ((err = OS_GetDiskStats(&spec.path, &blocksize, &totalblocks, &freeblocks)) != OS_NOERR) { blocksize = 256; totalblocks = 360; freeblocks = 0; } cat->total_sectors = totalblocks / 256 * blocksize; cat->free_sectors = freeblocks / 256 * freeblocks; /* Get file list */ cat->entries_max = cat->entries = 0; cat->filenames = 0; /* If 'iswild' is true, use directory matching helper */ if (!iswild) { OS_PathSpecToString2(&spec.path, wildpath); strcat(wildpath, "*"); } else { strcpy(wildpath, wildcard); } /* Any matches? */ if ((sptr = OS_MatchPath(wildpath)) == NULL) { return OS_FNFERR; } fiad_logger(_L | L_2, "reading dir\n"); do { /* validate file and get info */ if (!OS_IsFile(sptr) || fiad_tifile_setup_spec_with_spec(&tf, sptr) != OS_NOERR || !fiad_tifile_get_info(&tf) || tf.format == F_UNKNOWN) { fiad_logger(_L|L_1, "Skipping '%s' which doesn't appear to be a V9t9 file\n", OS_NameSpecToString1(&sptr->name)); continue; } /* more memory needed? */ if (x >= cat->entries_max) { char **names; fdrrec *fdrs; int *index; names = (char **) xrealloc(cat->filenames, (cat->entries_max + 128) * sizeof(char *)); fdrs = (fdrrec *) xrealloc(cat->fdrs, (cat->entries_max + 128) * sizeof(fdrrec)); index = (int *) xrealloc(cat->index, (cat->entries_max + 128) * sizeof(int)); cat->entries_max += 128; cat->filenames = names; cat->fdrs = fdrs; cat->index = index; } cat->filenames[x] = xstrdup(OS_NameSpecToString1(&sptr->name)); fiad_logger(_L | L_2, "got file '%s'\n\n", cat->filenames[x]); cat->fdrs[x] = tf.fdr; cat->index[x] = x; x++; cat->entries++; } while ((sptr = OS_MatchPath(0L)) != 0L); return OS_NOERR; } /* Sort a catalog */ static fiad_catalog *_sort_catalog; static bool _sort_order; static int _sort_disk(const void *a, const void *b) { // disk order return _sort_order ? ((char *)a - (char *)b) : ((char *)b - (char *)a); } static int _sort_filename(const void *a, const void *b) { const char *a1 = _sort_catalog->filenames[*(const int *)a]; const char *b1 = _sort_catalog->filenames[*(const int *)b]; return _sort_order ? strcasecmp(a1, b1) : strcasecmp(b1, a1); } static int _sort_type(const void *a, const void *b) { const fdrrec *a1 = &_sort_catalog->fdrs[*(const int *)a]; const fdrrec *b1 = &_sort_catalog->fdrs[*(const int *)b]; int diff; diff = (a1->flags & (ff_internal | ff_program | ff_variable)) - (b1->flags & (ff_internal | ff_program | ff_variable)); if (!_sort_order) diff = -diff; if (!diff) { diff = _sort_order ? a1->reclen - b1->reclen : b1->reclen - a1->reclen; } return diff; } static int _sort_size(const void *a, const void *b) { const fdrrec *a1 = &_sort_catalog->fdrs[*(const int *)a]; const fdrrec *b1 = &_sort_catalog->fdrs[*(const int *)b]; return _sort_order ? (FDR_FILESIZE(a1) - FDR_FILESIZE(b1)) : (FDR_FILESIZE(b1) - FDR_FILESIZE(a1)); } void fiad_catalog_sort_catalog(fiad_catalog *cat, int sort_by, bool ascending) { int (*func)(const void *a, const void *b); switch (sort_by) { case FIAD_CATALOG_SORT_BY_DISK: func = _sort_disk; break; case FIAD_CATALOG_SORT_BY_NAME: func = _sort_filename; break; case FIAD_CATALOG_SORT_BY_TYPE: func = _sort_type; break; case FIAD_CATALOG_SORT_BY_SIZE: func = _sort_size; break; default: fiad_logger(_L|LOG_FATAL, "Invalid catalog sort '%d'\n", sort_by); } _sort_catalog = cat; _sort_order = ascending; qsort(cat->index, cat->entries, sizeof(int), func); _sort_catalog = NULL; } /* Get file information from catalog */ int fiad_catalog_get_file_info(fiad_catalog *cat, int index, fdrrec *fdr) { if (index < 0 || index >= cat->entries) return 0; *fdr = cat->fdrs[cat->index[index]]; return 1; } /* * Return string giving standard name for type of file, i.e., "DIS/VAR 80" */ static struct { u8 fdr_flags; const char *name; } fiad_tifile_file_types[5] = { {0, "DIS/FIX"}, {ff_program, "PROGRAM"}, {ff_internal, "INT/FIX"}, {ff_variable, "DIS/VAR"}, {ff_variable+ff_internal, "INT/VAR"} }; const char * fiad_catalog_get_file_type_string(fdrrec *fdr) { int i; static char type_string[12]; for (i = 0; i < 5; i++) { if ((fdr->flags & (ff_program | ff_internal | ff_variable)) == fiad_tifile_file_types[i].fdr_flags) { // program files don't have record length if (fdr->flags & ff_program) { strcpy(type_string, fiad_tifile_file_types[i].name); } else { sprintf(type_string, "%s %d", fiad_tifile_file_types[i].name, fdr->reclen); } return type_string; } } return "???"; }