Subversion Repositories DevTools

Rev

Rev 227 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * This replace for the standard WATCOM supplied argv processing
 * function __Init_Argv.
 *
 *  Handles $<env-name>... expands into the named environment variabe
 *                         accepts $(<env-name>) or ${<env-name>} to
 *                         allow for trailing characters which would
 *                         normally be a part of the name. The value
 *                         of <env-name> is substituted into the string.
 *
 *  Handles @cmd-file-name read arguments.. any number per line, any
 *                         number of lines from <cmd-file-name>.. lines
 *                         read from the file get glob'd too!
 *
 *
 *  Handles "" strings     $<env-name> still expanded but results
 *                         in a single argument.
 *
 *  Handles '' strings     nothing inside expanded & results in a
 *                         single argument.
 *
 *  Handles `` expansion   where the command inside the backquotes
 *                         gets executed and its output substituted
 *                         in place of the string.
 *
 *  Handles unix like wildcards.. including [] character sets and
 *  {} type alternations.
 *
 */

#include <stdio.h>
#include <strings.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <dirent.h>
#include <malloc.h>

#include "glob.h"         /* filename globbing */



/*
 * Increments for growing various arrays.
 */
#define ARGV_GROW_SIZE 100
#define ARG_GROW_SIZE  2000

/*
 * The following are part of the Watcom runtime
 */
extern char **___Argv;          /* the eventual argv */
extern int  ___Argc;            /* the eventual argc */
extern char *_LpPgmName;        /* the program name */
extern char *_LpCmdLine;        /* the raw cmdline */
extern char **environ;          /* the environment strings */

/*
 * How big the array pointed to by __Argv really is.
 */
static int argv_allocated = 0;

/*
 * Non-zero if any wildcards were seen processing the arguments.
 */
static int wildcards_seen = 0;

/*
 * Hmmm, should be in a header someplace... maybe... perhaps.
 */
extern FILE *popen();
extern void pclose();

/*
 * Internal functions.
 */
static void glob_cmd_line(char *);
static void glob_env(int);
static void expand(char*, char*, int, int);
static char **exec_prog(char*m, int);
static char **read_cmd_file(char*);
static int  whitespace_p(char *s, int quote);
static char *xgetenv(char *);
static void add_arg(char*);
static glob_t *wildcard_expand(char*, int);

/*
 * Flag given to expand().
 */
#define ENV_ARG     1
#define CMDLINE_ARG 0

/*
 * True if `c' is a space, tab.
 */
#define WS_P(c) ((c) == ' ' || (c) == '\t')

/*
 * True if `c' is a CR or LF.
 */
#define NL_P(c) ((c) == '\r' || (c) == '\n')

/*
 * True if `c' is special to filename expansion.
 */
#define SPECIAL(c) ((c) == '*' || (c) == '?' || (c) == '[' || (c) == '{')


/******************************************************************************
 *
 * Primary entry point. By linking in this code we simply replace the standard
 * WATCOM supplied version of command line handling: the function __Init_Argv.
 *
 *****************************************************************************/

void __Init_Argv(void)
{
  int  nenv_args = 0;
  char *tmp;

  extern void __setenvp(void);  /* A WATCOM thing which sets up environ */
  __setenvp();                  /* found by grovelling                  */


#ifdef TEST
    printf("_LpCmdLine: %x\n", _LpCmdLine);
    printf("_LpPgmName: %x\n", _LpPgmName);
    printf("_LpCmdLine: \"%s\"\n", _LpCmdLine);
    printf("_LpPgmName: \"%s\"\n", _LpPgmName);
#endif
  ___Argc = 0;
  strlwr(_LpPgmName);

  for (tmp = _LpPgmName; *tmp; tmp++)
    {
      if (*tmp == '\\')
        *tmp = '/';
    }
  add_arg(_LpPgmName);

  if ((tmp = xgetenv("_argc")) != 0)
    {
#ifdef TEST
      printf("getenv(_argc): %x, \"%s\"\n", tmp, tmp);
#endif
      nenv_args = atoi(tmp);
    }

  /*
   *  Stuff from the environment goes first.
   */
  if (nenv_args)
    {
      glob_env(nenv_args);
    }

  /*
   * Tack anything from the command line on the end of the
   * argument array.
   */
  glob_cmd_line(_LpCmdLine);

  ___Argv[___Argc] = 0;

  if (wildcards_seen && ___Argc == 1)
    {
      printf("No match.\n");
      exit(1);
    }

#ifdef TEST
  printf("final argc: %d\n", ___Argc);
#endif
}


