home *** CD-ROM | disk | FTP | other *** search
/ ftp.whtech.com / ftp.whtech.com.7z / ftp.whtech.com / emulators / v9t9 / linux / sources / V9t9 / source / fiad.c < prev    next >
Encoding:
C/C++ Source or Header  |  2006-10-19  |  41.0 KB  |  1,741 lines

  1. /*
  2.  *    File-in-a-directory library routines
  3.  *
  4.  */
  5.  
  6. #include "v9t9_common.h"
  7. #include "fiad.h"
  8.  
  9. #define _L    LOG_EMUDISK
  10. #define _LL    _L | LOG_USER
  11.  
  12. #if BEWORKS_FS
  13. OSFileType  osV99Type = { 0666, "x/v9t9-file-image" };
  14. OSFileType  osTIFILESType = { 0666, "x/tifiles-image" };
  15. #elif POSIX_FS
  16. OSFileType  osV99Type = { 0666 };
  17. OSFileType  osTIFILESType = { 0666 };
  18. #elif WIN32_FS
  19. OSFileType  osV99Type = 0;
  20. OSFileType  osTIFILESType = 0;
  21. #elif MAC_FS
  22. OSFileType  osV99Type = 'FI99';
  23. OSFileType  osTIFILESType = 'TI99';
  24. #endif
  25.  
  26. /*
  27.  *    Suitable logger for no-nag situations
  28.  */
  29. static void 
  30. fiad_no_logger(u32 srcflags, const char *format, ...)
  31. {
  32.     if (srcflags & LOG_FATAL)    exit(234);
  33. }
  34.  
  35. static void (*fiad_logger)(u32, const char *, ...) = fiad_no_logger;
  36.  
  37. /*
  38.  *    Install a new function to log warnings and errors
  39.  *    from the fiad_xxx routines.  Passing 'NULL' uses
  40.  *    no logging (the default).
  41.  */
  42. fiad_logger_func
  43. fiad_set_logger(fiad_logger_func nw)
  44. {
  45.     fiad_logger_func old = fiad_logger;
  46.     fiad_logger = nw == 0L ? fiad_no_logger : nw;
  47.     return old;
  48. }
  49.  
  50. /*    Convert a TI filename to a DOS 8.3 filename. */
  51. void
  52. fiad_filename_ti2dos(const char *tiname, int len, char *dosname)
  53. {
  54.     int         max = 10;
  55.     int         ptr = 0;
  56.     int         dptr = 0;
  57.  
  58.     while (len-- && max--) {
  59.         char        cur;
  60.  
  61.         cur = tiname[ptr];
  62.  
  63.         /* forced end-of-filename? */
  64.         if (cur == ' ' || cur == 0)
  65.             break;
  66.  
  67.         if (ptr == 8)
  68.             dosname[dptr++] = '.';
  69.  
  70.         /* offset illegal chars */
  71.         if (strchr(DOS_illegalchars, cur) != NULL)
  72.             cur |= 0x80;
  73.  
  74.         /* force uppercase */
  75.         if (cur >= 'a' && cur <= 'z')
  76.             cur -= 0x20;
  77.  
  78.         dosname[dptr++] = cur;
  79.         ptr++;
  80.     }
  81.  
  82.     dosname[dptr] = 0;
  83.     fiad_logger(_L | L_2, "fiad_filename_ti2dos:  incoming = '%.*s', outgoing = '%s'\n", 10 - max, tiname,
  84.          dosname);
  85. }
  86.  
  87. /* Convert a TI filename to the host OS.  
  88.  
  89.    We convert illegal chars in FIAD_illegalchars into HTML-like
  90.    encodings (&#xx;) so all possible filenames can be stored.
  91. */
  92. void
  93. fiad_filename_ti2host(const char *tiname, int len, char *hostname)
  94. {
  95.     int         max = 10;
  96.     int         hptr = 0, tptr = 0;
  97.  
  98.     while (len-- && max--) {
  99.         char        cur = tiname[tptr++];
  100.  
  101.         /*  force lowercase  */
  102.         if (cur >= 'A' && cur <= 'Z')
  103.             cur += 0x20;
  104.         else
  105.             // illegal chars
  106.         if (cur == FIAD_esc || strchr(FIAD_illegalchars, cur) != NULL) {
  107.             u8          hex;
  108.  
  109.             hostname[hptr++] = '&';
  110.             hostname[hptr++] = '#';
  111.             hex = (cur & 0xf0) >> 4;
  112.             if (hex > 9)
  113.                 hex += 'A' - 10;
  114.             else
  115.                 hex += '0';
  116.             hostname[hptr++] = hex;
  117.             hex = (cur & 0xf);
  118.             if (hex > 9)
  119.                 hex += 'A' - 10;
  120.             else
  121.                 hex += '0';
  122.             hostname[hptr++] = hex;
  123.             cur = ';';
  124.             //hostname[hptr++] = ';';
  125.         }
  126.  
  127.         hostname[hptr++] = cur;
  128.     }
  129.     hostname[hptr] = 0;
  130.     fiad_logger(_L | L_2, "fiad_filename_ti2host:  incoming = '%.*s', outgoing = '%s'\n", 10 - max, tiname,
  131.          hostname);
  132. }
  133.  
  134. /*    Rename an old DOS-mangled V9t9 filename to the new format 
  135.     
  136.     name, len:  TI-format name 
  137.     spec:        spec for existing file in an old format
  138.  
  139.     If successful, OS_NOERR is returned and 'spec' is transformed
  140.     into the current filename.
  141. */
  142. OSError
  143. fiad_filename_fixup_old_filename(const char *name, int len,
  144.                                  OSSpec *spec)
  145. {
  146.     char        newname[OS_NAMESIZE];
  147.     OSSpec      newspec;
  148.     OSError        err;
  149.  
  150.     fiad_filename_ti2host(name, len, newname);
  151.     if ((err = OS_MakeSpecWithPath(&spec->path, 
  152.                                    newname,
  153.                                    !mswp_noRelative,
  154.                                    &newspec)) == OS_NOERR
  155.         && (err = OS_Status(&newspec)) != OS_NOERR) 
  156.     {
  157.         fiad_logger(_LL | 0, "FIAD server:  renaming old-style file '%s' to '%s'\n",
  158.                     OS_NameSpecToString1(&spec->name), newname);
  159.         if ((err = OS_Rename(spec, &newspec)) != OS_NOERR)
  160.             fiad_logger(_LL | 0, "FIAD server:  could not rename file (%s)\n",
  161.                         OS_GetErrText(err));
  162.         else
  163.             *spec = newspec;
  164.     }
  165.     return err;
  166. }
  167.  
  168. /*    Create a full path given a TI filename and an OS path. */
  169. OSError
  170. fiad_filename_to_spec(const OSPathSpec * path, const char *name, int len, 
  171.                       OSSpec * spec)
  172. {
  173.     OSError     err;
  174.     char        osname[OS_NAMESIZE];
  175.  
  176.     /*  Try the old-style 8.3 name first */
  177.     fiad_filename_ti2dos(name, len, osname);
  178.     if ((err = OS_MakeSpecWithPath(path, osname,
  179.                                    !mswp_noRelative, spec)) != OS_NOERR
  180.         || (err = OS_Status(spec)) != OS_NOERR
  181.         || generateoldv9t9filenames) 
  182.     {
  183.         /* if they wants it, they gots it */
  184.         if (generateoldv9t9filenames) {
  185.             return err;
  186.         }
  187.  
  188.         /*  Now try the new-style name */
  189.         fiad_filename_ti2host(name, len, osname);
  190.  
  191.         /*  if it fails, it fails... */
  192.         return OS_MakeSpecWithPath(path, osname, !mswp_noRelative, spec);
  193.     } 
  194.  
  195.         /* rename it to the new format? */
  196.     else if (fixupoldv9t9filenames) {
  197.         /* 
  198.          *    We can be reasonably sure this is a v9t9 file.
  199.          */
  200.         fiad_filename_fixup_old_filename(name, len, spec);
  201.     }
  202.     return OS_NOERR;
  203. }
  204.  
  205. /*    Convert a filename to TI format,
  206.     return length of filename */
  207. int
  208. fiad_filename_host2ti(const char *hostname, char *tiname)
  209. {
  210.     int         hptr = 0, tptr = 0, max = 10;
  211.  
  212.     memset(tiname, ' ', 10);
  213.     while (hostname[hptr] && max) {
  214.         char        cur = hostname[hptr];
  215.  
  216.         if (cur != '.') {
  217.             /* force uppercase */
  218.             if (islower(cur))
  219.                 cur = toupper(cur);
  220.             else
  221.                 if (cur == '&' && hostname[hptr + 1] == '#' &&
  222.                     isxdigit(hostname[hptr + 2])
  223.                     && isxdigit(hostname[hptr + 3])
  224.                     && hostname[hptr + 4] == ';') {
  225.                 u8          val;
  226.  
  227.                 val = hostname[hptr + 2] - '0';
  228.                 if (val > 9)
  229.                     val -= 7;
  230.                 cur = hostname[hptr + 3] - '0';
  231.                 if (cur > 9)
  232.                     cur -= 7;
  233.                 cur |= val << 4;
  234.                 hptr += 4;
  235.             } else if ((cur & 0x80)
  236.                        && strchr(DOS_illegalchars, (cur & 0x7f)) != NULL)
  237.             {
  238.                 cur ^= 0x80;
  239.             }
  240.  
  241.             tiname[tptr] = cur;
  242.             tptr++;
  243.             max--;
  244.         }
  245.         hptr++;
  246.     }
  247.     fiad_logger(_L | L_2, "fiad_filename_host2ti:  incoming: '%s', outgoing: '%.10s'\n", hostname,
  248.          tiname);
  249.  
  250.     return 10 - max + strlen(hostname + hptr);
  251. }
  252.  
  253. /*    Return length of filename in FDR */
  254. int
  255. fiad_filename_strlen(const char *tf)
  256. {
  257.     int len = 0;
  258.     char *ptr = (char *)tf;
  259.     while (len < 10 && ptr[len] != ' ') {
  260.         len++;
  261.     }
  262.     return len;
  263. }
  264.  
  265. /*    Convert a directory leaf to a TI name at 'name' and return length. */
  266. int
  267. fiad_path_disk2ti(const OSPathSpec *path, char *name)
  268. {
  269.     char        pth[OS_PATHSIZE];
  270.     const char     *nptr;
  271.     int         len;
  272.  
  273.     OS_PathSpecToString2(path, pth);
  274.     pth[strlen(pth) - 1] = 0;
  275.     nptr = OS_GetFileNamePtr(pth);
  276.     len = 0;
  277.  
  278.     memset(name, ' ', 10);        // clear field
  279.  
  280.     while (len < 10 && *nptr) {
  281.         char ch = *nptr++;
  282.  
  283.         if (ch == '.')
  284.             ch = '_';            // no periods allowed
  285.         else if (ch >= 'a' && ch <= 'z')
  286.             ch -= 0x20;            // no lowercase either
  287.  
  288.         *name++ = ch;
  289.         len++;
  290.     }
  291.     return len;
  292. }
  293.  
  294. /*********************************/
  295.  
  296. /*    Verify a V9t9 FDR as a real v9t9 FDR.
  297.     Perform sanity checks to assert that a v9t9 file
  298.     is really a v9t9 file and not a text file. */
  299. bool
  300. fiad_fdr_matches_v9t9_fdr(struct v9t9_fdr *v9f, const char *filename, OSSize filesize)
  301. {
  302.     // check for invalid filetype flags
  303.     if (v9f->flags & ~FF_VALID_FLAGS) {
  304.             fiad_logger(_L | LOG_ERROR, "FIAD server:  invalid flags %02x "
  305.                         "for file '%s'\n",
  306.                         v9f->flags,
  307.                         filename);
  308.             return false;
  309.     }
  310.  
  311.     // check for invalid file size:
  312.     // do not allow file to be more than one sector larger than FDR says,
  313.     // but allow it to be up to 64 sectors smaller:  
  314.     // this is a concession for files copied with "direct output to file", 
  315.     // which must write FDR changes before writing data.
  316.     else if ((long)TI2HOST(v9f->secsused) < (long)((filesize - FDRSIZE) / 256 - 1)
  317.              || TI2HOST(v9f->secsused) > (filesize - FDRSIZE) / 256 + 64) {
  318.             fiad_logger(_L | LOG_ERROR, "FIAD server:  invalid number of sectors %d "
  319.                         "for data size %d in file '%s'\n",
  320.                         TI2HOST(v9f->secsused), 
  321.                         filesize - FDRSIZE,
  322.                         filename);
  323.             return false;
  324.     }
  325.  
  326.     // fixed files have 256/reclen records per sector
  327.     else if (!(v9f->flags & ff_program)
  328.         && !(v9f->flags & ff_variable)) {
  329.         if (!v9f->reclen ||
  330.             (256 / v9f->reclen != v9f->recspersec)) 
  331.         {
  332.             fiad_logger(_L | LOG_ERROR, "FIAD server:  record length %d / records per sector %d invalid\n"
  333.                         "for FIXED file '%s'\n",
  334.                         v9f->reclen,
  335.                         v9f->recspersec,
  336.                         filename);
  337.             return false;
  338.         }
  339.     }
  340.     // variable files have 255/(reclen+1) records per sector
  341.     else if (!(v9f->flags & ff_program)) {
  342.         if (!v9f->reclen || 
  343.             (255 / (v9f->reclen + 1) != v9f->recspersec) 
  344.              // known problem that older v9t9s used this calculation
  345.             && (256 / v9f->reclen != v9f->recspersec))
  346.         {
  347.             fiad_logger(_L | LOG_ERROR, "FIAD server:  record length %d / records per sector %d invalid\n"
  348.                         "for VARIABLE file '%s'\n",
  349.                         v9f->reclen,
  350.                         v9f->recspersec,
  351.                         filename);
  352.             return false;
  353.         }
  354.     }
  355.  
  356.     // program files have 0
  357.     else if (v9f->reclen != 0 && v9f->recspersec != 0) {
  358.         fiad_logger(_LL | LOG_ERROR, "FIAD server:  record length %d / records per sector %d invalid\n"
  359.                     "for PROGRAM file '%s'\n",
  360.                     v9f->reclen,
  361.                     v9f->recspersec,
  362.                     filename);
  363.         return false;
  364.     }
  365.  
  366.     return true;
  367. }
  368.  
  369. /*    Setup the various flags in an FDR
  370.     according to the minimum filetype info */
  371. void
  372. fiad_fdr_setup(fdrrec *fdr, bool program, u8 flags, u8 reclen, u32 size)
  373. {
  374.     if (program) {
  375.         fdr->flags = ff_program;
  376.         fdr->recspersec = 0;
  377.         fdr->reclen = 0;
  378.     } else {
  379.         fdr->reclen = reclen ? reclen : 80;
  380.         fdr->flags = flags & FF_VALID_FLAGS;
  381.         fdr->recspersec = 
  382.             (flags & ff_variable) ?
  383.             (255 / (fdr->reclen + 1)) :
  384.             (256 / fdr->reclen);
  385.     }
  386.     fdr->secsused = (size + 255) >> 8;
  387.     fdr->byteoffs = size & 0xff;
  388.     if (fdr->flags & ff_variable) {
  389.         fdr->numrecs = fdr->secsused;
  390.     } else if (!(fdr->flags & ff_program)) {
  391.         fdr->numrecs = size / fdr->reclen;
  392.     } else {
  393.         fdr->numrecs = 0;
  394.     }
  395. }
  396.  
  397. /*    Repair various fields of the FDR according to
  398.     known bugs or common file-closing problems.
  399.  
  400.     Returns true if fdr was changed.
  401. */
  402. bool
  403. fiad_fdr_repair(fdrrec *fdr, OSSize datasize)
  404. {
  405.     fdrrec orig = *fdr;
  406.  
  407.     // fix invalid record length...
  408.     if (!(fdr->flags & ff_program)
  409.         && fdr->reclen == 0) 
  410.     {
  411.         fdr->reclen = 80;
  412.     }
  413.  
  414.     // fixup for buggy older versions
  415.     // which didn't calculate recspersec right
  416.     // for variable or fixed files
  417.     if (fdr->flags & ff_variable) {
  418.         fdr->recspersec = 256 / (fdr->reclen + 1);    // was 256 / reclen
  419.     } else if (!(fdr->flags & ff_program)) {
  420.         fdr->recspersec = 255 / fdr->reclen;        // was 256 / reclen
  421.     } else if (fdr->flags & ff_program) {
  422.         fdr->recspersec = 0;
  423.         fdr->reclen = 0;
  424.     }
  425.  
  426.     fdr->secsused = (datasize + 255) / 256;
  427.  
  428.     // fix for number of records used in variable file
  429.     if (fdr->flags & ff_variable) {
  430.         fdr->numrecs = fdr->secsused;
  431.     }
  432.  
  433.     return (memcmp((void *)&orig, (void *)fdr, sizeof(fdrrec)) != 0);
  434. }
  435.  
  436. /*********************************/
  437.  
  438. /*    Initialize a tifile */
  439. void
  440. fiad_tifile_clear(fiad_tifile *tf)
  441. {
  442.     tf->open = false;
  443.     tf->readonly = false;
  444.     tf->changed = false;
  445.     tf->changedfdr = false;
  446.     tf->handle = 0;
  447.     tf->error = OS_NOERR;
  448.     memset((void *)&tf->fdr, 0, sizeof(fdrrec));
  449. }
  450.  
  451. /*    Setup the FDR with the file path. */
  452. OSError
  453. fiad_tifile_setup_spec_with_file(fiad_tifile *tf, 
  454.                                  const OSPathSpec *path, 
  455.                                  const char *fname, int len)
  456. {
  457.     if (len > 10) {
  458.         return OS_FNTLERR;
  459.     }
  460.  
  461.     // copy filename into FDR
  462.     memset(tf->fdr.filenam, ' ', 10);
  463.     memcpy(tf->fdr.filenam, fname, len);
  464.     fiad_logger(_L | L_2, "setup FDR name as '%.10s'\n", tf->fdr.filenam);
  465.  
  466.     return fiad_filename_to_spec(path, fname, len, &tf->spec);
  467. }
  468.  
  469. /*    Setup the FDR with the OSSpec. */
  470. OSError
  471. fiad_tifile_setup_spec_with_spec(fiad_tifile *tf, 
  472.                                  OSSpec *spec)
  473. {
  474.     char tiname[10];
  475.     int len;
  476.  
  477.     len = fiad_filename_host2ti(OS_NameSpecToString1(&spec->name), tiname);
  478.     if (len > 10) {
  479.         return OS_FNTLERR;
  480.     }
  481.  
  482.     tf->spec = *spec;
  483.  
  484.     /* Since that was successful, now backtrack and see if the
  485.        file was in the old format */
  486.     if (fixupoldv9t9filenames) {
  487.         /*        
  488.          * We can't assume that 'tiname' is the actual name
  489.          * of the file due to weird extended ASCII mangling
  490.          * by the filesystem or unzip, so, if the file really exists,
  491.          * read its FDR and get the name from there.
  492.          */
  493.  
  494.         if (fiad_tifile_get_info(tf)) {
  495.             if (tf->format == F_V9t9) {
  496.                 memcpy(tiname, tf->fdr.filenam, 10);
  497.                 len = fiad_filename_strlen(tf->fdr.filenam);
  498.                 fiad_filename_fixup_old_filename(tiname, len, &tf->spec);
  499.             } else if (tf->format == F_TIFILES) {
  500.                 /*
  501.                  *    There's no information about the original filename.
  502.                  *    Blast!  Hope/pray/assume nothing screwed up the
  503.                  *    extended characters.
  504.                  */
  505.                 fiad_filename_fixup_old_filename(tiname, len, &tf->spec);
  506.             }
  507.         }
  508.     }
  509.  
  510.     // copy filename into FDR
  511.     memset(tf->fdr.filenam, ' ', 10);
  512.     memcpy(tf->fdr.filenam, tiname, len);
  513.     fiad_logger(_L | L_2, "setup FDR name as '%.10s'\n", tf->fdr.filenam);
  514.  
  515.     return OS_NOERR;
  516. }
  517.  
  518. /*    Read the FDR from a file,
  519.     return 0 if it's bad. */
  520. int
  521. fiad_tifile_read_fdr(fiad_tifile * tf)
  522. {
  523.     OSError     err;
  524.     u8          fdrsec[FDRSIZE];
  525.     OSSize      len = FDRSIZE, flen;
  526.     fdrrec        *fdr = &tf->fdr;
  527.  
  528.     /* can we read the FDR? */
  529.     if ((err = OS_GetSize(tf->handle, &flen)) != OS_NOERR ||
  530.         (err = OS_Seek(tf->handle, OSSeekAbs, 0)) != OS_NOERR ||
  531.         (err = OS_Read(tf->handle, (void *) fdrsec, &len)) != OS_NOERR) {
  532.         tf->error = err;
  533.         fiad_logger(_L|LOG_ERROR, "%s: could not read FDR\n", OS_SpecToString1(&tf->spec));
  534.         return 0;
  535.     } 
  536.     /* is it big enough? */
  537.     else if (len < FDRSIZE && !unknownfileistext) {
  538.         fiad_logger(_L|LOG_ERROR, "%s: FDR is short\n", OS_SpecToString1(&tf->spec));
  539.         return 0;
  540.     } 
  541.     /* is it a TIFILES or V9t9 file, or text? */
  542.     else {
  543.         v9t9_fdr   *v9f = (v9t9_fdr *) fdrsec;
  544.         tifiles_fdr *tif = (tifiles_fdr *) fdrsec;
  545.         char        filename[OS_NAMESIZE];
  546.  
  547.         /*  Figure out what kind it is and convert. */
  548.         if (memcmp(tif->sig, "\007TIFILES", 8) == 0) {
  549.             // TIFILES fdr has no filename
  550.             tf->format = F_TIFILES;
  551.             OS_NameSpecToString2(&tf->spec.name, filename);
  552.             fiad_filename_host2ti(filename, fdr->filenam);
  553.             fdr->secsused = TI2HOST(tif->secsused);
  554.             fdr->flags = tif->flags;
  555.             fdr->recspersec = tif->recspersec;
  556.             fdr->byteoffs = tif->byteoffs;
  557.             fdr->reclen = tif->reclen;
  558.             fdr->numrecs = SWAPTI(tif->numrecs);
  559.             fiad_logger(_L | L_3, "TIFILES read> secsused=%04X, flags=%x, recspersec=%x, byteoffs=%x, reclen=%d, numrecs=%04X\n",
  560.                    tif->secsused, tif->flags, tif->recspersec,
  561.                    tif->byteoffs, tif->reclen, tif->numrecs);
  562.         } else if (len == FDRSIZE 
  563.                    && fiad_fdr_matches_v9t9_fdr(v9f, OS_SpecToString1(&tf->spec), flen)) {
  564.             // assume V9t9
  565.  
  566.             tf->format = F_V9t9;
  567.             memcpy(fdr->filenam, v9f->filenam, 10);
  568.             fdr->flags = v9f->flags;
  569.             fdr->recspersec = v9f->recspersec;
  570.             fdr->secsused = TI2HOST(v9f->secsused);
  571.             fdr->byteoffs = v9f->byteoffs;
  572.             fdr->reclen = v9f->reclen;
  573.             fdr->numrecs = SWAPTI(v9f->numrecs);
  574.             fiad_logger(_L | L_3, "V9t9 read> secsused=%04X, flags=%x, recspersec=%x, byteoffs=%x, reclen=%d, numrecs=%04X\n",
  575.                    v9f->secsused, v9f->flags, v9f->recspersec,
  576.                    v9f->byteoffs, v9f->reclen, v9f->numrecs);
  577.         } else if (unknownfileistext) {
  578.             // treat file as text
  579.  
  580.             fiad_logger(_L | L_2, "Treating '%s' as text\n", OS_SpecToString1(&tf->spec));
  581.             tf->format = F_TEXT;
  582.             OS_NameSpecToString2(&tf->spec.name, filename);
  583.             fiad_filename_host2ti(filename, fdr->filenam);
  584.             fiad_fdr_setup(fdr, false /*program*/, ff_variable, 80, flen);
  585.         } else {
  586.             fiad_logger(_L | LOG_ERROR, "File '%s' does not appear to be a V9t9 file\n", OS_SpecToString1(&tf->spec));
  587.             return 0;
  588.             
  589.         }
  590.         tf->changedfdr = false;
  591.         return 1;
  592.     }
  593. }
  594.  
  595. /*    Write FDR to file, 
  596.     return 0 and report error if failed. */
  597. int
  598. fiad_tifile_write_fdr(fiad_tifile * tf)
  599. {
  600.     OSError     err;
  601.     u8          fdrsec[FDRSIZE];
  602.     OSSize      len = FDRSIZE;
  603.     fdrrec        *fdr = &tf->fdr;
  604.  
  605.     if (tf->readonly || !tf->open) {
  606.         fiad_logger(_LL|LOG_ERROR, "%s: Trying to write FDR on a closed or read-only file\n", OS_SpecToString1(&tf->spec));
  607.         return 0;
  608.     }
  609.  
  610.     memset(fdrsec, 0, FDRSIZE);
  611.  
  612.     //  Decide what format to write the FDR in.
  613.  
  614.     //     don't write one for text or for an unknown file
  615.     if (tf->format == F_TEXT || tf->format == F_UNKNOWN) {
  616.         tf->changedfdr = false;
  617.         return 1;
  618.     }
  619.  
  620.     //     write FDR in given format
  621.     if (tf->format == F_V9t9) {
  622.         v9t9_fdr   *v9f = (v9t9_fdr *) fdrsec;
  623.  
  624.         tf->format = F_V9t9;
  625.         memcpy(v9f->filenam, fdr->filenam, 10);
  626.         v9f->flags = fdr->flags;
  627.         v9f->recspersec = fdr->recspersec;
  628.         v9f->secsused = HOST2TI(fdr->secsused);
  629.         v9f->byteoffs = fdr->byteoffs;
  630.         v9f->reclen = fdr->reclen;
  631.         v9f->numrecs = SWAPTI(fdr->numrecs);
  632.     } else {
  633.         tifiles_fdr *tif = (tifiles_fdr *) fdrsec;
  634.  
  635.         tf->format = F_TIFILES;
  636.         memcpy(tif->sig, "\007TIFILES", 8);
  637.         tif->flags = fdr->flags;
  638.         tif->recspersec = fdr->recspersec;
  639.         tif->secsused = HOST2TI(fdr->secsused);
  640.         tif->byteoffs = fdr->byteoffs;
  641.         tif->reclen = fdr->reclen;
  642.         tif->numrecs = SWAPTI(fdr->numrecs);
  643. /*            fiad_logger(_LL, "TIFILES write> secsused=%04X, flags=%x, recspersec=%x, byteoffs=%x, reclen=%d, numrecs=%04X\n",
  644.                    tif->secsused, tif->flags, tif->recspersec,
  645.                    tif->byteoffs, tif->reclen, tif->numrecs);
  646. */
  647.     }
  648.  
  649.     if ((err = OS_Seek(tf->handle, OSSeekAbs, 0)) != OS_NOERR ||
  650.         (err = OS_Write(tf->handle, (void *) fdrsec, &len)) != OS_NOERR) {
  651.         tf->error = err;
  652.         fiad_logger(_LL|LOG_ERROR, "%s: could not write FDR\n", OS_SpecToString1(&tf->spec));
  653.         return 0;
  654.     } else if (len < FDRSIZE) {
  655.         fiad_logger(_LL|LOG_ERROR, "%s: wrote short FDR\n", OS_SpecToString1(&tf->spec));
  656.         return 0;
  657.     } else {
  658.         tf->changedfdr = false;
  659.         return 1;
  660.     }
  661. }
  662.  
  663.  
  664. /*    Verify a file, by classifying its type and checking invariants */
  665. int
  666. fiad_tifile_verify(fiad_tifile * tf, bool fixup)
  667. {
  668.     OSSize      sz;
  669.     OSError     err;
  670.  
  671.     if (!fiad_tifile_read_fdr(tf)) {
  672.         fiad_logger(_LL | LOG_ERROR, "FIAD server: can't read FDR from file '%s'\n",
  673.               OS_SpecToString1(&tf->spec));
  674.         return 0;
  675.     }
  676.  
  677.     if (tf->format == F_TEXT)
  678.         return 1;
  679.  
  680.     if (tf->format == F_UNKNOWN)
  681.         return 0;
  682.  
  683.     err = OS_GetSize(tf->handle, &sz);
  684.  
  685.     if (err != OS_NOERR || sz < FDRSIZE) {
  686.         fiad_logger(_LL | LOG_ERROR, "FIAD server:  FDR is short in file '%s'\n",
  687.               OS_SpecToString1(&tf->spec));
  688.         return 0;
  689.     }
  690.  
  691.     if (tf->format == F_V9t9) {
  692.         sz -= FDRSIZE;
  693.     }
  694.  
  695.     if (fixup && repairbadfiles) {
  696.         tf->changedfdr = fiad_fdr_repair(&tf->fdr, sz);
  697.  
  698.         fiad_logger(_LL, "FIAD server:  repaired fields of FDR in '%s'\n",
  699.               OS_SpecToString1(&tf->spec));
  700.     }
  701.  
  702.     return 1;
  703. }
  704.  
  705. OSFileType *
  706. fiad_get_file_type(int newfileformat)
  707. {
  708.     return     
  709.         newfileformat == F_V9t9 ? &osV99Type :
  710.         newfileformat == F_TIFILES ? &osTIFILESType :
  711.         &OS_TEXTTYPE;
  712. }
  713.  
  714. /*    Initialize file pointers which keep track of current record */
  715. int
  716. fiad_tifile_init_file_pointers(fiad_tifile * tf)
  717. {
  718.     tf->cursec = tf->curoffs = tf->curnrecs = tf->currec = 0;
  719.     tf->changed = false;
  720.     tf->changedfdr = false;
  721.     return 1;
  722. }
  723.  
  724. /*    Read a sector from the file at tf->cursec. 
  725.     If tf points to the last empty sector of file, 
  726.     this is not an error.
  727.     Return 0 if sector not found.  */
  728. int
  729. fiad_tifile_read_sector(fiad_tifile * tf)
  730. {
  731.     OSPos       pos;
  732.     OSSize      sz = 256;
  733.     OSError        err;
  734.  
  735.     if (!fiad_tifile_flush(tf))
  736.         return 0;
  737.  
  738.     pos = tf->cursec * 256 + (tf->format != F_TEXT ? FDRSIZE : 0);
  739.  
  740.     // Try to read it...
  741.     if ((err = OS_Seek(tf->handle, OSSeekAbs, pos)) != OS_NOERR ||
  742.         (err = OS_Read(tf->handle, tf->sector, &sz)) != OS_NOERR) 
  743.     {
  744.         // bad error
  745.         tf->error = err;
  746.         return 0;
  747.     } 
  748.     else if (sz < 256) {
  749.         if (tf->cursec == tf->fdr.secsused && sz == 0) {
  750.             // last sector can be empty
  751.             return 1;
  752.         } else if (tf->format != F_TEXT || tf->cursec != tf->fdr.secsused - 1) {
  753.             // last sector can be short, but not the other ones
  754.             return 0;
  755.         } else
  756.             return 1;
  757.     } else {
  758.         return 1;
  759.     }
  760. }
  761.  
  762. /*    Write a sector to the file at tf->cursec. 
  763.     Return 0 if sector not written.  */
  764. int
  765. fiad_tifile_write_sector(fiad_tifile * tf)
  766. {
  767.     OSPos       pos;
  768.     OSSize      sz = 256;
  769.     OSError        err;
  770.  
  771.     pos = tf->cursec * 256 + (tf->format != F_TEXT ? FDRSIZE : 0);
  772.  
  773.     if ((err = OS_Seek(tf->handle, OSSeekAbs, pos)) != OS_NOERR ||
  774.         (err = OS_Write(tf->handle, tf->sector, &sz)) != OS_NOERR) 
  775.     {
  776.         // bad error
  777.         tf->error = err;
  778.         return 0;
  779.     } else if (sz < 256) {
  780.         // no sector can be written short
  781.         tf->error = OS_MEMERR;        // "out of space"
  782.         return 0;
  783.     } else {
  784.         tf->changed = false;
  785.         return 1;
  786.     }
  787. }
  788.  
  789. /*  Set file size according to cursec and curoffs */
  790. int
  791. fiad_tifile_set_file_size(fiad_tifile *tf)
  792. {
  793.        OSPos       pos;
  794.        OSSize      sz = 0;
  795.        OSError         err;
  796.  
  797.        pos = tf->cursec * 256 + (tf->format != F_TEXT ? FDRSIZE : 0);
  798.  
  799.        if ((err = OS_SetSize(tf->handle, pos)) != OS_NOERR)
  800.        {
  801.                // bad error
  802.                tf->error = err;
  803.                return 0;
  804.        } else {
  805.                return 1;
  806.        }
  807. }
  808.  
  809. /*
  810.   Open or create the file, either r/w or r/o.
  811.   We expect the spec to have been set up.
  812.  
  813.   create && always means, delete existing file.
  814.   create means, create file if not existing.
  815.  */
  816. OSError
  817. fiad_tifile_open_file(fiad_tifile *tf, int newfileformat,
  818.                       bool create, bool always, bool readonly)
  819. {
  820.     OSError     err;
  821.     bool        creating = false;
  822.  
  823.     fiad_logger(_L | L_3, "fiad_tifile_open_file: '%s', create: %d, always: %d, readonly: %d\n", 
  824.            OS_SpecToString1(&tf->spec), create, always, readonly);
  825.  
  826.     tf->open = false;
  827.     tf->readonly = readonly;
  828.     tf->format = F_UNKNOWN;
  829.     fiad_tifile_init_file_pointers(tf);
  830.  
  831.     if (create) {
  832.         // go ahead and delete if we want a clean slate
  833.         if (always) {
  834.             OS_Delete(&tf->spec);
  835.         }
  836.  
  837.         // see if file exists
  838.         err = OS_Status(&tf->spec);
  839.  
  840.         // if not (or the file is broken), try to create it
  841.         if (err != OS_NOERR) {
  842.             err = OS_Create(&tf->spec, fiad_get_file_type(newfileformat));
  843.  
  844.             // if we couldn't create, error
  845.             if (err != OS_NOERR) {
  846.                 return err;
  847.             }
  848.             if (err == OS_NOERR) {
  849.                 fiad_logger(_L | L_2, "created FIAD file '%s'\n", OS_SpecToString1(&tf->spec));
  850.             }
  851.             tf->format = newfileformat;
  852.             creating = true;
  853.         }
  854.     }
  855.  
  856.     /* Try to open file; it should exist by now */
  857.     err = OS_Open(&tf->spec, readonly ? OSReadOnly : OSReadWrite, 
  858.                   &tf->handle);
  859.  
  860.     tf->open = (err == OS_NOERR);
  861.     tf->changed = false;
  862.     tf->changedfdr = false;
  863.  
  864.     /* Permission error? */
  865.     if (!readonly && err == OS_PERMERR) {
  866.         return err;
  867.     }
  868.  
  869.     /* It was opened, verify it.
  870.        If verification fails, but we can create, then do so. */
  871.     if (!creating && err == OS_NOERR) {
  872.         if (!fiad_tifile_verify(tf, !readonly /*fixup*/)) {
  873.             if (create) {
  874.                 fiad_logger(_L|L_2, "Failed to verify, creating new file\n");
  875.                 creating = true;
  876.                 tf->format = newfileformat;
  877.                 err = OS_NOERR;
  878.             } else {
  879.                 fiad_tifile_close_file(tf);
  880.                 err = OS_FNFERR;
  881.             }
  882.         }
  883.     }
  884.  
  885.     /* Existing but wanting to create? */
  886.     if (create && creating && err == OS_NOERR) {
  887.         fiad_logger(_L|L_2, "Truncating file\n");
  888.         err = OS_SetSize(tf->handle, 0);
  889.  
  890.         /* don't write FDR here; caller uses this to determine
  891.            if we created the file or not */
  892.         tf->open = (err == OS_NOERR);
  893.         tf->changed = true;
  894.         tf->changedfdr = true;
  895.     }
  896.  
  897.     /* Change FDR format? */
  898.     if (tf->format != newfileformat &&
  899.         !keepfileformat && 
  900.         tf->format != F_TEXT &&
  901.         newfileformat != F_TEXT)
  902.     {
  903.         tf->format = newfileformat;
  904.         tf->changedfdr = true;
  905.     }
  906.  
  907.     return err;
  908. }
  909.  
  910. /*
  911.  *    Reopen a file, keeping open file information intact.
  912.  */
  913. OSError
  914. fiad_tifile_reopen_file(fiad_tifile *tf, int newfileformat,
  915.                         bool readonly)
  916. {
  917.     OSError err;
  918.     fiad_tifile copy = *tf;
  919.     err = fiad_tifile_open_file(tf, 
  920.                                 newfileformat, 
  921.                                 false /*create*/,
  922.                                 false /*always*/,
  923.                                 readonly);
  924.  
  925.     tf->cursec = copy.cursec;
  926.     tf->curoffs = copy.curoffs;
  927.     tf->curnrecs = copy.curnrecs;
  928.     tf->currec = copy.currec;
  929.     fiad_tifile_read_sector(tf);
  930.  
  931.     return err;
  932. }
  933.  
  934. /*
  935.  *    Close a tifile.
  936.  */
  937. void
  938. fiad_tifile_close_file(fiad_tifile * tf)
  939. {
  940.     if (!tf || !tf->open)
  941.         return;
  942.  
  943.     /*    ignore errors */
  944.     fiad_tifile_flush(tf);
  945.     OS_Close(tf->handle);
  946.  
  947.     tf->open = false;
  948.     tf->handle = (OSRef) -1;
  949.  
  950.     /* if FDR is still different, file was either readonly
  951.        or we changed something we should write now */
  952.     if (tf->changedfdr) {
  953.         fiad_tifile_rewrite_fdr(tf);
  954.     }
  955. }
  956.  
  957. /*
  958.   Rewrite FDR of closed file 
  959.  
  960.   This may be used to change the format (change tf->format)
  961.   or rewrite a fixed FDR on a readonly file.
  962. */
  963. void
  964. fiad_tifile_rewrite_fdr(fiad_tifile *tf)
  965. {
  966.     int newfileformat = tf->format;
  967.  
  968.     if (tf->open) 
  969.         fiad_logger(LOG_FATAL, "fiad_tifile_rewrite_fdr: File is open!\n");
  970.  
  971.     if (fiad_tifile_open_file(tf, 
  972.                               F_UNKNOWN, 
  973.                               false /*create*/, 
  974.                               false /*always*/, 
  975.                               false /*readonly*/) == OS_NOERR)
  976.     {
  977.         if (fiad_tifile_read_fdr(tf)) {
  978.  
  979.             if ((newfileformat == F_V9t9 || newfileformat == F_TIFILES) &&
  980.                 newfileformat != tf->format) {
  981.                 fiad_logger(_L|L_2, "%s: changing file format to %s\n",
  982.                        OS_SpecToString1(&tf->spec),
  983.                        newfileformat == F_V9t9 ? "V9t9" : "TIFILES");
  984.  
  985.                 tf->format = newfileformat;
  986.             } else if (!fiad_tifile_verify(tf, true /*fixup*/) 
  987.                        && repairbadfiles) {
  988.                 fiad_logger(_L|L_2, "%s: rewriting damaged FDR\n",
  989.                        OS_SpecToString1(&tf->spec));
  990.             } else {
  991.                 goto dont_write;
  992.             }
  993.             fiad_tifile_write_fdr(tf);
  994.         }
  995.     dont_write:
  996.         fiad_tifile_close_file(tf);
  997.     }
  998. }
  999.  
  1000. /*    Flush dirty buffers */
  1001. int
  1002. fiad_tifile_flush(fiad_tifile *tf)
  1003. {
  1004.     if (!tf->open)
  1005.         return 0;
  1006.  
  1007.     if (tf->readonly)
  1008.         return 1;
  1009.  
  1010.     if (tf->changed) {
  1011.         if (!fiad_tifile_write_sector(tf))
  1012.             return 0;
  1013.     }
  1014.     if (tf->changedfdr) {
  1015.         if (!fiad_tifile_write_fdr(tf))
  1016.             return 0;
  1017.     }
  1018.     return 1;
  1019. }
  1020.  
  1021. /*    Change the current sector */
  1022. int
  1023. fiad_tifile_seek_to_sector(fiad_tifile *tf, int secnum)
  1024. {
  1025.     if (!fiad_tifile_flush(tf))
  1026.         return 0;
  1027.     tf->cursec = secnum;
  1028.     tf->curoffs = 0;
  1029.     return 1;
  1030. }
  1031.  
  1032. /*    Seek, logically, to the end of file */
  1033. int
  1034. fiad_tifile_seek_to_end(fiad_tifile * tf)
  1035. {
  1036.     if (!fiad_tifile_seek_to_sector(tf, FDR_LASTSEC(&tf->fdr)))
  1037.         return 0;
  1038.     tf->curoffs = tf->fdr.byteoffs;
  1039.     tf->curnrecs = 0;
  1040.     tf->currec = 0;
  1041.     return fiad_tifile_read_sector(tf);
  1042. }
  1043.  
  1044. /*    Read a record from a file (all but PROGRAM files)
  1045.  
  1046.     Return 0 for success, 1 for EOF, and -1 for hardware failure
  1047. */
  1048. int fiad_tifile_read_record(fiad_tifile *tf, u8 *data, u8 *reclen)
  1049. {
  1050.     if ((tf->fdr.flags & ff_variable) && tf->format != F_TEXT) {
  1051.  
  1052.         /* read variable data record */
  1053.         u8          len;
  1054.  
  1055.       retry_var_read:
  1056.         //  Get a new sector?
  1057.         if (!tf->curoffs) {
  1058.             // any more sectors?
  1059.             if (tf->cursec < tf->fdr.secsused) {
  1060.                 if (!fiad_tifile_read_sector(tf)) {
  1061.                     return -1;
  1062.                 }
  1063.             } else {
  1064.                 return 1;    // EOF
  1065.             }
  1066.         }
  1067.  
  1068.         len = tf->sector[tf->curoffs];
  1069.  
  1070.         // 0xff means end of sector
  1071.         if (len == 0xff) {
  1072.             if (!fiad_tifile_seek_to_sector(tf, tf->cursec+1)) {
  1073.                 return -1;
  1074.             }
  1075.             goto retry_var_read;
  1076.         }
  1077.  
  1078.         if (len > tf->fdr.reclen) {
  1079.             //fiad_logger(_L | "FIAD ERROR:  length of record on disk is longer than maximum");
  1080.             // this is legal; we only set tf->fdr.reclen bytes,
  1081.             // but advance the pointer 'len' bytes.
  1082.         }
  1083.  
  1084.         if ((u32) tf->curoffs + (u32) len >= 256) {
  1085.             fiad_logger(_LL | LOG_ERROR,
  1086.                   "FIAD ERROR:  file appears corrupt [curoffs=%d, len=%d]\n\n\n",
  1087.                   tf->curoffs, len);
  1088.             len = 255 - tf->curoffs;
  1089.             tf->cursec++;
  1090.         }
  1091.  
  1092.         *reclen = len > tf->fdr.reclen ? tf->fdr.reclen : len;
  1093.         memcpy(data, tf->sector + tf->curoffs + 1, *reclen);
  1094.         tf->curoffs += len + 1;    // length byte
  1095.     
  1096.     } else if ((tf->fdr.flags & ff_variable) && tf->format == F_TEXT) {
  1097.  
  1098.         /* read variable data from text file */
  1099.         int         len;
  1100.         int         lastbyte, strln, cpyln;
  1101.         char        str255[256];
  1102.         bool        at_eoln;
  1103.  
  1104.         fiad_logger(_L | L_3, "Variable read from F_TEXT: #%d/%d %d/%d\n", tf->cursec,
  1105.              tf->fdr.secsused, tf->curoffs, tf->fdr.byteoffs);
  1106.         *str255 = 0;
  1107.         strln = 0;
  1108.  
  1109.       retry_textvar_read:
  1110.         lastbyte =
  1111.             (tf->cursec < tf->fdr.secsused - 1 ? 255 : tf->fdr.byteoffs);
  1112.         fiad_logger(_L | L_3, "lastbyte=%d\n", lastbyte);
  1113.  
  1114.         if (tf->curoffs >= lastbyte) {
  1115.             tf->curoffs = 0;
  1116.             tf->cursec++;
  1117.         }
  1118.         //  Get a new sector?
  1119.         if (!tf->curoffs) {
  1120.             // any more sectors?
  1121.             if (tf->cursec < tf->fdr.secsused) {
  1122.                 if (!fiad_tifile_read_sector(tf)) {
  1123.                     return -1;
  1124.                 }
  1125.             } else {
  1126.                 return 1;    // EOF
  1127.             }
  1128.         }
  1129.  
  1130.         /*  Read until newline */
  1131.         len = 0;
  1132.         at_eoln = false;
  1133.         while (tf->curoffs + len <= lastbyte) {
  1134.             len++;
  1135.             if (tf->sector[tf->curoffs + len - 1] == '\r' ||
  1136.                 tf->sector[tf->curoffs + len - 1] == '\n') 
  1137.             {
  1138.                 at_eoln = true;
  1139.                 break;
  1140.             }
  1141.         }
  1142.  
  1143.         /*
  1144.          *     We can only copy up to 255 bytes into a record (and even then,
  1145.          *    it will be truncated according to the record size in the PAB).
  1146.          *  Perhaps add an option to split long lines into smaller records?
  1147.          */
  1148.         if (strln > 255)
  1149.             cpyln = 0;
  1150.         else if (strln + (len - (at_eoln == true)) > 255)
  1151.             cpyln = 255 - strln;
  1152.         else 
  1153.             cpyln = len - (at_eoln == true);
  1154.  
  1155.         memcpy(str255 + strln, tf->sector + tf->curoffs, cpyln);
  1156.  
  1157.         tf->curoffs += len;
  1158.         strln += cpyln;
  1159.         str255[strln] = 0;
  1160.  
  1161.         fiad_logger(_L | L_2, "F_TEXT: '%s'\n", str255);
  1162.  
  1163.         /*  Skip newline(s) */
  1164.         if (tf->curoffs) {
  1165.             if (tf->sector[tf->curoffs] == '\r' &&
  1166.                 (tf->curoffs <= lastbyte - 1
  1167.                  && tf->sector[tf->curoffs + 1] == '\n')) tf->curoffs++;
  1168.         }
  1169.  
  1170.         /*  Get new sector? */
  1171.         if (!tf->curoffs) {
  1172.             if (!fiad_tifile_seek_to_sector(tf, tf->cursec+1)) {
  1173.                 return -1;
  1174.             }
  1175.             goto retry_textvar_read;
  1176.         }
  1177.  
  1178.         len = strln;
  1179.         if (len > tf->fdr.reclen) {
  1180.             //fiad_logger(_L | "FIAD ERROR:  length of record on disk is longer than maximum");
  1181.             // this is legal; we only set tf->fdr.reclen bytes,
  1182.             // but advance the pointer 'len' bytes.
  1183.         }
  1184.  
  1185.         *reclen = len > tf->fdr.reclen ? tf->fdr.reclen : len;
  1186.         memcpy(data, str255, *reclen);
  1187.  
  1188.     } else {
  1189.  
  1190.         /* read fixed data record */
  1191.         u16         newsec;
  1192.  
  1193.         fiad_logger(_L | L_3, "fixed read, wanted %d, max is %d\n\n", tf->currec,
  1194.              tf->fdr.numrecs);
  1195.         if (tf->currec >= tf->fdr.numrecs) {
  1196.             return 1;     // EOF
  1197.         }
  1198.  
  1199.         newsec = tf->currec / tf->fdr.recspersec;
  1200.  
  1201.         // different sector?
  1202.         if (newsec != tf->cursec) {
  1203.             if (!fiad_tifile_seek_to_sector(tf, newsec)) {
  1204.                 return -1;
  1205.             }
  1206.             //  Get a new sector
  1207.             if (!fiad_tifile_read_sector(tf)) {
  1208.                 return -1;
  1209.             }
  1210.         }
  1211.  
  1212.         tf->curnrecs = tf->currec % tf->fdr.recspersec;
  1213.         tf->curoffs = tf->curnrecs * tf->fdr.reclen;
  1214.  
  1215. //        fiad_logger(_L|LOG_USER, "%p, %04X, %04X, %d\n", tf->pab, PABTOVDP(tf->pab), tf->pab.addr, tf->fdr.reclen);
  1216.  
  1217.         *reclen = tf->fdr.reclen;
  1218.         memcpy(data, tf->sector + tf->curoffs, *reclen);
  1219.  
  1220.         tf->currec++;
  1221.     }
  1222.     return 0;
  1223. }
  1224.  
  1225. /*    Write a record to the file (all but PROGRAM files)
  1226.  
  1227.     Return 0 for success, -1 for hardware failure, 1 for disk full 
  1228. */
  1229. int
  1230. fiad_tifile_write_record(fiad_tifile *tf, u8 *data, u8 reclen)
  1231. {
  1232.     if ((tf->fdr.flags & ff_variable) && tf->format != F_TEXT) {
  1233.  
  1234.         /* write variable data record */
  1235.         u8          len;
  1236.  
  1237.       retry_var_write:
  1238.         // write a new sector?
  1239.         if (!tf->curoffs) {
  1240.             // clip file to current position
  1241.             if (tf->cursec + 1 != tf->fdr.secsused) {
  1242.                 tf->fdr.secsused = tf->cursec + 1;
  1243.                 tf->fdr.numrecs = tf->cursec + 1;
  1244.                 if (!fiad_tifile_set_file_size(tf)) {
  1245.                     return -1;        // hardware failure
  1246.                 }
  1247.             }
  1248.         }
  1249.  
  1250.         len = reclen;
  1251.  
  1252.         // I think this is standard
  1253.         if (len > tf->fdr.reclen)
  1254.             len = tf->fdr.reclen;
  1255.  
  1256.         // need room for record, length, and 0xff eos byte
  1257.         if (len + 1 + tf->curoffs >= 255) {
  1258.             if (!fiad_tifile_seek_to_sector(tf, tf->cursec+1)) {
  1259.                 return tf->error == OS_MEMERR /*disk full*/ ? 1 : -1;
  1260.             }
  1261.             goto retry_var_write;
  1262.         }
  1263.  
  1264.         tf->sector[tf->curoffs] = len;
  1265.         memcpy(tf->sector + tf->curoffs + 1, data, len);
  1266.         tf->changed = true;
  1267.         tf->changedfdr = true;
  1268.  
  1269.         tf->curoffs += len + 1;    // update EOF ptr
  1270.  
  1271.         tf->sector[tf->curoffs] = 0xff;    // end of sector marker
  1272.  
  1273.         tf->fdr.byteoffs = tf->curoffs;    // update FDR
  1274.  
  1275.     } else if ((tf->fdr.flags & ff_variable) && tf->format == F_TEXT) {
  1276.  
  1277.         /* write variable data to text file */
  1278.         u8          len;
  1279.         int         strln, cpyln;
  1280.  
  1281.         len = reclen;
  1282.  
  1283.         // I think this is standard
  1284.         if (len > tf->fdr.reclen)
  1285.             len = tf->fdr.reclen;
  1286.  
  1287.         strln = len + 1;        // to copy
  1288.  
  1289.       retry_textvar_write:
  1290.         // write a new sector?
  1291.         if (!tf->curoffs) {
  1292.             // clip file to current position
  1293.             if (tf->cursec + 1 != tf->fdr.secsused) {
  1294.                 tf->fdr.secsused = tf->cursec + 1;
  1295.                 tf->fdr.numrecs = tf->cursec + 1;
  1296.                 if (!fiad_tifile_set_file_size(tf)) {
  1297.                     return -1;        // hardware failure
  1298.                 }
  1299.             }
  1300.         }
  1301.  
  1302.         // fill up sector to the end,
  1303.         // keep one byte for newline
  1304.         cpyln = tf->curoffs + strln < 256 ? strln : 255 - strln;
  1305.         memcpy(tf->sector + tf->curoffs, data + len - strln, cpyln);
  1306.         tf->changed = true;
  1307.         tf->changedfdr = true;
  1308.  
  1309.         strln -= cpyln;
  1310.         if (!strln)
  1311. #ifndef UNDER_MACOS
  1312.             tf->sector[tf->curoffs + cpyln - 1] = '\n';
  1313. #else
  1314.             tf->sector[tf->curoffs + cpyln - 1] = '\r';
  1315. #endif
  1316.  
  1317.         tf->curoffs += cpyln;
  1318.  
  1319.         if (!tf->curoffs) {
  1320.             if (!fiad_tifile_seek_to_sector(tf, tf->cursec+1)) {
  1321.                 return tf->error == OS_MEMERR /*disk full*/ ? 1 : -1;
  1322.             }
  1323.             goto retry_textvar_write;
  1324.         }
  1325.  
  1326.         tf->fdr.byteoffs = tf->curoffs;    // update FDR
  1327.  
  1328.     } else {
  1329.  
  1330.         /* write fixed data record */
  1331.         u16         newsec;
  1332.  
  1333.         fiad_logger(_L | L_3, "fixed write, at %d, cur is %d\n", tf->currec,
  1334.              tf->fdr.numrecs);
  1335.  
  1336.         newsec = tf->currec / tf->fdr.recspersec;
  1337.  
  1338.         // different sector?
  1339.         if (newsec != tf->cursec || tf->fdr.secsused == 0) {
  1340.             if (!fiad_tifile_seek_to_sector(tf, newsec)) {
  1341.                 return tf->error == OS_MEMERR /*disk full*/ ? 1 : -1;
  1342.             }
  1343.  
  1344.             //  Get the current sector, growing file if needed
  1345.             if (tf->fdr.secsused <= tf->cursec) {
  1346.                 // update sector count
  1347.                 tf->fdr.secsused = tf->cursec + 1;
  1348.                 tf->changedfdr = true;
  1349.             } else {
  1350.                 // read old sector
  1351.                 if (!fiad_tifile_read_sector(tf)) {
  1352.                     return -1;
  1353.                 }
  1354.             }
  1355.         }
  1356.  
  1357.         tf->curnrecs = tf->currec % tf->fdr.recspersec;
  1358.         tf->curoffs = tf->curnrecs * tf->fdr.reclen;
  1359.  
  1360.         memcpy(tf->sector + tf->curoffs, data, reclen);
  1361.         memset(tf->sector + tf->curoffs + reclen, 0, tf->fdr.reclen - reclen);
  1362.         tf->changed = true;
  1363.  
  1364.         tf->currec++;
  1365.  
  1366.         // update # records if needed
  1367.         if (tf->currec > tf->fdr.numrecs) {
  1368.             tf->fdr.numrecs = tf->currec;
  1369.             tf->fdr.byteoffs = 0;    // for fixed files
  1370.             tf->changedfdr = true;
  1371.         }
  1372.     }
  1373.     return 0;
  1374. }
  1375.  
  1376. /*    Seek to a given record.  For variable files, only 0 is accepted. 
  1377.  
  1378.     Return 0 for success, -1 for hardware failure, 1 for disk full
  1379. */
  1380. int
  1381. fiad_tifile_seek_to_record(fiad_tifile *tf, u16 recnum)
  1382. {
  1383.     /* Variable files always RESTORE to beginning of file */
  1384.     if (tf->fdr.flags & ff_variable) {
  1385.         if (!fiad_tifile_seek_to_sector(tf, 0)) {
  1386.             return tf->error == OS_MEMERR /*disk full*/ ? 1 : -1;
  1387.         }
  1388.         fiad_tifile_init_file_pointers(tf);
  1389.  
  1390.     } else {
  1391.         if (!fiad_tifile_flush(tf)) {
  1392.             return tf->error == OS_MEMERR /*disk full*/ ? 1 : -1;
  1393.         }
  1394.         tf->currec = recnum;
  1395.     }
  1396.     return 0;
  1397. }
  1398.  
  1399. /*    Read binary data from file at current position.
  1400.  
  1401.     Returns 0 for success, -1 for hardware failure, 1 for EOF.
  1402. */
  1403. int
  1404. fiad_tifile_read_binary_image(fiad_tifile *tf, u8 *data, u16 maxread, u16 *gotread)
  1405. {
  1406.     u16 toread;
  1407.  
  1408.     /* take minimum size */
  1409.     toread = maxread;
  1410.  
  1411.     fiad_logger(_L | L_2, "file has >%04X or %d sectors\n\n", tf->fdr.secsused,
  1412.            tf->fdr.secsused);
  1413.     fiad_logger(_L | L_2, "going to read at most %d bytes from file with size %d\n", toread,
  1414.              FDR_FILESIZE(&tf->fdr));
  1415.     
  1416.     /* bytes read */
  1417.     *gotread = 0;
  1418.  
  1419.     while (toread) {
  1420.         u16 copy = toread < 256 ? toread : 256;
  1421.  
  1422.         if (tf->cursec >= tf->fdr.secsused) {
  1423.             // end of file
  1424.             return 1;
  1425.         }
  1426.  
  1427.         if (!fiad_tifile_read_sector(tf)) {
  1428.             return -1;
  1429.         }
  1430.  
  1431.         memcpy(data, tf->sector, copy);
  1432.         data += copy;
  1433.         *gotread += copy;
  1434.         toread -= copy;
  1435.  
  1436.         if (!fiad_tifile_seek_to_sector(tf, tf->cursec+1)) {
  1437.             return -1;
  1438.         }
  1439.     }
  1440.     return 0;
  1441. }
  1442.  
  1443.  
  1444. /*    Write binary data to a file at the current position 
  1445.  
  1446.     Return 0 for success, -1 for hardware failure, 1 for disk full
  1447. */
  1448. int
  1449. fiad_tifile_write_binary_image(fiad_tifile *tf, u8 *data, u16 towrite, u16 *written)
  1450. {
  1451.     u16 secs = 0;
  1452.  
  1453.     fiad_logger(_L | L_2, "trying to write %d bytes to file\n\n", towrite);
  1454.  
  1455.     *written = 0;
  1456.     while (*written < towrite) {
  1457.         u16 copy = towrite - (*written) < 256 ? towrite - (*written) : 256;
  1458.  
  1459.         if (tf->cursec >= tf->fdr.secsused) {
  1460.             tf->fdr.secsused = tf->cursec + 1;
  1461.             tf->fdr.byteoffs = 0;
  1462.             tf->changedfdr = true;
  1463.         }
  1464.  
  1465.         memcpy(tf->sector, data, copy);
  1466.         tf->changed = true;
  1467.  
  1468.         if (!fiad_tifile_seek_to_sector(tf, tf->cursec+1))
  1469.             return tf->error == OS_MEMERR /*disk full*/ ? 1 : -1;
  1470.  
  1471.         data += copy;
  1472.         *written += copy;
  1473.     }
  1474.  
  1475.     // last sector may be partial
  1476.     tf->fdr.byteoffs = *written & 0xff;
  1477.     return 0;
  1478. }
  1479.  
  1480. /*    Get info about a file in tf,
  1481.     return 1 for success
  1482. */
  1483. int
  1484. fiad_tifile_get_info(fiad_tifile *tf)
  1485. {
  1486.     OSError err;
  1487.     int ret;
  1488.  
  1489.     err = fiad_tifile_open_file(tf, 
  1490.                                 F_UNKNOWN,
  1491.                                 false /*create*/, 
  1492.                                 false /*always*/, 
  1493.                                 true /*readonly*/);
  1494.     if ((tf->error = err) != OS_NOERR)
  1495.         return 0;
  1496.     
  1497.     // don't return right away in case we're
  1498.     // repairing the FDR
  1499.     ret = fiad_tifile_verify(tf, false /*fixup*/);
  1500.  
  1501.     fiad_tifile_close_file(tf);
  1502.     return ret;
  1503. }
  1504.  
  1505. /*    Free a catalog */
  1506. void
  1507. fiad_catalog_free_catalog(fiad_catalog *cat)
  1508. {
  1509.     int x;
  1510.  
  1511.     if (!cat->filenames) {
  1512.         return;
  1513.     }
  1514.  
  1515.     for (x = 0; x < cat->entries; x++) {
  1516.         xfree(cat->filenames[x]);
  1517.     }
  1518.     xfree(cat->filenames);
  1519.  
  1520.     cat->filenames = 0L;
  1521. }
  1522.  
  1523. static int sort_filename(const void *a, const void *b)
  1524. {
  1525.     return strcasecmp(*(const char **)a, *(const char **)b);
  1526. }
  1527.  
  1528. /*    Read a catalog from a directory */
  1529. OSError
  1530. fiad_catalog_read_catalog(fiad_catalog *cat, const char *wildcard)
  1531. {
  1532.     char wildpath[OS_PATHSIZE];
  1533.     OSSpec spec, *sptr;
  1534.     OSError err;
  1535.     OSSize    blocksize, totalblocks, freeblocks;
  1536.     bool    iswild;
  1537.     int         x = 0;
  1538.     fiad_tifile    tf;
  1539.  
  1540.  
  1541.     fiad_catalog_free_catalog(cat);
  1542.  
  1543.     /* Get disk info */
  1544.  
  1545.     err = OS_MakeSpec(wildcard, &spec, &iswild);
  1546.     if (err != OS_NOERR)
  1547.         return err;
  1548.  
  1549.     cat->path = spec.path;
  1550.  
  1551.     if ((err = OS_GetDiskStats(&spec.path, &blocksize, 
  1552.                                &totalblocks, &freeblocks)) != OS_NOERR) {
  1553.         blocksize = 256;
  1554.         totalblocks = 360;
  1555.         freeblocks = 0;
  1556.     }
  1557.     cat->total_sectors = totalblocks / 256 * blocksize;
  1558.     cat->free_sectors = freeblocks / 256 * freeblocks;
  1559.  
  1560.     /* Get file list */
  1561.  
  1562.     cat->entries_max = cat->entries = 0;
  1563.     cat->filenames = 0;
  1564.  
  1565.     /* If 'iswild' is true, use directory matching helper */
  1566.     if (!iswild) {
  1567.         OS_PathSpecToString2(&spec.path, wildpath);
  1568.         strcat(wildpath, "*");
  1569.     } else {
  1570.         strcpy(wildpath, wildcard);
  1571.     }
  1572.  
  1573.     /* Any matches? */
  1574.  
  1575.     if ((sptr = OS_MatchPath(wildpath)) == NULL) {
  1576.         return OS_FNFERR;
  1577.     }
  1578.  
  1579.     fiad_logger(_L | L_2, "reading dir\n");
  1580.  
  1581.     do {
  1582.         /* validate file and get info */
  1583.         if (!OS_IsFile(sptr) ||
  1584.             fiad_tifile_setup_spec_with_spec(&tf, sptr) != OS_NOERR ||
  1585.             !fiad_tifile_get_info(&tf) ||
  1586.             tf.format == F_UNKNOWN)
  1587.         {
  1588.             fiad_logger(_L|L_1, "Skipping '%s' which doesn't appear to be a V9t9 file\n", 
  1589.                         OS_NameSpecToString1(&sptr->name));
  1590.             continue;
  1591.         }
  1592.  
  1593.         /* more memory needed? */
  1594.         if (x >= cat->entries_max) {
  1595.             char **names;
  1596.             fdrrec *fdrs;
  1597.             int *index;
  1598.                 
  1599.             names = (char **) xrealloc(cat->filenames,
  1600.                                        (cat->entries_max + 128) * sizeof(char *));
  1601.             fdrs = (fdrrec *) xrealloc(cat->fdrs,
  1602.                                        (cat->entries_max + 128) * sizeof(fdrrec));
  1603.             index = (int *) xrealloc(cat->index,
  1604.                                      (cat->entries_max + 128) * sizeof(int));
  1605.             cat->entries_max += 128;
  1606.             cat->filenames = names;
  1607.             cat->fdrs = fdrs;
  1608.             cat->index = index;
  1609.         }
  1610.  
  1611.         cat->filenames[x] = xstrdup(OS_NameSpecToString1(&sptr->name));
  1612.         fiad_logger(_L | L_2, "got file '%s'\n\n", cat->filenames[x]);
  1613.         cat->fdrs[x] = tf.fdr;
  1614.         cat->index[x] = x;
  1615.         x++;
  1616.         cat->entries++;
  1617.     } while ((sptr = OS_MatchPath(0L)) != 0L);
  1618.  
  1619.     return OS_NOERR;
  1620. }
  1621.  
  1622. /*    Sort a catalog */
  1623.  
  1624. static fiad_catalog *_sort_catalog;
  1625. static bool _sort_order;
  1626.  
  1627. static int _sort_disk(const void *a, const void *b)
  1628. {
  1629.     // disk order
  1630.     return _sort_order ? ((char *)a - (char *)b) :
  1631.         ((char *)b - (char *)a);
  1632. }
  1633.  
  1634. static int _sort_filename(const void *a, const void *b)
  1635. {
  1636.     const char *a1 = _sort_catalog->filenames[*(const int *)a];
  1637.     const char *b1 = _sort_catalog->filenames[*(const int *)b];
  1638.  
  1639.     return _sort_order ? strcasecmp(a1, b1) : strcasecmp(b1, a1);
  1640. }
  1641.  
  1642. static int _sort_type(const void *a, const void *b)
  1643. {
  1644.     const fdrrec *a1 = &_sort_catalog->fdrs[*(const int *)a];
  1645.     const fdrrec *b1 = &_sort_catalog->fdrs[*(const int *)b];
  1646.     int diff;
  1647.  
  1648.     diff = (a1->flags & (ff_internal | ff_program | ff_variable)) -
  1649.            (b1->flags & (ff_internal | ff_program | ff_variable));
  1650.  
  1651.     if (!_sort_order) diff = -diff;
  1652.  
  1653.     if (!diff)
  1654.     {
  1655.         diff = _sort_order ? a1->reclen - b1->reclen :
  1656.             b1->reclen - a1->reclen;
  1657.     }
  1658.     return diff;
  1659. }
  1660.  
  1661. static int _sort_size(const void *a, const void *b)
  1662. {
  1663.     const fdrrec *a1 = &_sort_catalog->fdrs[*(const int *)a];
  1664.     const fdrrec *b1 = &_sort_catalog->fdrs[*(const int *)b];
  1665.  
  1666.     return _sort_order ? (FDR_FILESIZE(a1) - FDR_FILESIZE(b1)) :
  1667.         (FDR_FILESIZE(b1) - FDR_FILESIZE(a1));
  1668. }
  1669.  
  1670. void
  1671. fiad_catalog_sort_catalog(fiad_catalog *cat, int sort_by, bool ascending)
  1672. {
  1673.     int (*func)(const void *a, const void *b);
  1674.  
  1675.     switch (sort_by)
  1676.     {
  1677.     case FIAD_CATALOG_SORT_BY_DISK:    func = _sort_disk; break;
  1678.     case FIAD_CATALOG_SORT_BY_NAME:    func = _sort_filename; break;
  1679.     case FIAD_CATALOG_SORT_BY_TYPE:    func = _sort_type; break;
  1680.     case FIAD_CATALOG_SORT_BY_SIZE:    func = _sort_size; break;
  1681.     default:    fiad_logger(_L|LOG_FATAL, "Invalid catalog sort '%d'\n", sort_by); 
  1682.     }
  1683.  
  1684.     _sort_catalog = cat;
  1685.     _sort_order = ascending;
  1686.     qsort(cat->index, cat->entries, sizeof(int), func);
  1687.     _sort_catalog = NULL;
  1688. }
  1689.  
  1690. /*    Get file information from catalog */
  1691. int
  1692. fiad_catalog_get_file_info(fiad_catalog *cat, int index, fdrrec *fdr)
  1693. {
  1694.     if (index < 0 || index >= cat->entries)
  1695.         return 0;
  1696.  
  1697.     *fdr = cat->fdrs[cat->index[index]];
  1698.     return 1;
  1699. }
  1700.  
  1701. /*
  1702.  *    Return string giving standard name for type of file, i.e., "DIS/VAR 80"
  1703.  */
  1704.  
  1705. static struct {
  1706.     u8 fdr_flags;
  1707.     const char *name;
  1708. }    fiad_tifile_file_types[5] =
  1709. {
  1710.     {0,                         "DIS/FIX"},
  1711.     {ff_program,                "PROGRAM"},
  1712.     {ff_internal,                "INT/FIX"},
  1713.     {ff_variable,                "DIS/VAR"},
  1714.     {ff_variable+ff_internal,    "INT/VAR"}
  1715. };
  1716.  
  1717. const char *
  1718. fiad_catalog_get_file_type_string(fdrrec *fdr)
  1719. {
  1720.     int i;
  1721.     static char type_string[12];
  1722.  
  1723.     for (i = 0; i < 5; i++) {
  1724.         if ((fdr->flags & (ff_program | ff_internal | ff_variable)) ==
  1725.             fiad_tifile_file_types[i].fdr_flags) 
  1726.         {
  1727.             // program files don't have record length
  1728.             if (fdr->flags & ff_program) {
  1729.                 strcpy(type_string, 
  1730.                        fiad_tifile_file_types[i].name);
  1731.             } else {
  1732.                 sprintf(type_string, "%s %d", 
  1733.                         fiad_tifile_file_types[i].name,
  1734.                         fdr->reclen);
  1735.             }
  1736.             return type_string;
  1737.         }
  1738.     }
  1739.     return "???";
  1740. }
  1741.