/* * This replace for the standard WATCOM supplied argv processing * function __Init_Argv. * * Handles $... expands into the named environment variabe * accepts $() or ${} to * allow for trailing characters which would * normally be a part of the name. The value * of is substituted into the string. * * Handles @cmd-file-name read arguments.. any number per line, any * number of lines from .. lines * read from the file get glob'd too! * * * Handles "" strings $ 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 #include #include #include #include #include #include #include #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; }