/******************************************************************************
 *
 * Loop through the environment grabbing arguments from it.
 *
 *****************************************************************************/

static void glob_env(int nargs)
{
  int i;
  char *arg;
  char env_name[80];

  if (nargs > 999999)
    {
      fprintf(stderr, "glob_env: too many arguments (>= 1000000)\n");
      exit(42);
    }

  for (i = 1; i < nargs; i++)
    {
      sprintf(env_name, "_argv%d", i);
      if ((arg = xgetenv(env_name)) == 0)
        {
          fprintf(stderr, "glob_env: BAD environment: _argc: %d, no _argv%d\n",
                  nargs, i);
          exit(42);
        }
      expand(0, arg, ENV_ARG, 0);
    }
}



/******************************************************************************
 *
 * Find each distinct argument on the command line, taking note
 * of quotes etc. then expand & add each argument to the final
 * argument vector. Leaves the quote characters intact (for now).
 *
 *****************************************************************************/

static void glob_cmd_line(const char *cmd_line)
{
  char *l   = strdup(cmd_line);
  char argc = 0;
  char *argv[64];       /* can't get more than this on a DOS command line */
  int  i;

  while (*l)
    {
      /*
       * Find the start of the argument.
       */
      while (*l == ' ' || *l == '\t') l++;

      argv[argc++] = l;

      /*
       * Find the end of the argument.
       */
      for (;*l; l++)
        {
          if (*l == '\'' || *l == '\"' || *l == '`')
            {
              int quote_char = *l++;
              
              do
                {
                  if (!*l)
                    {
                      fprintf(stderr, "Unmatched %c.\n", quote_char);
                      exit(42);
                    }
                } while (*l++ != quote_char);
            }

          if (*l == ' ' || *l == '\t')
            {
              *l++ = '\0';
              break;
            }
        }
    }

  for (i = 0; i < argc; i++)
    {
      expand(0, argv[i], CMDLINE_ARG, 0);
    }
}



/******************************************************************************
 *
 * Process an argument...
 *
 * If PREFIX is non-zero then it contains a partial argument
 * which has already been processed... it is simply copied into
 * the output buffer. This is used when handling variables and
 * backquoting.. backquoting particularly crappy because it
 * turns a single argument into many.
 *
 *****************************************************************************/

