From:	MERC::"uunet!CRVAX.SRI.COM!RELAY-INFO-VAX" 16-APR-1992 12:16:53.74
To:	info-vax@kl.sri.com
CC:	
Subj:	C function to glob command line arguments

Included below is a C function that can be used to perform command line
file globbing from a VAX/VMS C program.  To use it, you simply add one
line of code to your program and then install your program as a foreign
command.  It seems to be pretty robust, so I'm turning it over to the
net for further testing :-) .  I have personally used it to add file
globbing to a VMS-ported version of "wc" and the VMS-ported version of
"RCS".  If you find problems, let me know...  Thanks.

-------------------------------- Cut Here ----------------------------

/*
 * FILENAME
 *    expfiles.c
 *
 * PURPOSE
 *    Simple routine to take argc and argv from the unix-emulated command
 *    line provided by the VAX C runtime environment and:
 *
 *    - expand all wild card filenames,
 *
 *    - lowercase the expanded filenames,
 *
 *    - strip the working directory from each expanded file name if 
 *      it is the same as the invoking process's working directory, and
 *
 *    - strip the version# from the expanded filename if requested by the
 *      client.
 *
 * FEATURES
 *    - Handles normal Unix command line file globbing (using VMS wildcard
 *      characters only).  Example:
 *
 *      <program_name> *.h *.c some_logical:*.com
 *
 *    - Handles VMS-style comma-delimited file globbing so long as the
 *      file spec containing the comma (or commas) is a single command
 *      line token.  Example:
 *    
 *      <program_name> some_logical:*.com,*.opt        !Okay - 1 token
 *
 *      <program_name> "*.h ,*.opt"                    !Okay - 1 token
 *
 *      <program_name> *.h ,*.opt                      !Wrong - 2 tokens
 *
 *    - Handles all wildcard tokens supported by lib$find_file(),
 *      including "*", "%", and "[...]".
 *
 * ERROR RECOVERY:
 *    If the command line arguments cannot be completely expanded (i.e., no
 *    errors encountered), the original argv and argc are returned unchanged.
 *
 * EXAMPLE USAGE
 *   The code fragment shown below was taken from a VMS version of the
 *   Unix "wc" utility:
 *
 *     main(int argc, char **argv)
 *     {
 *        ... 
 *        expand_files(&argc, &argv, 0);
 *        while (--argc)
 *        {
 *           ...
 *        }
 *     }
 *
 * AUTHOR
 *    Clark O. Morgan
 *
 * RHETORICAL COMMENT
 *    Why in the blue blazes doesn't DEC supply this kind of function
 *    with its C runtime library?
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <descrip.h>
#include <rmsdef.h>

#define BUFLEN             257
#define INCR                64
#define MULTIPLE    (0x1 << 1)

/* ---------------------------------------------------------------------- */

void
expand_files(
   int  *cmdline_argc,    /* From client's main(), by reference. */
   char ***cmdline_argv,  /* Ditto.                              */
   int  strip_version     /* Boolean, T -> strip version#        */
   )    
{
   int  argc, allocd, argv_len;
   char **argv, *cwd, cwd_buf[BUFLEN], inbuf[BUFLEN], outbuf[BUFLEN], **pc,
        *delim, *hb, holdbuf[BUFLEN];
   long context = NULL, find_status, flags = MULTIPLE;
   
   $DESCRIPTOR(input_desc, inbuf);
   $DESCRIPTOR(output_desc, outbuf);
   
   argc = *cmdline_argc;
   if (argc == 1)
      return;     /* simple optimization, nothing to expand. */
   if (! (argv = calloc(sizeof(argv), INCR)))
      return;     /* Boy oh boy, this process doesn't have much memory. */  
   argv_len = INCR;
   cwd      = NULL;
   pc       = *cmdline_argv;
   argv[0]  = *pc++;    /* Copy program name. */
   allocd   = 1;
   while (--argc)
   {
      strcpy(holdbuf, *pc);
      hb = holdbuf;
      while (hb)
      {
         delim = strchr(hb, ',');   /* handle comma-delmited files. */
         if (delim)
            *delim++ = '\0';
         strcpy(inbuf, hb);
         input_desc.dsc$w_length = strlen(inbuf);
         while (1)
         {
            char *beginning;
            int  status = lib$find_file(&input_desc,
                                        &output_desc,
                                        &context,
                                        NULL,
                                        NULL,
                                        &find_status,
                                        &flags);  
            if (status == RMS$_NMF || status == RMS$_CHN)
               break;  /* No more translations for this file spec. */
            else
            {
               /*
                * Successful lookup one way or another, the client will
                * need to deal with the error cases (e.g., permission
                * denied, invalid device, etc.).
                */
               
               char *cp;
               
               if (allocd >= argv_len)
               {
                  int i;
                  
                  if (! (argv = realloc(argv, (argv_len + INCR) * sizeof(argv))))
                  {
                     /* Out of memory. */
                     
                     lib$find_file_end(&context);
                     return;
                  }
                  for (i = argv_len; i < argv_len + INCR; i++)
                     argv[i] = NULL;
                  argv_len += INCR;
               }
               cp = strchr(outbuf, ' ');  /* Strip trailing white space */
               if (cp)                    /* from returned file name.   */
                  *cp = '\0';
               else
                  outbuf[BUFLEN - 1] = '\0';  
                  
               /*
                * Strip working directory and working disk if appropriate...
                * lib$find_file always returns a working directory and working
                * disk with the expanded file name.  This tends to foul up a
                * lot of Unix-ported utilities, like RCS.
                */
               if ((cp = strrchr(outbuf, ']')) != NULL)
               {
                  char save;
                  
                  if (! cwd)
                     cwd = getcwd(cwd_buf, sizeof(cwd_buf));
                  save = *++cp;
                  *cp  = '\0';
                  if (strcmp(outbuf, cwd) == 0)
                  {
                     *cp       = save;
                     beginning = cp;
                  }
                  else
                  {
                     *cp = save;
                     cp  = beginning = outbuf;
                  }
               }
               else
                  cp = beginning = outbuf;
               if (strip_version)
               {
                  char *ver = strrchr(cp, ';');
                  
                  if (ver)
                     *ver = '\0';
               }
               while (*cp)
               {
                  if (isupper(*cp))        /* Lowercase file name. */
                     *cp = tolower(*cp);
                  cp++;
               }
               if (! (argv[allocd] = malloc(cp - beginning + 1)))
               {
                  lib$find_file_end(&context);
                  return;
               }
               strcpy(argv[allocd++], beginning);
            }
         }
         hb = (delim) ? delim : NULL;
      }
      lib$find_file_end(&context);
      pc++;
   }
   *cmdline_argc = allocd;
   *cmdline_argv = argv;
}

