home *** CD-ROM | disk | FTP | other *** search
/ PC Online 1999 February / PCO_0299.ISO / filesbbs / dos / indigo01.exe / INDIGO.CPP < prev    next >
Encoding:
C/C++ Source or Header  |  1997-08-26  |  20.3 KB  |  582 lines

  1. // This program is free software; you can redistribute it and/or modify it
  2. // under the terms of the GNU General Public License as published by the Free
  3. // Software Foundation; either version 2 of the License, or (at your option)
  4. // any later version.
  5.  
  6. // You should have received a copy of the GNU General Public License along
  7. // with this program; if not, write to the Free Software Foundation, Inc., 675
  8. // Mass Ave, Cambridge, MA 02139, USA.
  9.  
  10. // indigo.cpp
  11. // Main routines for Indigo.
  12.  
  13. #include <stdio.h>
  14. #include <string.h>
  15. #include <dos.h>
  16. #include <time.h>
  17. #include <stdlib.h>
  18. #include "areas.h"
  19. #include "pkthead.h"
  20. #include "bluewave.h"
  21. #include "debug.h"
  22. #include "config.h"
  23. #include "version.h"
  24.  
  25. #ifdef __TURBOC__
  26. #include <dir.h>
  27. #endif
  28. #ifdef __EMX__
  29. #include <process.h>
  30. #include <unistd.h>
  31. #include <emx/syscalls.h>
  32. #endif
  33.  
  34. #ifdef __LINUX__
  35. #define stricmp strcasecmp
  36. #endif
  37.  
  38. // Since the file finding routines are different between the compilers, we
  39. // need some macros to get it working.
  40.  
  41. #ifdef __TURBOC__
  42. #define FINDTYPE struct ffblk
  43. #define FINDFIRST(p,t,a) findfirst(p,t,a)
  44. #define FINDNEXT(t) findnext(t)
  45. #define ARCH_OR_READONLY (FA_ARCH | FA_RDONLY)
  46. #define ALL_FILES (FA_RDONLY | FA_HIDDEN | FA_SYSTEM | FA_DIREC | FA_ARCH)
  47. #define GETFILENAME(t) (t.ff_name)
  48. #endif
  49. #ifdef __EMX__
  50. #define FINDTYPE struct _find
  51. #define FINDFIRST(p,t,a) __findfirst(p,a,t)
  52. #define FINDNEXT(t) __findnext(t)
  53. #define ARCH_OR_READONLY (_A_ARCH | _A_RDONLY)
  54. #define ALL_FILES (_A_RDONLY | _A_HIDDEN | _A_SYSTEM | _A_SUBDIR | _A_ARCH)
  55. #define GETFILENAME(t) (t.name)
  56. #endif
  57.  
  58. // Prototypes. Never leave home without them.
  59.  
  60. int pkt2bw(char **, char *, int);
  61. int bw2pkt(char *, char *);
  62. void read2null(FILE *, char *);
  63. int nextFree(char *);
  64.  
  65. // main
  66. // Checks the command line parameters, and calls the appropriate functions
  67. int main(int argc, char *argv[])
  68. {
  69.     int rc = 0;
  70.  
  71.     // Show a banner
  72.     puts(PROGNAME " " VERSTRING);
  73.  
  74. #ifdef __EMX__
  75.     // If we are running the 32-bit protected mode version under DOS,
  76.     // instead run the 16-bit real mode DOS version. This is just because I
  77.     // never bothered to DOS "fix" the protected mode version of the
  78.     // IDSERVER routines.
  79.  
  80.     if (OS2_MODE != _osmode) {
  81.         spawnv(P_OVERLAY, "indigo.exe", argv);
  82.         return 0;
  83.     }
  84. #endif
  85.  
  86.     // Copyright info.
  87.  
  88.     puts("(c) Copyright 1997 Peter Karlsson");
  89.     puts("This program is released under the GNU public license"
  90. #ifdef __MSDOS__
  91.          " except for");
  92.     puts("the SPAWNO routines by Ralf Brown, used to minimize memory while");
  93.     puts("shelling to DOS and running other programs.");
  94. #else
  95.         );
  96. #endif
  97.  
  98.     puts("You should have received a copy of the GNU General Public License along");
  99.     puts("with this program; if not, write to the Free Software Foundation, Inc., 675");
  100.     puts("Mass Ave, Cambridge, MA 02139, USA.");
  101.  
  102.     // Initialize.
  103.  
  104.     tzset();
  105.  
  106.     // Check the command line parameters.
  107.  
  108.     if (argc < 3) { // Less than three are too few.
  109.         // Extract the program name from the path in argv[0].
  110.         char *ch_p = strrchr(argv[0], '\\');
  111.         ch_p = ch_p ? ch_p + 1 : argv[0];
  112.  
  113.         fprintf(stderr, "Usage: %s [-i[drive:][path]configfile] IN pktfile[s]\n"
  114.                         "       %s [-i[drive:][path]configfile] OUT newfile\n", ch_p, ch_p);
  115.         rc = 0;
  116.     }
  117.     else { // Enough parameters
  118.         unsigned paramoffset = 0;
  119.  
  120.         // Do we have a configuration file parameter?
  121.  
  122.         char *configfile;
  123.         if (!strncmp("-i", argv[1], 2)) {
  124.             configfile = strdup(&argv[1][2]);
  125.             paramoffset = 1;
  126.         }
  127.         else
  128.             configfile = "indigo.cfg";
  129.  
  130.         // Check direction (IN/OUT).
  131.  
  132.         if (!stricmp("IN", argv[1 + paramoffset]))
  133.             rc = pkt2bw(&argv[2 + paramoffset], configfile,
  134.                         argc - 2 - paramoffset);
  135.         else if (!stricmp("OUT", argv[1 + paramoffset])) {
  136.             if (3 + paramoffset == argc)
  137.                 rc = bw2pkt(argv[2 + paramoffset], configfile);
  138.             else {
  139.                 fprintf(stderr, "Illegal number of parameters to OUT: %d",
  140.                         argc - 2);
  141.                 rc = 1;
  142.             }
  143.         }
  144.         else {
  145.             fprintf(stderr, "Illegal direction parameter: %s\n", argv[1]);
  146.             rc = 1;
  147.         }
  148.         if (paramoffset)
  149.             delete configfile;
  150.     }
  151.  
  152.     return rc;
  153. }
  154.  
  155. // pkt2bw
  156. // This routine converts a Fidonet PKT file into a Blue Wave packet.
  157. int pkt2bw(char **pktnames, char *paramfile, int numpars)
  158. {
  159.     doDEBUG(printf("pkt2bw((char *[]) %p -> \"%s\"..., \"%s\", %d)\n",
  160.             (void *) pktnames, pktnames[0], paramfile, numpars));
  161.  
  162.     // Read the configuration file
  163.     Configuration *configdata_p = new Configuration(paramfile, in);
  164.     if (0 == configdata_p->numuplinks || 0 == configdata_p->numareas)
  165.         return 1;
  166.  
  167.     // Read all the PKT files, one at a time
  168.     // -*-Todo: process these in chronological order.-*-
  169.     unsigned i = 0;
  170.     while (numpars --) {
  171.         doDEBUG(printf("Searching PKT files for: %s\n", pktnames[i]));
  172.  
  173.         // Extract the path to the PKT file spec.
  174.         // findfirst/findnext only returns the actual name, so we need to keep
  175.         // it.
  176.  
  177.         char *searchpath = strdup(pktnames[i]);
  178.         char *ch_p;
  179.         if (NULL != (ch_p = strrchr(searchpath, '\\')))
  180.             *(ch_p + 1) = 0; // path up to the backslash
  181.         else
  182.             *searchpath = 0; // no path was given
  183.  
  184.         // Now look for all PKT files that are matching the file specification
  185.  
  186.         FINDTYPE ffblk;
  187.         int done = FINDFIRST(pktnames[i], &ffblk, ARCH_OR_READONLY);
  188.         while (!done) {
  189.             char *pkt = new char[strlen(searchpath) + strlen(GETFILENAME(ffblk)) + 1];
  190.             strcpy(pkt, searchpath);
  191.             strcat(pkt, GETFILENAME(ffblk));
  192.             doDEBUG(printf("Found PKT file: %s\n", pkt));
  193.  
  194.             // Open the PKT file
  195.             FILE *pktf;
  196.             if (NULL == (pktf = fopen(pkt, "rb")))
  197.                 return 1; // Perhaps aborting here is a Bad Idea?
  198.             fseek(pktf, 0, SEEK_END);
  199.             long pktsize = ftell(pktf);
  200.             fseek(pktf, 0, SEEK_SET);
  201.  
  202.             // Read the PKT file, and add the messages to the Blue Wave packet
  203.             pktheader_t pktheader;
  204.  
  205.             // Get the PKT header.
  206.             fread(&pktheader, sizeof(pktheader_t), 1, pktf);
  207.  
  208.             // Figure out who this is from.
  209.             int thisuplinknumber =
  210.                 configdata_p->uplinkNumber(FidoAddress(pktheader.orgzone,
  211.                                                        pktheader.orgnet,
  212.                                                        pktheader.orgnode,
  213.                                                        pktheader.orgpoint));
  214.  
  215.             // If we didn't find a matching uplink, report it, and proceed as
  216.             // if the uplink matched number one.
  217.             if (-1 == thisuplinknumber) {
  218.                 fprintf(stderr, "* Undefined uplink: %u:%u/%u.%u\n",
  219.                         pktheader.orgzone, pktheader.orgnet, pktheader.orgnode,
  220.                         pktheader.orgpoint);
  221.                 thisuplinknumber = 0;
  222.             }
  223.  
  224.             // Now read each message.
  225.             pktmsg_t msgheader;
  226.             do {
  227.                 ifDEBUG(printf("%s [%ld/%ld byte]\n", pkt, ftell(pktf), pktsize),
  228.                         printf("%s (%06.2f%%)\r", pkt, (100.0 * ftell(pktf)) / pktsize));
  229.                 // Fetch the message header.
  230.                 msgheader.pktver = 0;
  231.                 fread(&msgheader, sizeof(pktmsg_t), 1, pktf);
  232.                 // Run if we get indication of version 2 (=message)
  233.                 if (2 == msgheader.pktver) {
  234.                     BlueWaveArea *thisarea_p;
  235.  
  236.                     // Fetch recipient, sender, and subject
  237.                     char mFrom[36], mTo[36], mSubj[72];
  238.                     read2null(pktf, mTo);
  239.                     read2null(pktf, mFrom);
  240.                     read2null(pktf, mSubj);
  241.  
  242.                     // Fetch AREA kludge, if any
  243.                     long textpos = ftell(pktf);
  244.                     char area[256];
  245.                     area[255] = 0;
  246.                     for (unsigned j = 0; j < 255; j ++) {
  247.                         area[j] = fgetc(pktf);
  248.                         if (13 == area[j]) {
  249.                             area[j] = 0;
  250.                             break;
  251.                         }
  252.                     }
  253.                     if (area == strstr(area, "AREA:")) { // AREA found
  254.                         // Match the area tag
  255.                         thisarea_p = NULL;
  256.                         for (unsigned j = 0; j < configdata_p->numareas &&
  257.                              NULL == thisarea_p; j ++) {
  258.                             if (configdata_p->arealist_pp[j]->isThisYou(&area[5]))
  259.                                 thisarea_p = configdata_p->arealist_pp[j];
  260.                         }
  261.                         // We didn't find a matching area.
  262.                         if (!thisarea_p) {
  263.                             fprintf(stderr, "* Area setup missing: %s\n",
  264.                                     &area[5]);
  265.                             // Put it in the "bad messages" area.
  266.                             thisarea_p = configdata_p->arealist_pp[configdata_p->badareano];
  267.                             // Reset file pointer.
  268.                             fseek(pktf, textpos, SEEK_SET);
  269.                         }
  270.                     }
  271.                     else { // This is a netmail area, reset the file pointer.
  272.                         fseek(pktf, textpos, SEEK_SET);
  273.                         thisarea_p = configdata_p->arealist_pp[thisuplinknumber];
  274.                     }
  275.  
  276.                     // Save the message, if we found an area to put it in.
  277.                     if (thisarea_p) {
  278.                         FidoAddress thisaddr(0, msgheader.orgnet, msgheader.orgnode, 0);
  279.                         thisarea_p->addMessage(mFrom, mTo, mSubj,
  280.                                                (char *) &msgheader.datetime[0],
  281.                                                thisaddr, pktf,
  282.                                                0 == stricmp(mTo, configdata_p->myName_p));
  283.                     }
  284.                     else { // We should never get here.
  285.                         doDEBUG(puts("Skipping msg (no matching area)"));
  286.                         int ch;
  287.                         while (0 != (ch = fgetc(pktf)) && EOF != ch)
  288.                             ; // null
  289.                     }
  290.                 }
  291.             } while (2 == msgheader.pktver);
  292.             ifDEBUG(,printf("%s (done)   \n", pkt));
  293.  
  294.             // Close the PKT file.
  295.             fclose(pktf);
  296.  
  297.             // And remove it.
  298.             remove(pkt);
  299.  
  300.             done = FINDNEXT(&ffblk);
  301.         } // while
  302.  
  303.         // -*-Todo: Under OS/2, we ought to close the findfirst handle,
  304.         // but I couldn't find any findclose function in the EMX runtime
  305.         // library.-*-
  306.         // FINDCLOSE(&ffblk);
  307.  
  308.         i ++;
  309.         delete searchpath;
  310.     } // while
  311.  
  312.     // Create the Blue Wave files.
  313.     FILE *inf, *mix, *fti, *dat;
  314.     char nametemplate[256];
  315.     strcpy(nametemplate, configdata_p->tempName(configdata_p->base_p));
  316.     strcat(nametemplate, ".%s");
  317.  
  318.     char filename[256];
  319.  
  320.     // .inf file (packet and area information)
  321.     sprintf(filename, nametemplate, "inf");
  322.     inf = fopen(filename, "wb");
  323.  
  324.     // .mix file (message area index)
  325.     sprintf(filename, nametemplate, "mix");
  326.     mix = fopen(filename, "wb");
  327.  
  328.     // .fti file (message index)
  329.     sprintf(filename, nametemplate, "fti");
  330.     fti = fopen(filename, "wb");
  331.  
  332.     // .dat file (message texts)
  333.     sprintf(filename, nametemplate, "dat");
  334.     dat = fopen(filename, "wb");
  335.  
  336.     // Check if we could open the files.
  337.     if (NULL == inf || NULL == mix || NULL == fti || NULL == dat) {
  338.         fputs("* Unable to create output file(s)", stderr);
  339.         delete configdata_p;
  340.     }
  341.  
  342.     // Create the header of the .inf file
  343.     INF_HEADER infheader;
  344.     memset(&infheader, 0, sizeof(INF_HEADER));
  345.  
  346.     infheader.ver = PACKET_LEVEL;
  347.     strcpy((char *) &infheader.loginname[0], configdata_p->myName_p);
  348.     strcpy((char *) &infheader.aliasname[0], configdata_p->myName_p);
  349.     infheader.zone  = configdata_p->myAddress_pp[0]->zone;
  350.     infheader.net   = configdata_p->myAddress_pp[0]->net;
  351.     infheader.node  = configdata_p->myAddress_pp[0]->node;
  352.     infheader.point = configdata_p->myAddress_pp[0]->point;
  353.     strcpy((char *) &infheader.sysop[0], configdata_p->myName_p);
  354.     infheader.ctrl_flags = INF_NO_CONFIG | INF_NO_FREQ;
  355.     sprintf((char *) &infheader.systemname[0], "%u:%u/%u.%u ("
  356.             PROGNAME " " VERSTRING ")",
  357.             configdata_p->myAddress_pp[0]->zone,
  358.             configdata_p->myAddress_pp[0]->net,
  359.             configdata_p->myAddress_pp[0]->node,
  360.             configdata_p->myAddress_pp[0]->point);
  361.     infheader.uflags = INF_EXT_INFO;
  362.     // -*-Todo: We ought to allow more netmail flags, but since there
  363.     // is no handler for these flags written, I thought it was a
  364.     // Good Idea to not do that, yet-*-
  365. //  infheader.netmail_flags = INF_CAN_CRASH | INF_CAN_ATTACH |
  366. //                            INF_CAN_KSENT | INF_CAN_HOLD   |
  367. //                            INF_CAN_IMM   | INF_CAN_FREQ   |
  368. //                            INF_CAN_DIRECT;
  369.     infheader.can_forward = 1; // I find this a stupid restriction.
  370.     infheader.inf_header_len = sizeof(INF_HEADER);
  371.     infheader.inf_areainfo_len = sizeof(INF_AREA_INFO);
  372.     infheader.mix_structlen = sizeof(MIX_REC);
  373.     infheader.fti_structlen = sizeof(FTI_REC);
  374.     infheader.uses_upl_file = 1;
  375.     infheader.from_to_len = 35;
  376.     infheader.subject_len = 71;
  377.     strcpy((char *) &infheader.packet_id[0], configdata_p->base_p);
  378.  
  379.     // Write the header
  380.     fwrite(&infheader, sizeof(infheader), 1, inf);
  381.  
  382.     // Let each area write their parts of the files
  383.     for (unsigned j = 0; j < configdata_p->numareas; j ++) {
  384.         configdata_p->arealist_pp[j]->addToFiles(inf, mix, fti, dat);
  385.     }
  386.  
  387.     // Close them
  388.     fclose(inf);
  389.     fclose(mix);
  390.     fclose(fti);
  391.     fclose(dat);
  392.  
  393.     // Now we need to create the BW archive.
  394.  
  395.     // Get a name for the packet.
  396.     char *bwname = configdata_p->bwName(nextFree(configdata_p->bwName("*")));
  397.  
  398.     // These files are to be included.
  399.     sprintf(filename, nametemplate, "*");
  400.  
  401.     // Set up the command line.
  402.     char *cmdline = configdata_p->compressionCommand(bwname, filename);
  403.     if (NULL == cmdline) {
  404.         fputs("* No decompression command defined", stderr);
  405.         return 1;
  406.     }
  407.  
  408.     doDEBUG(printf("Compress commandline is \"%s\"\n", cmdline));
  409.  
  410.     // Release memory from the configuration.
  411.     delete configdata_p;
  412.  
  413.     // Call the archiver.
  414.     int rc = system(cmdline);
  415.     doDEBUG(printf("Exitcode => %d\n", rc));
  416.     if (rc) {
  417.         fprintf(stderr, "* Compression command %s returned exit code %d.\n",
  418.                 cmdline, rc);
  419.         return 1;
  420.     }
  421.  
  422.     // Remove the files that have now been archived.
  423.     sprintf(filename, nametemplate, "inf");
  424.     remove(filename);
  425.     sprintf(filename, nametemplate, "mix");
  426.     remove(filename);
  427.     sprintf(filename, nametemplate, "fti");
  428.     remove(filename);
  429.     sprintf(filename, nametemplate, "dat");
  430.     remove(filename);
  431.  
  432.     // All's well, that ends well.
  433.     return 0;
  434. }
  435.  
  436. // read2null
  437. // Reads a null terminated string from a file.
  438. void read2null(FILE *input, char *dest_p)
  439. {
  440.     doDEBUG(printf("read2null((FILE *) %p, (char *) %p) => ", (void *) input,
  441.             (void *) dest));
  442.  
  443.     int ch;
  444.     while (0 != (ch = fgetc(input)) && EOF != ch) {
  445.         *dest_p = ch;
  446.         dest_p ++;
  447.         doDEBUG(fputc(ch, stdout));
  448.     }
  449.     *dest_p = 0; // nullterminate, just to be sure.
  450.     doDEBUG(puts(""));
  451. }
  452.  
  453. // bw2pkt
  454. // This routine converts a .new archive to an outgoing PKT file.
  455. int bw2pkt(char *newName, char *paramfile)
  456. {
  457.     doDEBUG(printf("bw2pkt(\"%s\", \"%s\")\n", newName, paramfile));
  458.  
  459.     // Read the configuration file.
  460.     Configuration configdata(paramfile, out);
  461.  
  462.     // We have to decompress the .new packet first
  463.  
  464.     // Get the decompression command line.
  465.     char *cmdline = configdata.deCompressCommand(newName, configdata.tempName(NULL));
  466.     if (NULL == cmdline) {
  467.         fputs("* No decompression command defined", stderr);
  468.         return 1;
  469.     }
  470.  
  471.     doDEBUG(printf("Decompressing with \"%s\"\n", cmdline));
  472.  
  473.     // And do it.
  474.     int rc = system(cmdline);
  475.     doDEBUG(printf("Exitcode => %d\n", rc));
  476.     if (rc) {
  477.         fprintf(stderr, "* Decompression command %s returned exit code %d.\n",
  478.                 cmdline, rc);
  479.         return 1;
  480.     }
  481.  
  482.     // The .upl file is now decompressed into our temporary directory.
  483.     // Now we need to find out where that is.
  484.     char *uplNameTmp = configdata.tempName(configdata.base_p);
  485.     char *uplName = new char[strlen(uplNameTmp) + 5];
  486.     strcpy(uplName, uplNameTmp);
  487.     strcat(uplName, ".upl");
  488.  
  489.     doDEBUG(printf("UPL file is %s\n", uplName));
  490.  
  491.     // Open the .upl file and process all the messages that are listed
  492.     // in it.
  493.     
  494.     FILE *uplFile;
  495.     if (NULL != (uplFile = fopen(uplName, "rb"))) { // We could open it
  496.         UPL_REC upl_rec;
  497.         UPL_HEADER upl_header;
  498.         // Fetch the .upl file header.
  499.         fread(&upl_header, sizeof(upl_header), 1, uplFile);
  500.         if (0 != upl_header.upl_header_len)
  501.             fseek(uplFile, upl_header.upl_header_len, SEEK_SET); // adjust
  502.         // Find out if there is a size difference between the records in
  503.         // the .upl files, and the record we know of (a future revision).
  504.         long recLenDiff = upl_header.upl_rec_len - sizeof(upl_rec);
  505.         if (0 == upl_header.upl_rec_len)
  506.             recLenDiff = 0;
  507.  
  508.         // Put something on the screen.
  509.         printf("Mail packet created by %s",
  510.                (char *) upl_header.reader_name);
  511.  
  512.         // Read all the .upl record.
  513.         while (1 == fread(&upl_rec, sizeof(upl_rec), 1, uplFile)) {
  514.             if (recLenDiff)
  515.                 fseek(uplFile, recLenDiff, SEEK_CUR); // adjust
  516.  
  517.             // Match the .upl echotag to, so that we know where to send it
  518.             // and what address to use.
  519.             Uplink *thisuplink_p = NULL;
  520.             for (unsigned i = 0; i < configdata.numuplinks &&
  521.                  NULL == thisuplink_p; i ++) {
  522.                 if (configdata.uplinklist_pp[i]->isThisYours((char *) &upl_rec.echotag[0]))
  523.                     thisuplink_p = configdata.uplinklist_pp[i];
  524.             }
  525.             if (NULL == thisuplink_p)
  526.                 thisuplink_p = configdata.uplinklist_pp[0]; // Undefined area.
  527.  
  528.             // Put the message into the PKT file.
  529.             thisuplink_p->addMessage(upl_header, upl_rec,
  530.                                      configdata.tempName(NULL));
  531.         }
  532.  
  533.         // Close the .upl file
  534.         fclose(uplFile);
  535.  
  536.         // and remove it.
  537.         remove(uplName);
  538.         
  539.         delete uplName;
  540.     }
  541.     else {
  542.         fprintf(stderr, "* Cannot read %s\n", uplName);
  543.     }
  544.  
  545.     return 0;
  546. }
  547.  
  548. // nextFree
  549. // Finds the next free extension number of a given path declaration.
  550. int nextFree(char *filemask)
  551. {
  552.     doDEBUG(printf("nextFree(\"%s\")", filemask));
  553.  
  554.     // Initialize the "highest found" counter to zero.
  555.     int highest = 0;
  556.  
  557.     // Locate all files matching the filespec.
  558.     FINDTYPE ffblk;
  559.     int rc = FINDFIRST(filemask, &ffblk, ALL_FILES);
  560.     while (0 == rc) {
  561.         doDEBUG(printf("match: \"%s\" => ", GETFILENAME(ffblk)));
  562.         
  563.         // Find the file extension.
  564.         char *c_p = strrchr(GETFILENAME(ffblk), '.');
  565.         if (c_p) {
  566.             // Convert it to a number, and check if it's the highest yet.
  567.             int thisnum = atoi(c_p + 1);
  568.             doDEBUG(printf("%d => ", thisnum));
  569.             highest = thisnum > highest ? thisnum : highest;
  570.             doDEBUG(printf("highest=%d\n", highest));
  571.         }
  572.         rc = FINDNEXT(&ffblk);
  573.     }
  574.  
  575.     // -*-Todo: Under OS/2, we ought to close the findfirst handle,
  576.     // but I couldn't find any findclose function in the EMX runtime
  577.     // library.-*-
  578.     // FINDCLOSE(&ffblk);
  579.  
  580.     return highest + 1;
  581. }
  582.