static void expand(char *prefix, char *arg, int from_env, int in_string)
{
  int  prefix_len = (prefix)?strlen(prefix):0;
  int  buf_len = prefix_len + ARG_GROW_SIZE;
  char *buf = (char*)malloc(buf_len);
  char *bufp = buf;
  int   in_quote = 0;

#ifdef TEST
  printf("expand(prefix: \"%s\", arg: \"%s\")\n",
          prefix?prefix:"(nil)", arg);
#endif

#define CHECK_BUF_END(l)                                        \
  {                                                             \
    int offset = bufp - buf;                                    \
    while ((offset + (l)) >= buf_len)                           \
      {                                                         \
        buf = (char*)realloc(buf, buf_len += ARG_GROW_SIZE);    \
      }                                                         \
    bufp = buf + offset;                                        \
  }

  if (!buf)
    {
      fprintf(stderr, "expand: Out of memory\n");
      exit(42);
    }

  if (prefix)
    {
      strcpy(buf, prefix);
      bufp += prefix_len;
    }


  /*
   * do stuff..
   */
  for (;*arg; arg++)
    {
      switch (*arg)
        {
        case '\'':
          {
            in_quote = !in_quote;
            arg;
            break;
          }

        case '\"':
          {
            if (!in_quote)
              {
                in_string = !in_string;
                arg;
              }
            else
              goto copy_it;
            break;
          }

        case '`':
          {
            if (!in_quote)
              {
                char *start = ++arg;
                char **lines;

                for (arg++; *arg != '`'; arg++);
                CHECK_BUF_END(arg - start);
                strncpy(bufp, start, arg - start);
                *(bufp + (arg - start)) = '\0';
                lines =  exec_prog(bufp, in_string);
                *bufp = '\0';

                if (lines)
                  {
                    for (;*lines; lines++)
                      {
                        if (lines[1])
                          {
                            /* more arguments after this one. */
                            expand((bufp == buf)?0:buf, *lines, 0, in_string);
                            bufp = buf;
                          }
                        else
                          {
                            char *new_arg;
                            /* last argument.. tack the rest of arg onto it. */
                            new_arg = (char*)malloc(strlen(*lines) +
                                                    strlen(arg + 1) + 1);
                            if (!new_arg)
                              {
                                fprintf(stderr, "expand: Out of memory\n");
                              }
                            strcpy(new_arg, *lines);
                            strcat(new_arg, arg + 1);
                            expand((bufp == buf)?0:buf, new_arg, 0, in_string);
                            free(new_arg);
                          }
                      }
                  }
                return;
              }
            else
              goto copy_it;
            break;
          }

        case '$':
          {
            if (!in_quote)
              {
                char *start  = ++arg;
                int  bracket = 0;
                char var_name[200];
                char *new_arg;
                char *var_val;

                if (*start == '(')
                  {
                    bracket = ')';
                    start++;
                  }
                else
                  if (*start == '{')
                    {
                      bracket = '}';
                      start++;
                    }

                if (bracket)
                  {
                    while (*arg && *arg != bracket)
                      arg++;
                    if (!*arg)
                      {
                        fprintf(stderr, "Missing %c.\n", bracket);
                        exit(42);
                      }
                    strncpy(var_name, start, arg - start);
                    var_name[arg-start] = '\0';
                    arg++;
                  }
                else
                  {
                    while (*arg && (isalnum(*arg) || *arg == '_'))
                      arg++;
                    strncpy(var_name, start, arg - start);
                    var_name[arg-start] = '\0';
                  }

                if (var_val = xgetenv(var_name))
                  {
                    new_arg = (char *)malloc(strlen(arg) +
                                             strlen(var_val) + 1);
                    if (!new_arg)
                      {
                        fprintf(stderr, "expand: Out of memory (expanding var)\n");
                        exit(42);
                      }
                    strcpy(new_arg, var_val);
                    strcat(new_arg, arg);
                    *bufp = '\0';
                    expand((bufp == buf)?0:buf, new_arg, 0, in_string);
                    free(new_arg);
                  }
                else
                  {
                    fprintf(stderr, "Undefined variable: %s\n", var_name);
                    exit(42);
                  }
                return;
              }
            else
              goto copy_it;
            break;
          }

        default:
          {
          copy_it:
            if (SPECIAL(*arg) && (in_quote || in_string))
              {
                CHECK_BUF_END(1);
                *bufp++ = '\\';
              }
            CHECK_BUF_END(1);
            *bufp++ = *arg;
            break;
          }
        }
    }
  
  CHECK_BUF_END(1);
  *bufp = '\0';

  /*
   * Check for a reference to a command file..
   */
  if (*buf == '@')
    {
      char **lines = read_cmd_file(buf + 1);
      while (lines && *lines)
        {
          expand(0, *lines++, 0, 0);
        }
      free(buf);
      return;
    }

  /*
   * wildcard expand the argument... arguments from the environment
   * only get wildcarded if they don't contain any spaces.
   */
  if (*buf)
    {
      if (from_env && (strchr(buf, ' ') || strchr(buf, '\t')))
        add_arg(buf);
      else
        {
          glob_t *gb = wildcard_expand(buf, 1);
          int    i;
          if (gb)
            {
              for (i = 0; i < gb->nused; i++)
                add_arg(gb->paths[i]);
              arg_free_glob_t(gb);
            }
          else
            add_arg(buf); /* no wildcards in `buf' so just add it */
        }
    }

  free(buf);
}



