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 TESTprintf("_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 TESTprintf("getenv(_argc): %x, \"%s\"\n", tmp, tmp);#endifnenv_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 TESTprintf("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 TESTprintf("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;}elsegoto 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;}elsegoto 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++;}elseif (*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;}elsegoto 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);}elseadd_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 TESTprintf("add_arg: \"%s\"\n", arg);#endifif (!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 TESTprintf("wildcard_expand: \"%s\"\n", s);#endifif (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 TESTprintf("globbing finds %d names\n", gb->nused);#endifif (status == E_NOMATCH){if (no_match_ok)status = 0;elsemess = "No match\n";}elseif (status == E_NOMEMORY)mess = "glob: out of memory\n";elseif (status == E_IMPOSSIBLE)mess = "glob: can't handle wildcard drive names\n";elseif (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 TESTprintf("exec_prog(\"%s\")\n", cmd_line);#endifif ((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 TESTprintf("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 TESTprintf("read_cmd_file(%s)\n", fname);#endifif ((r = stat(fname, &statb)) == 0){#ifdef TESTprintf("read_cmd_file: opens: \"%s\"\n", fname);#endifif ((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 TESTprintf("envp: %x, getenv(\"%s\") returns ", environ, var_name);#endifwhile (envp && *envp){char *endp = (*envp) + var_len;if (!strncmp(var_name, *envp, var_len)){if (*endp == '='){#ifdef TESTprintf("%s\n", endp+1);#endifreturn endp + 1;}}envp++;}#ifdef TESTprintf("(nil)\n");#endifreturn 0;}