home *** CD-ROM | disk | FTP | other *** search
/ Gold Fish 1 / GoldFishApril1994_CD1.img / pitools / pitool.c < prev    next >
C/C++ Source or Header  |  1994-04-16  |  21KB  |  861 lines

  1. /*
  2.  *    Process Product-Info files.
  3.  *    Written by Fred Fish.
  4.  *    All portions not covered by GNU GPL are public domain.
  5.  *
  6.  *    usage:  pitool [options] file1 file2 file3 ...
  7.  *    Options:
  8.  *
  9.  *    -b    Find Product-Info files and append a suitable entry to
  10.  *        the files.bbs file in the directory in which it is
  11.  *        found.
  12.  *
  13.  *    -F <s>    Use <s> as a format specifier for writing entries
  14.  *        with -b option.  EX: "%-30.30B %8.8V %4.4KK %-40.40S\n"
  15.  *        Format specs are:
  16.  *
  17.  *        %%    Literal '%' character.
  18.  *        %B    Basename of "stored-in" path.
  19.  *        %D    Dirname of "stored-in" path.
  20.  *        %K    Size of the "stored-in" file/directory, in Kb.
  21.  *        %P    Full "stored-in" path.
  22.  *        %S    Contents of the ".short" field, 40 chars max.
  23.  *        %V    Version number of the product.
  24.  *        
  25.  *        Other characters are passed through literally, as with
  26.  *        printf.  Note that %D & %B are defined such that
  27.  *        %P=%D%B without having to know what the separator was.
  28.  *
  29.  *    -f <s>    Use <s> as the name of the file to use to write entries
  30.  *        to as a result of using the -b option.  If <s> is "-",
  31.  *        the entries are written to stdout rather than to a file
  32.  *        in each directory containing product info files.
  33.  *
  34.  *    -h    Print help message.
  35.  *
  36.  *    -n    Find Product-Info files and print names to stdout.
  37.  *
  38.  *    -s    Gather product info files and add ".stored-in" fields.
  39.  *
  40.  *    -t    Test Product Info files, report possible problems.
  41.  *
  42.  *    -v    Verbose flag, print name of each Product Info file to
  43.  *        stdout.
  44.  *
  45.  *    Generally, "file1", "file2", etc can be replaced with "-" to
  46.  *    indicate that pitool should operate on a list of files provided
  47.  *    on stdin rather than walking a file tree rooted in the path given
  48.  *    by "file1", "file2", etc.
  49.  *
  50.  */
  51.  
  52. #include <stdio.h>
  53. #include <sys/types.h>
  54. #include <sys/stat.h>
  55. #include "ftw.h"
  56.  
  57. #define STREQ(a,b) ((*(a) == *(b)) && !strcmp ((a),(b)))
  58.  
  59. extern int strlen (const char *);
  60. extern char *strrchr (const char *, char);
  61. extern char *strchr (const char *, char);
  62. extern char *strdup ();
  63.  
  64. static char *format = "%-30.30B %8.8V %4.4KK %-40.40S\n";
  65. static char *bbs_file_name = "files.bbs";
  66. static int verbose;
  67.  
  68. struct pif {
  69.   const char *pi_fname;    /* Name of file containing the product info */
  70.   int pi_line;        /* Current line number being read */
  71.   FILE *pi_fp;        /* "FILE *" pointer to the open file */
  72.   const char *pi_basename;
  73.   int pi_size;
  74.   char *pi_address;
  75.   char *pi_aminet_dir;
  76.   char *pi_author;
  77.   char *pi_construction;
  78.   char *pi_comment;
  79.   char *pi_contents;
  80.   char *pi_date;
  81.   char *pi_described_by;
  82.   char *pi_description;
  83.   char *pi_distribution;
  84.   char *pi_docs;
  85.   char *pi_email;
  86.   char *pi_exectype;
  87.   char *pi_fax;
  88.   char *pi_fullname;
  89.   char *pi_installsize;
  90.   char *pi_keywords;
  91.   char *pi_locale;
  92.   char *pi_name;    /* Contents of the ".name" field */
  93.   char *pi_phone;
  94.   char *pi_price;
  95.   char *pi_reference;
  96.   char *pi_requirements;
  97.   char *pi_restrictions;
  98.   char *pi_run;
  99.   char *pi_short;
  100.   char *pi_source;
  101.   char *pi_stored_in;
  102.   char *pi_submittal;
  103.   char *pi_tested;
  104.   char *pi_type;
  105.   char *pi_version;
  106. };
  107.  
  108. /* Note, we can't use isspace() here, because it counts
  109.    international characters as whitespace.  So just look
  110.    for the characters we are actually concerned about */
  111.  
  112. #define WHITESPACE(a) (((a) == ' ') || ((a) == '\t') || ((a) == '\n'))
  113.  
  114. void
  115. stripwhite (struct pif *pip, char *fieldp)
  116. {
  117.   char *endp;
  118.  
  119.   for (endp = fieldp; *endp != '\000'; endp++) {;}
  120.   endp--;
  121.  
  122.   if (WHITESPACE (*endp))
  123.     {
  124. #if 0    /* Currently there are too many Product Info files where this
  125.        is true, to make this a useful message.  Enable it once
  126.        we have processed all of them to eliminate the whitespace. */
  127.       fprintf (stderr, "%s:%d: extraneous whitespace at end of line\n",
  128.            pip -> pi_fname, pip -> pi_line);
  129. #endif
  130.     }
  131.   while ((endp >= fieldp) && (WHITESPACE (*endp)))
  132.     {
  133.       *endp-- = '\000';
  134.     }
  135. }
  136.  
  137. int
  138. filesize (const char *fname)
  139. {
  140.   struct stat statbuf;
  141.  
  142.   if (stat (fname, &statbuf) == 0)
  143.     {
  144.       return (statbuf.st_size);
  145.     }
  146.   else
  147.     {
  148.       return (0);
  149.     }
  150. }
  151.  
  152. const char *
  153. basename (const char *sp)
  154. {
  155.   const char *bname;
  156.  
  157.   /* Look first for typical Unix or AmigaDOS separator */
  158.  
  159.   bname = strrchr (sp, '/');
  160.  
  161.   /* Look for special AmigaDOS separator */
  162.  
  163. #ifdef __amigados__
  164.   if (bname == NULL)
  165.     {
  166.       bname = strrchr (sp, ':');
  167.     }
  168. #endif
  169.  
  170.   /* If any separator found, skip over it, otherwise the
  171.      basename is just the entire string. */
  172.  
  173.   if (bname == NULL)
  174.     {
  175.       bname = sp;
  176.     }
  177.   else
  178.     {
  179.       bname++;
  180.     }
  181.  
  182.   return (bname);
  183. }
  184.  
  185. /*
  186.  *  Note that dirname is defined such that it *includes* the
  187.  *  separator.  This way you don't need to know whether it 
  188.  *  was a '/' or ':' to reconstruct the full path.  I.E.
  189.  *  %P=%D%B  not %D/%B if '/' or %D%B if ':'
  190.  */
  191.  
  192. const char *
  193. dirname (const char *sp)
  194. {
  195.   char *endp;
  196.   static char buf[256];
  197.  
  198.   strcpy (buf, sp);
  199.  
  200.   /* Look first for typical Unix or AmigaDOS separator */
  201.  
  202.   endp = strrchr (buf, '/');
  203.  
  204.   /* Look for special AmigaDOS separator */
  205.  
  206. #ifdef __amigados__
  207.   if (endp == NULL)
  208.     {
  209.       endp = strrchr (buf, ':');
  210.     }
  211. #endif
  212.  
  213.   /* If there was a separator, set up to zap the next
  214.      character, otherwise the dirname is just the empty
  215.      string. */
  216.  
  217.   if (endp == NULL)
  218.     {
  219.       endp = buf;
  220.     }
  221.   else
  222.     {
  223.       endp++;
  224.     }
  225.   *endp = '\000';
  226.   return ((const char *) buf);
  227. }
  228.  
  229. /*
  230.  *    Given a name of a file that is potentially a product info file,
  231.  *    generate the name of the directory or file that the associated
  232.  *    product is stored in.  By convention, the "stored in" name is
  233.  *    terminated with a '/' if it is a directory.
  234.  *
  235.  *    Returns NULL if the name is not a product info file.
  236.  */
  237.  
  238. char *
  239. stored_in (const char *name)
  240. {
  241.   int length;
  242.   char *namep;
  243.   char *rtnval = NULL;
  244.   static char stored_in_buf[256];
  245.  
  246.   strcpy (stored_in_buf, name);
  247.   length = strlen (stored_in_buf);
  248.   if ((stored_in_buf[length - 1] == 'i') &&
  249.       (stored_in_buf[length - 2] == 'p') &&
  250.       (stored_in_buf[length - 3] == '.'))
  251.     {
  252.       stored_in_buf[length - 3] = '\000';
  253.       rtnval = stored_in_buf;
  254.     }
  255.   else
  256.     {
  257.       namep = strrchr (stored_in_buf, '/');
  258.       if ((namep != NULL) && (STREQ (namep + 1, "Product-Info")))
  259.     {
  260.       *++namep = '\000';
  261.       rtnval = stored_in_buf;
  262.     }
  263.     }
  264.   if (rtnval != NULL)
  265.     {
  266. #ifdef __amigados__
  267.       /* Convert "/path1/path2/file" to "path1:path2/file" */
  268.       if ((*rtnval == '/') &&
  269.       ((namep = strchr (rtnval + 1, '/')) != NULL))
  270.     {
  271.       rtnval++;
  272.       *namep = ':';
  273.     }
  274. #endif
  275.     }
  276.   return (rtnval);
  277. }
  278.  
  279. int
  280. find_pifiles (const char *name, struct stat *statbuf, int flags)
  281. {
  282.   if (stored_in (name) != NULL)
  283.     {
  284.       printf ("%s\n", name);
  285.     }
  286.   return (0);
  287. }
  288.  
  289. void
  290. copy_out (const char *filename)
  291. {
  292.   int ch;
  293.   FILE *fin;
  294.   char buf[256];
  295.   char *endp;
  296.  
  297.   fin = fopen (filename, "r");
  298.   if (fin != NULL)
  299.     {
  300.       while (fgets (buf, 256, fin) != NULL)
  301.     {
  302.       for (endp = buf; *endp != '\000'; endp++) {;}
  303.       endp--;
  304.       while ((endp >= buf) && (WHITESPACE (*endp)))
  305.         {
  306.           *endp-- = '\000';
  307.         }
  308.       puts (buf);
  309.     }
  310.       fclose (fin);
  311.     }
  312. }
  313.  
  314. int
  315. add_stored_in (const char *name, struct stat *statbuf, int flags)
  316. {
  317.   char *sname;
  318.  
  319.   sname = stored_in (name);
  320.   if (sname != NULL)
  321.     {
  322.       copy_out (name);
  323.       printf (".stored-in\n%s\n", sname);
  324.     }
  325.   return (0);
  326. }
  327.  
  328. void
  329. free_pif (struct pif *pip)
  330. {
  331.   if (pip -> pi_fp != NULL)        fclose (pip -> pi_fp);
  332.   if (pip -> pi_stored_in != NULL)    free (pip -> pi_stored_in);
  333.   if (pip -> pi_address != NULL)    free (pip -> pi_address);
  334.   if (pip -> pi_aminet_dir != NULL)    free (pip -> pi_aminet_dir);
  335.   if (pip -> pi_author != NULL)        free (pip -> pi_author);
  336.   if (pip -> pi_construction != NULL)    free (pip -> pi_construction);
  337.   if (pip -> pi_comment != NULL)    free (pip -> pi_comment);
  338.   if (pip -> pi_contents != NULL)    free (pip -> pi_contents);
  339.   if (pip -> pi_date != NULL)        free (pip -> pi_date);
  340.   if (pip -> pi_described_by != NULL)    free (pip -> pi_described_by);
  341.   if (pip -> pi_description != NULL)    free (pip -> pi_description);
  342.   if (pip -> pi_distribution != NULL)    free (pip -> pi_distribution);
  343.   if (pip -> pi_docs != NULL)        free (pip -> pi_docs);
  344.   if (pip -> pi_email != NULL)        free (pip -> pi_email);
  345.   if (pip -> pi_exectype != NULL)    free (pip -> pi_exectype);
  346.   if (pip -> pi_fax != NULL)        free (pip -> pi_fax);
  347.   if (pip -> pi_fullname != NULL)    free (pip -> pi_fullname);
  348.   if (pip -> pi_installsize != NULL)    free (pip -> pi_installsize);
  349.   if (pip -> pi_keywords != NULL)    free (pip -> pi_keywords);
  350.   if (pip -> pi_locale != NULL)        free (pip -> pi_locale);
  351.   if (pip -> pi_name != NULL)        free (pip -> pi_name);
  352.   if (pip -> pi_phone != NULL)        free (pip -> pi_phone);
  353.   if (pip -> pi_price != NULL)        free (pip -> pi_price);
  354.   if (pip -> pi_reference != NULL)    free (pip -> pi_reference);
  355.   if (pip -> pi_requirements != NULL)    free (pip -> pi_requirements);
  356.   if (pip -> pi_restrictions != NULL)    free (pip -> pi_restrictions);
  357.   if (pip -> pi_run != NULL)        free (pip -> pi_run);
  358.   if (pip -> pi_short != NULL)        free (pip -> pi_short);
  359.   if (pip -> pi_source != NULL)        free (pip -> pi_source);
  360.   if (pip -> pi_submittal != NULL)    free (pip -> pi_submittal);
  361.   if (pip -> pi_tested != NULL)        free (pip -> pi_tested);
  362.   if (pip -> pi_type != NULL)        free (pip -> pi_type);
  363.   if (pip -> pi_version != NULL)    free (pip -> pi_version);
  364.  
  365.   free (pip);
  366. }
  367.  
  368. struct pif *
  369. open_pif (const char *name)
  370. {
  371.   struct pif *pip;
  372.   extern char *malloc ();
  373.   char *stor;
  374.  
  375.   pip = (struct pif *) malloc (sizeof (struct pif));
  376.   if (pip == NULL)
  377.     {
  378.       fprintf (stderr, "pitool: out of virtual memory\n");
  379.       exit (1);
  380.     }
  381.   memset (pip, 0, sizeof (struct pif));
  382.   pip -> pi_fname = name;
  383.   pip -> pi_line = 0;
  384.   pip -> pi_basename = basename (pip -> pi_fname);
  385.   pip -> pi_fp = fopen (pip -> pi_fname, "r");
  386.   stor = stored_in (pip -> pi_fname);
  387.   if (stor != NULL)
  388.     {
  389.       pip -> pi_stored_in = strdup (stor);
  390.     }
  391.   if (pip -> pi_fp == NULL)
  392.     {
  393.       perror (pip -> pi_fname);
  394.       free_pif (pip);
  395.       pip = NULL;
  396.     }
  397.   else if (verbose)
  398.     {
  399.       printf ("%s\n", pip -> pi_fname);
  400.     }
  401.   return (pip);
  402. }
  403.  
  404. /* Find next field marker and strip off any whitespace from end of the
  405.    line. */
  406.  
  407. char *
  408. next_field (struct pif *pip)
  409. {
  410.   static char readbuf[256];
  411.   char *bufp;
  412.   
  413.   while (fgets (readbuf, sizeof (readbuf), pip -> pi_fp) != NULL)
  414.     {
  415.       pip -> pi_line++;
  416.       if (readbuf[0] == '.')
  417.     {
  418.       for (bufp = readbuf; *bufp != '\000'; bufp++) {;}
  419.       bufp--;
  420.       while ((bufp >= readbuf) && WHITESPACE (*bufp))
  421.         {
  422.           *bufp-- = '\000';
  423.         }
  424.       return (readbuf);
  425.     }
  426.     }
  427.   return (NULL);
  428. }
  429.  
  430. char *
  431. read_field (struct pif *pip)
  432. {
  433.   static char readbuf[16 * 1024];
  434.   char *sp = readbuf;
  435.   int ch = '\n';    /* Detect '.' if first char read */
  436.   int prevch = 0;
  437.   
  438.   for (;;)
  439.     {
  440.       prevch = ch;
  441.       ch = getc (pip -> pi_fp);
  442.       if (ch == '\n')
  443.     {
  444.       pip -> pi_line++;
  445.     }
  446.       if (ch == '.')
  447.     {
  448.       if (prevch == '\n')
  449.         {
  450.           ungetc (ch, pip -> pi_fp);
  451.           break;
  452.         }
  453.     }
  454.       else if (ch == EOF)
  455.     {
  456.       if (prevch != '\n')
  457.         {
  458.           fprintf (stderr, "%s:%d: does not end with newline\n",
  459.                pip -> pi_fname, pip -> pi_line);
  460.           *sp++ = '\n';
  461.         }
  462.       break;
  463.     }
  464.       *sp++ = ch;
  465.     }
  466.   if (sp > readbuf)
  467.     {
  468.       sp--;
  469.     }
  470.   *sp = '\000';
  471.   stripwhite (pip, readbuf);
  472.   return (readbuf);
  473. }
  474.  
  475. struct pif *
  476. read_pif (const char *name)
  477. {
  478.   struct pif *pip;
  479.   char *fieldname;
  480.   char *rawfield;
  481.   char *temp;
  482.  
  483.   pip = open_pif (name);
  484.   if (pip != NULL)
  485.     {
  486.       while ((fieldname = next_field (pip)) != NULL)
  487.     {
  488.       rawfield = read_field (pip);
  489.       if (STREQ (fieldname + 1, "address"))
  490.         { pip -> pi_address = strdup (rawfield); continue; }
  491.       if (STREQ (fieldname + 1, "aminet-dir"))
  492.         { pip -> pi_aminet_dir = strdup (rawfield); continue; }
  493.       if (STREQ (fieldname + 1, "author"))
  494.         { pip -> pi_author = strdup (rawfield); continue; }
  495.       if (STREQ (fieldname + 1, "construction"))
  496.         { pip -> pi_construction = strdup (rawfield); continue; }
  497.       if (STREQ (fieldname + 1, "comment"))
  498.         { pip -> pi_comment = strdup (rawfield); continue; }
  499.       if (STREQ (fieldname + 1, "contents"))
  500.         { pip -> pi_contents = strdup (rawfield); continue; }
  501.       if (STREQ (fieldname + 1, "date"))
  502.         { pip -> pi_date = strdup (rawfield); continue; }
  503.       if (STREQ (fieldname + 1, "described-by"))
  504.         { pip -> pi_described_by = strdup (rawfield); continue; }
  505.       if (STREQ (fieldname + 1, "description"))
  506.         { pip -> pi_description = strdup (rawfield); continue; }
  507.       if (STREQ (fieldname + 1, "distribution"))
  508.         { pip -> pi_distribution = strdup (rawfield); continue; }
  509.       if (STREQ (fieldname + 1, "docs"))
  510.         { pip -> pi_docs = strdup (rawfield); continue; }
  511.       if (STREQ (fieldname + 1, "email"))
  512.         { pip -> pi_email = strdup (rawfield); continue; }
  513.       if (STREQ (fieldname + 1, "exectype"))
  514.         { pip -> pi_exectype = strdup (rawfield); continue; }
  515.       if (STREQ (fieldname + 1, "fax"))
  516.         { pip -> pi_fax = strdup (rawfield); continue; }
  517.       if (STREQ (fieldname + 1, "fullname"))
  518.         { pip -> pi_fullname = strdup (rawfield); continue; }
  519.       if (STREQ (fieldname + 1, "installsize"))
  520.         { pip -> pi_installsize = strdup (rawfield); continue; }
  521.       if (STREQ (fieldname + 1, "keywords"))
  522.         { pip -> pi_keywords = strdup (rawfield); continue; }
  523.       if (STREQ (fieldname + 1, "locale"))
  524.         { pip -> pi_locale = strdup (rawfield); continue; }
  525.       if (STREQ (fieldname + 1, "name"))
  526.         { pip -> pi_name = strdup (rawfield); continue; }
  527.       if (STREQ (fieldname + 1, "phone"))
  528.         { pip -> pi_phone = strdup (rawfield); continue; }
  529.       if (STREQ (fieldname + 1, "price"))
  530.         { pip -> pi_price = strdup (rawfield); continue; }
  531.       if (STREQ (fieldname + 1, "reference"))
  532.         { pip -> pi_reference = strdup (rawfield); continue; }
  533.       if (STREQ (fieldname + 1, "requirements"))
  534.         { pip -> pi_requirements = strdup (rawfield); continue; }
  535.       if (STREQ (fieldname + 1, "restrictions"))
  536.         { pip -> pi_restrictions = strdup (rawfield); continue; }
  537.       if (STREQ (fieldname + 1, "run"))
  538.         { pip -> pi_run = strdup (rawfield); continue; }
  539.       if (STREQ (fieldname + 1, "short"))
  540.         {
  541.           pip -> pi_short = strdup (rawfield);
  542.           if (strlen (rawfield) > 40)
  543.         {
  544.           fprintf (stderr,
  545.                "%s:%d: '.short' field > 40 characters\n",
  546.                pip -> pi_fname, pip -> pi_line);
  547.         }
  548.           if ((temp = strchr (rawfield, '\n')) != NULL)
  549.         {
  550.           *temp = '\000';
  551.           fprintf (stderr,
  552.                "%s:%d: '.short' field contains newline\n",
  553.                pip -> pi_fname, pip -> pi_line);
  554.         }
  555.           continue;
  556.         }
  557.       if (STREQ (fieldname + 1, "source"))
  558.         { pip -> pi_source = strdup (rawfield); continue; }
  559.       if (STREQ (fieldname + 1, "submittal"))
  560.         { pip -> pi_submittal = strdup (rawfield); continue; }
  561.       if (STREQ (fieldname + 1, "tested"))
  562.         { pip -> pi_tested = strdup (rawfield); continue; }
  563.       if (STREQ (fieldname + 1, "type"))
  564.         { pip -> pi_type = strdup (rawfield); continue; }
  565.       if (STREQ (fieldname + 1, "version"))
  566.         { pip -> pi_version = strdup (rawfield); continue; }
  567.       fprintf (stderr, "%s:%d: unrecognized field '%s'\n", 
  568.            pip -> pi_fname, pip -> pi_line, fieldname);
  569.     }
  570.       fclose (pip -> pi_fp);
  571.       pip -> pi_fp = NULL;
  572.     }
  573.   if (pip -> pi_name == NULL)
  574.     {
  575.       fprintf (stderr, "%s:%d: missing '.name' field\n", pip -> pi_fname,
  576.            pip -> pi_line);
  577.     }
  578.   if (pip -> pi_short == NULL)
  579.     {
  580.       fprintf (stderr, "%s:%d: missing '.short' field\n", pip -> pi_fname,
  581.            pip -> pi_line);
  582.     }
  583.   return (pip);
  584. }
  585.  
  586. void
  587. fprintf_pif (FILE *fp, struct pif *pip)
  588. {
  589.   char pformat[256];
  590.   char *pformatp = pformat;
  591.   char *formatp = format;
  592.   int argc = 0;
  593.   int needspec = 0;
  594.   const char *argv[10];        /* FIXME:  check for overflow */
  595.   static char sizebuf[16];
  596.  
  597.   while (*formatp != '\000')
  598.     {
  599.       if (needspec)
  600.     {
  601.       switch (*formatp)
  602.         {
  603.         case 'B':        /* Basename of "stored-in" path */
  604.           argv[argc++] = basename (pip -> pi_stored_in);
  605.           *pformatp++ = 's';
  606.           formatp++;
  607.           needspec = 0;
  608.           break;
  609.         case 'D':        /* Dirname of "stored-in" path */
  610.           argv[argc++] = dirname (pip -> pi_stored_in);
  611.           *pformatp++ = 's';
  612.           formatp++;
  613.           needspec = 0;
  614.           break;
  615.         case 'K':        /* "Stored-in" filesize in Kb */
  616.           sprintf (sizebuf, "%d", filesize (pip -> pi_stored_in) / 1024);
  617.           argv[argc++] = sizebuf;
  618.           *pformatp++ = 's';
  619.           formatp++;
  620.           needspec = 0;
  621.           break;
  622.         case 'P':        /* Full "stored-in" path */
  623.           argv[argc++] = pip -> pi_stored_in;
  624.           *pformatp++ = 's';
  625.           formatp++;
  626.           needspec = 0;
  627.           break;
  628.         case 'S':        /* The ".short" field */
  629.           argv[argc++] = pip -> pi_short ? pip -> pi_short : "<no description avail>";
  630.           *pformatp++ = 's';
  631.           *formatp++;
  632.           needspec = 0;
  633.           break;
  634.         case 'V':        /* Version */
  635.           argv[argc++] = pip -> pi_version ? pip -> pi_version : "?.?";
  636.           *pformatp++ = 's';
  637.           formatp++;
  638.           needspec = 0;
  639.           break;
  640.         case '%':
  641.           if (*(formatp - 1) != '%')
  642.         {
  643.           fprintf (stderr, "bad -F format string, missing spec\n");
  644.           exit (1);
  645.         }
  646.           /* A '%' can quote the following '%' */
  647.           *pformatp++ = *formatp++;
  648.           needspec = 0;
  649.           break;
  650.         default:
  651.           /* Stuff to print verbatum */
  652.           *pformatp++ = *formatp++;
  653.           break;
  654.         }
  655.     }
  656.       else if (*formatp == '%')
  657.     {
  658.       /* We are not looking for a spec because of a previous '%' */
  659.       needspec++;
  660.       *pformatp++ = *formatp++;
  661.     }
  662.       else if (*formatp == '\\')
  663.     {
  664.       /* Simple '\' evaluation, only single chars now.  FIXME */
  665.       formatp++;
  666.       switch (*formatp)
  667.         {
  668.         case '\\':
  669.           *pformatp++ = '\\'; /* quoted it */
  670.           break;
  671.         case 't':
  672.           *pformatp++ = '\t';
  673.           break;
  674.         case 'n':
  675.           *pformatp++ = '\n';
  676.           break;
  677.         case 'b':
  678.           *pformatp++ = '\b';
  679.           break;
  680.         case 'r':
  681.           *pformatp++ = '\r';
  682.           break;
  683.         case 'f':
  684.           *pformatp++ = '\f';
  685.           break;
  686.         default:
  687.           *pformatp++ = *formatp;
  688.           break;
  689.         }
  690.       formatp++;
  691.     }
  692.       else
  693.     {
  694.       /* stuff that needs no interpretation */
  695.       *pformatp++ = *formatp++;
  696.     }
  697.     }
  698.   *pformatp++ = '\000';
  699.   fprintf (fp, pformat,
  700.        argv[0], argv[1], argv[2], argv[3], argv[4],
  701.        argv[5], argv[6], argv[7], argv[8], argv[9]);
  702. }
  703.  
  704. int
  705. add_files_bbs (const char *name, struct stat *statbuf, int flags)
  706. {
  707.   struct pif *pip;
  708.   char bbsfile[256];
  709.   char *scan;
  710.   FILE *bbsfp;
  711.  
  712.   if (stored_in (name) != NULL)
  713.     {
  714.       pip = read_pif (name);
  715.       if (pip != NULL)
  716.     {
  717.       if (STREQ (bbs_file_name, "-"))
  718.         {
  719.           fprintf_pif (stdout, pip);
  720.         }
  721.       else
  722.         {
  723.           strcpy (bbsfile, name);
  724.           scan = strrchr (bbsfile, '/');
  725.           if (scan != NULL)
  726.         {
  727.           scan++;
  728.           strcpy (scan, bbs_file_name);
  729.           bbsfp = fopen (bbsfile, "a");
  730.           if (bbsfp != NULL)
  731.             {
  732.               fprintf_pif (bbsfp, pip);
  733.               fclose (bbsfp);
  734.             }
  735.         }
  736.         }
  737.       free_pif (pip);
  738.     }
  739.     }
  740.   return (0);
  741. }
  742.  
  743. int
  744. test_pifiles (const char *name, struct stat *statbuf, int flags)
  745. {
  746.   struct pif *pip;
  747.   char bbsfile[256];
  748.   char *scan;
  749.   FILE *bbsfp;
  750.  
  751.   if (stored_in (name) != NULL)
  752.     {
  753.       pip = read_pif (name);
  754.       if (pip != NULL)
  755.     {
  756.       free_pif (pip);
  757.     }
  758.     }
  759.   return (0);
  760. }
  761.  
  762. void
  763. filter_stdin (int (*filter_through) (const char *name, struct stat *statbuf, int flags))
  764. {
  765.   char buf [256];
  766.  
  767.   while (gets (buf) != NULL)
  768.     {
  769.       /* Note that currently only first arg is used. */
  770.       (*filter_through) ((const char *) buf, NULL, 0);
  771.     }
  772. }
  773.  
  774. int
  775. main (argc, argv)
  776. int argc;
  777. char *argv[];
  778. {
  779.   int ch;
  780.   int action;
  781.   int errflg = 0;
  782.   extern int optind;
  783.   extern char *optarg;
  784.  
  785.   while ((ch = getopt (argc, argv, "F:f:bfhnstv")) != EOF)
  786.     {
  787.       switch (ch)
  788.     {
  789.     case 'F':
  790.       format = optarg;
  791.       break;
  792.     case 'f':
  793.       bbs_file_name = optarg;
  794.       break;
  795.     case 'h':
  796.       break;
  797.     case 'v':
  798.       verbose++;
  799.       break;
  800.     case '?':
  801.       errflg++;
  802.       break;
  803.     default:
  804.       action = ch;
  805.       break;
  806.     }
  807.       if (errflg)
  808.     {
  809.       fprintf (stderr, "unknown arg, use -h for help\n");
  810.       exit (1);
  811.     }
  812.     }
  813.   for ( ; optind < argc; optind++)
  814.     {
  815.       if (STREQ (argv[optind], "-"))
  816.     {
  817.       switch (action)
  818.         {
  819.         case 'b':
  820.           filter_stdin (add_files_bbs);
  821.           break;
  822.         case 'n':
  823.           filter_stdin (find_pifiles);
  824.           break;
  825.         case 's':
  826.           filter_stdin (add_stored_in);
  827.           break;
  828.         case 't':
  829.           filter_stdin (test_pifiles);
  830.           break;
  831.         default:
  832.           fprintf (stderr, "unknown action '%c'\n", action);
  833.           exit (1);
  834.         }
  835.     }
  836.       else
  837.     {
  838.       switch (action)
  839.         {
  840.         case 'b':
  841.           ftw ((const char *) argv[optind], add_files_bbs, 30);
  842.           break;
  843.         case 'n':
  844.           ftw ((const char *) argv[optind], find_pifiles, 30);
  845.           break;
  846.         case 's':
  847.           ftw ((const char *) argv[optind], add_stored_in, 30);
  848.           break;
  849.         case 't':
  850.           ftw ((const char *) argv[optind], test_pifiles, 30);
  851.           break;
  852.         default:
  853.           fprintf (stderr, "unknown action '%c'\n", action);
  854.           exit (1);
  855.         }
  856.     }
  857.     }
  858.   exit (0);
  859.   return (0);
  860. }
  861.