/******************************************************************************
 *
 * Add an argument to ___Argv.. expanding Argv if required.
 *
 *****************************************************************************/

static void add_arg(char *arg)
{
  char *str = strdup(arg);


#ifdef TEST
  printf("add_arg: \"%s\"\n", arg);
#endif

  if (!str)
    {
      fprintf(stderr, "add_arg: Out of memory.\n");
      exit(42);
    }
          
  if (___Argc >= argv_allocated)
    {
      if (!argv_allocated)
        {
          argv_allocated = ARGV_GROW_SIZE;
          ___Argv = (char**)malloc(argv_allocated * sizeof(char*));
        }
      else
        {
          argv_allocated += ARGV_GROW_SIZE;
          ___Argv = (char**)localRealloc(___Argv, argv_allocated * sizeof(char*));
        }
    }

  if (!___Argv)
    {
      fprintf(stderr, "add_arg: Out of memory.\n");
      exit(42);
    }
  ___Argv[___Argc++] = str;
}


/*****************************************************************************
 *
 * Do the work of expanding the wild card characters in a string.
 *
 *****************************************************************************/

static glob_t *wildcard_expand(char *s, int no_match_ok)
{
  glob_t *gb = 0;

#ifdef TEST
  printf("wildcard_expand: \"%s\"\n", s);
#endif

  if (wildcard_p(s, 0))
    {
      int status;
      char *mess;

      wildcards_seen++;

      if ((gb = (glob_t *) malloc(sizeof(glob_t))) == 0)
        {
          fprintf(stderr, "argv: Out of memory.\n");
          exit(42);
        }
      gb->nused = 0;
      gb->paths = 0;
      gb->nallocated = 0;
      status = arg_glob(s, GLOB_FLAGS, gb);

#ifdef TEST
      printf("globbing finds %d names\n", gb->nused);
#endif

      if (status == E_NOMATCH)
        {
          if (no_match_ok)
            status = 0;
          else
            mess = "No match\n";
        }
      else
      if (status == E_NOMEMORY)
        mess = "glob: out of memory\n";
      else
      if (status == E_IMPOSSIBLE)
        mess = "glob: can't handle wildcard drive names\n";
      else
      if (status == E_NODIR)
        mess = "glob: failed opening directory\n";

      if (status)
        {
          fprintf(stderr, mess);
          exit(42);
        }
    }
  return gb;
}







/******************************************************************************
 *
 * Exec a subprogram and returns it's output as an array of strings.
 * If in_string is non-zero only newlines separate fields.
 *
 *****************************************************************************/

static char **exec_prog(char *cmd_line, int in_string)
{
  int   buf_len   = ARG_GROW_SIZE;
  char *buf       = malloc(ARG_GROW_SIZE); /* the input line buffer */
  char *l         = buf;

  int    argc     = 0;
  char **argv     = 0;

  FILE  *fp;
  int    ch;

#ifdef TEST
  printf("exec_prog(\"%s\")\n", cmd_line);
#endif

  if ((fp = popen(cmd_line, "r")) == 0)
    {
      fprintf(stderr, "exec of subcommand failed...\n");
      perror("argv-handler");
      exit(42);
    }

  /*
   * Suck in all the output of the exec'd program.
   */

  while ((ch = fgetc(fp)) != EOF)
    {
      if (l >= (buf + buf_len - 1))
        {
          int offset = l - buf;
          buf = (char*)realloc(buf, buf_len += ARG_GROW_SIZE);
          l = buf + offset;
        }
      *l++ = ch;
    }
  *l++ = '\0';
  pclose(fp);

#ifdef TEST
  printf("Output from exec'd prog: |%s|\n", buf);
#endif

  /*
   * Parse it into fields... straight up guess the sizeof argv as
   * half the length of the buffer (ie, every second character a
   * space).
   */
  if (0 == (argv = malloc(((l - buf) / 2) * sizeof(char *))))
    {
      fprintf(stderr, "Out of memory executing backquote.\n");
      exit(42);
    }

  for (l = buf; *l;)
    {
      /*
       * Kill leading whitespace.
       */
      while ( NL_P(*l) || (!in_string && WS_P(*l)))
        l++;

      if (*l)
        {
          argv[argc++] = l;

          /*
           * Skip to next whitespace or end of string.
           */
          while (*l && !(NL_P(*l) || (!in_string && WS_P(*l))))
            l++;

          /*
           * Only terminate if not at end of input.
           */
          if (*l)
            *l++ = '\0';
        }
    }

  /*
   * Fix up the size of the returned array.
   */
  argv = localRealloc(argv, sizeof(char *) * (argc + 1));
  argv[argc] = 0;

  return argv;
}




/******************************************************************************
 *
 * Read a command file.. returns a null terminated array of fields..
 *
 *****************************************************************************/

static char **read_cmd_file(char *fname)
{
  struct stat statb;
  int    ifile;
  char  *buf;
  char **argv = 0;
  char  *l;
  int    r;
  int    argc = 0;

#ifdef TEST
  printf("read_cmd_file(%s)\n", fname);
#endif

  if ((r = stat(fname, &statb)) == 0)
    {
#ifdef TEST
      printf("read_cmd_file: opens: \"%s\"\n", fname);
#endif
      if ((ifile = open(fname, O_RDONLY | O_BINARY)) >= 0)
        {
          if ((buf = (char*)malloc(statb.st_size + 1)) != 0)
            {
              if (statb.st_size == read(ifile, buf, statb.st_size))
                {
                  buf[statb.st_size] = '\0';
                  close(ifile);
                }
              else
                {
                  fprintf(stderr, "read_cmd_file: read failed, size: %d\n",
                          statb.st_size);
                  exit(42);
                }
            }
          else
            {
              fprintf(stderr, "read_cmd_file: Out of memory\n");
              _exit(42);
            }
        }
      else
        {
          perror("read_cmd_file");
          exit(42);
        }
    }
  else
    {
      fprintf(stderr, "read_cmd_file: r: %d\n", r);
      perror("read_cmd_file: stat failed");
      exit(42);
    }


  /*
   * Construct the vector of fields... use a straight up guess on the
   * size of argv... then fix it up afterwards.
   */
  if (0 == (argv = (char**)malloc((statb.st_size / 2) * sizeof(char*))))
    {
      fprintf(stderr, "Out of memory reading command file.\n");
      exit(42);
    }

  for (l = buf; *l;)
    {
      char *start = 0;
      /*
       * Find the start of the argument.
       */
      while (NL_P(*l) || WS_P(*l))
        l++;

      if (*l)
        start = l;

      /*
       * Find the end of the argument.
       */
      for (;*l;l++)
        {
          if (*l == '\'' || *l == '\"' || *l == '`')
            {
              int quote_char = *l++;
              
              do
                {
                  if (!*l)
                    {
                      fprintf(stderr, "Unmatched %c. (reading %s)\n",
                              quote_char, fname);
                      exit(42);
                    }
                } while (*l++ != quote_char);
            }


          if (NL_P(*l) || WS_P(*l))
            {
              *l++ = '\0';
              break;
            }
        }
      if (start)
        argv[argc++] = start;
    }

  argv = localRealloc(argv, sizeof(char *) * (argc + 1));
  argv[argc] = 0;

  return argv;
}




/******************************************************************************
 *
 * A replacement for getenv() which IS case sensitive, the standard
 * WATCOM one is not.
 *
 *****************************************************************************/

static char *xgetenv(char *var_name)
{
  char **envp = environ;
  int  var_len = strlen(var_name);

#ifdef TEST
  printf("envp: %x, getenv(\"%s\") returns ", environ, var_name);
#endif

  while (envp && *envp)
    {
      char *endp = (*envp) + var_len;

      if (!strncmp(var_name, *envp, var_len))
        {
          if (*endp == '=')
            {
#ifdef TEST
              printf("%s\n", endp+1);
#endif
              return endp + 1;
            }
        }
      envp++;
    }
#ifdef TEST
  printf("(nil)\n");
#endif
  return 0;
}