/* vms_args.c -- command line parsing, to emulate shell i/o redirection. [ Escape sequence parsing now suppressed. ] Copyright (C) 1991-1996, 1997, 2011, 2014 the Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * [.vms]vms_arg_fixup - emulate shell's command line processing: handle * stdio redirection, backslash escape sequences, and file wildcard * expansion. Should be called immediately upon image startup. * * Pat Rankin, Nov'89 * rankin@pactechdata.com * * nfile - create 'nfile' as 'stdout' (stream-lf format) * >>ofile - append to 'ofile' for 'stdout'; create it if necessary * >&efile - point 'stderr' (SYS$ERROR) at 'efile', but don't open * >$vfile - create 'vfile' as 'stdout', using rms attributes * appropriate for a standard text file (variable length * records with implied carriage control) * >+vfile - create 'vfile' as 'stdout' in binary mode (using * variable length records with implied carriage control) * 2>&1 - special case: direct error messages into output file * 1>&2 - special case: direct output data to error destination * <- - error: stdin/stdout closure not implemented * | anything - error; pipes not implemented * & - error; background execution not implemented * * any\Xany - convert 'X' as appropriate; \000 will not work as * intended since subsequent processing will misinterpret * * any*any - perform wildcard directory lookup to find file(s) * any%any - " " ('%' is vms wildcard for '?' [ie, /./]) * any?any - treat like 'any%any' unless no files match * *, %, ? - if no file(s) match, leave original value in arg list * * * Notes: a redirection operator can have optional white space between it * and its filename; the operator itself *must* be preceded by white * space so that it starts a separate argument. '<' is ambiguous * since "file" is a valid VMS file specification; leading '<' is * assumed to be stdin--use "\file" to override. '>$' is local * kludge to force stdout to be created with text file RMS attributes * instead of stream format; file sharing is disabled for stdout * regardless. Multiple instances of stdin or stdout or stderr are * treated as fatal errors rather than using the first or last. If a * wildcard file specification is detected, it is expanded into a list * of filenames which match; if there are no matches, the original * file-spec is left in the argument list rather than having it expand * into thin air. No attempt is made to identify and make $(var) * environment substitutions--must draw the line somewhere! * * Oct'91, gawk 2.13.3 * Open '<' with full sharing allowed, so that we can read batch logs * and other open files. Create record-format output ('>$') with read * sharing permited, so that others can read our output file to check * progess. For stream output ('>' or '>>'), sharing is disallowed * (for performance reasons). * * Sep'94, gawk 2.15.6 [pr] * Add '>+' to force binary mode output, to enable better control * for the user when the output destination is a mailbox or socket. * (ORS = "\r\n" for tcp/ip.) Contributed by Per Steinar Iversen. * * Jan'11, gawk 4.0.0 [pr] * If AWK_LIBRARY is undefined, define it to be SYS$LIBRARY: so * that the default value of AWKPATH ends with a valid directory. */ #include "awk.h" /* really "../awk.h" */ #include "vms.h" #include void v_add_arg(int, const char *); static char *skipblanks(const char *); static void vms_expand_wildcards(const char *); static U_Long vms_define(const char *, const char *); static char *t_strstr(const char *, const char *); #define strstr t_strstr /* strstr() missing from vaxcrtl for V4.x */ static int v_argc, v_argz = 0; static char **v_argv; /* vms_arg_fixup() - scan argv[] for i/o redirection and wildcards and also */ /* rebuild it with those removed or expanded, respectively */ void vms_arg_fixup( int *pargc, char ***pargv ) { const char *f_in, *f_out, *f_err, *out_mode, *rms_rfm, *rms_shr, *rms_mrs; char **argv = *pargv; int i, argc = *pargc; int err_to_out_redirect = 0, out_to_err_redirect = 0; char * shell; int using_shell; /* make sure AWK_LIBRARY has a value */ if (!getenv("AWK_LIBRARY")) vms_define("AWK_LIBRARY", "SYS$LIBRARY:"); /* Check if running under a shell instead of DCL */ using_shell = 1; shell = getenv("SHELL"); if (shell != NULL) { if (strcmp(shell, "DCL") == 0) { using_shell = 0; } } else { using_shell = 0; } if (using_shell) { return; } #ifdef CHECK_DECSHELL /* don't define this if linking with DECC$SHR */ if (shell$is_shell()) return; /* don't do anything if we're running DEC/Shell */ #endif #ifndef NO_DCL_CMD for (i = 1; i < argc ; i++) /* check for dash or other non-VMS args */ if (strchr("->\\|", *argv[i])) break; /* found => (i < argc) */ if (i >= argc && (v_argc = vms_gawk()) > 0) { /* vms_gawk => dcl_parse */ /* if we successfully parsed the command, replace original argv[] */ argc = v_argc, argv = v_argv; v_argz = v_argc = 0, v_argv = NULL; } #endif v_add_arg(v_argc = 0, argv[0]); /* store arg #0 (image name) */ f_in = f_out = f_err = NULL; /* stdio setup (no filenames yet) */ out_mode = "w"; /* default access for stdout */ rms_rfm = "rfm=stmlf"; /* stream_LF format */ rms_shr = "shr=nil"; /* no sharing (for '>' output file) */ rms_mrs = "mrs=0"; /* no maximum record size */ for (i = 1; i < argc; i++) { char *p, *fn; int is_arg; is_arg = 0; /* current arg does not begin with dash */ p = argv[i]; /* current arg */ switch (*p) { case '<': /* stdin */ /*[should try to determine whether this is really a directory spec using <>; for now, force user to quote them with '\<']*/ if ( f_in ) { fatal("multiple specification of '<' for stdin"); } else if (*++p == '<') { /* '<<' is not supported */ fatal("'<<' not available for stdin"); } else { p = skipblanks(p); fn = (*p ? p : argv[++i]); /* use next arg if necessary */ if (i >= argc || *fn == '-') fatal("invalid i/o redirection, null filespec after '<'"); else f_in = fn; /* save filename for stdin */ } break; case '>': { /* stdout or stderr */ /*[vms-specific kludge '>$' added to force stdout to be created as record-oriented text file instead of in stream-lf format]*/ int is_out = 1; /* assume stdout */ if (*++p == '>') /* '>>' => append */ out_mode = "a", p++; else if (*p == '&') /* '>&' => stderr */ is_out = 0, p++; else if (*p == '$') /* '>$' => kludge for record format */ rms_rfm = "rfm=var", rms_shr = "shr=get,upi", rms_mrs = "mrs=32767", p++; else if (*p == '+') /* '>+' => kludge for binary output */ out_mode = "wb", rms_rfm = "rfm=var", rms_mrs = "mrs=32767", p++; else /* '>' => create */ {} /* use default values initialized prior to loop */ p = skipblanks(p); fn = (*p ? p : argv[++i]); /* use next arg if necessary */ if (i >= argc || *fn == '-') { fatal("invalid i/o redirection, null filespec after '>'"); } else if (is_out) { if (out_to_err_redirect) fatal("conflicting specifications for stdout"); else if (f_out) fatal("multiple specification of '>' for stdout"); else f_out = fn; /* save filename for stdout */ } else { if (err_to_out_redirect) fatal("conflicting specifications for stderr"); else if (f_err) fatal("multiple specification of '>&' for stderr"); else f_err = fn; /* save filename for stderr */ } } break; case '2': /* check for ``2>&1'' special case'' */ if (strcmp(p, "2>&1") != 0) goto ordinary_arg; else if (f_err || out_to_err_redirect) fatal("conflicting specifications for stderr"); else { err_to_out_redirect = 1; f_err = "SYS$OUTPUT:"; } break; case '1': /* check for ``1>&2'' special case'' */ if (strcmp(p, "1>&2") != 0) goto ordinary_arg; else if (f_out || err_to_out_redirect) fatal("conflicting specifications for stdout"); else { out_to_err_redirect = 1; /* f_out = "SYS$ERROR:"; */ } break; case '|': /* pipe */ /* command pipelines are not supported */ fatal("command pipes not available ('|' encountered)"); break; case '&': /* background */ /*[we could probably spawn or fork ourself--maybe someday]*/ if (*(p+1) == '\0' && i == argc - 1) { fatal("background tasks not available ('&' encountered)"); break; } else { /* fall through */ ; /*NOBREAK*/ } case '-': /* argument */ is_arg = 1; /*(=> skip wildcard check)*/ default: /* other (filespec assumed) */ ordinary_arg: /* process escape sequences or expand wildcards */ v_add_arg(++v_argc, p); /* include this arg */ p = strchr(p, '\\'); /* look for backslash */ if (p != NULL) { /* does it have escape sequence(s)? */ #if 0 /* disable escape parsing; it's now done elsewhere within gawk */ register int c; char *q = v_argv[v_argc] + (p - argv[i]); do { c = *p++; if (c == '\\') c = parse_escape(&p); *q++ = (c >= 0 ? (char)c : '\\'); } while (*p != '\0'); *q = '\0'; #endif /*0*/ } else if (!is_arg && strchr(v_argv[v_argc], '=') == NULL) { vms_expand_wildcards(v_argv[v_argc]); } break; } /* end switch */ } /* loop */ /* * Now process any/all I/O options encountered above. */ /* must do stderr first, or vaxcrtl init might not see it */ /*[ catch 22: we'll also redirect errors encountered doing out ]*/ if (f_err) { /* define logical name but don't open file */ int len = strlen(f_err); if (len >= (sizeof "SYS$OUTPUT" - sizeof "") && strncasecmp(f_err, "SYS$OUTPUT:", len) == 0) err_to_out_redirect = 1; else (void) vms_define("SYS$ERROR", f_err); } /* do stdin before stdout, so if we bomb we won't make empty output file */ if (f_in) { /* [re]open file and define logical name */ if (freopen(f_in, "r", stdin, "ctx=rec", "shr=get,put,del,upd", "mrs=32767", "mbc=32", "mbf=2")) (void) vms_define("SYS$INPUT", f_in); else fatal("<%s (%s)", f_in, strerror(errno)); } if (f_out) { if (freopen(f_out, out_mode, stdout, rms_rfm, rms_shr, rms_mrs, "rat=cr", "mbc=32", "mbf=2")) (void) vms_define("SYS$OUTPUT", f_out); else fatal(">%s%s (%s)", (*out_mode == 'a' ? ">" : ""), f_out, strerror(errno)); } if (err_to_out_redirect) { /* special case for ``2>&1'' construct */ (void) dup2(1, 2); /* make file 2 (stderr) share file 1 (stdout) */ (void) vms_define("SYS$ERROR", "SYS$OUTPUT:"); } else if (out_to_err_redirect) { /* ``1>&2'' */ (void) dup2(2, 1); /* make file 1 (stdout) share file 2 (stderr) */ (void) vms_define("SYS$OUTPUT", "SYS$ERROR:"); } #ifndef NO_DCL_CMD /* if we replaced argv[] with our own, we can release it now */ if (argv != *pargv) free((void *)argv), argv = NULL; #endif *pargc = ++v_argc; /* increment to account for argv[0] */ *pargv = v_argv; return; } /* vms_expand_wildcards() - check a string for wildcard punctuation; */ /* if it has any, attempt a directory lookup */ /* and store resulting name(s) in argv array */ static void vms_expand_wildcards( const char *prospective_filespec ) { char *p, spec_buf[255+1], res_buf[255+1]; Dsc spec, result; void *context; register int len = strlen(prospective_filespec); if (len >= sizeof spec_buf) return; /* can't be valid--or at least we can't handle it */ strcpy(spec_buf, prospective_filespec); /* copy the arg */ p = strchr(spec_buf, '?'); if (p != NULL) /* change '?' single-char wildcard to '%' */ do *p++ = '%', p = strchr(p, '?'); while (p != NULL); else if (strchr(spec_buf, '*') == strchr(spec_buf, '%') /* => both NULL */ && strstr(spec_buf, "...") == NULL) return; /* no wildcards present; don't attempt file lookup */ spec.len = len, spec.adr = spec_buf; result.len = sizeof res_buf - 1, result.adr = res_buf; /* The filespec is already in v_argv[v_argc]; if we fail to match anything, we'll just leave it there (unlike most shells, where it would evaporate). */ len = -1; /* overload 'len' with flag value */ context = NULL; /* init */ while (vmswork(LIB$FIND_FILE(&spec, &result, &context))) { for (len = sizeof(res_buf)-1; len > 0 && res_buf[len-1] == ' '; len--) ; res_buf[len] = '\0'; /* terminate after discarding trailing blanks */ v_add_arg(v_argc++, strdup(res_buf)); /* store result */ } (void)LIB$FIND_FILE_END(&context); if (len >= 0) /* (still -1 => never entered loop) */ --v_argc; /* undo final post-increment */ return; } /* v_add_arg() - store string pointer in v_argv[]; expand array if necessary */ void v_add_arg( int idx, const char *val ) { #ifdef DEBUG_VMS fprintf(stderr, "v_add_arg: v_argv[%d] ", idx); #endif if (idx + 1 >= v_argz) { /* 'v_argz' is the current size of v_argv[] */ int old_size = v_argz; v_argz = idx + 10; /* increment by arbitrary amount */ if (old_size == 0) v_argv = (char **)malloc((unsigned)(v_argz * sizeof(char **))); else v_argv = (char **)realloc((char *)v_argv, (unsigned)(v_argz * sizeof(char **))); if (v_argv == NULL) { /* error */ fatal("%s: %s: can't allocate memory (%s)", "vms_args", "v_argv", strerror(errno)); } else { while (old_size < v_argz) v_argv[old_size++] = NULL; } } v_argv[idx] = (char *)val; #ifdef DEBUG_VMS fprintf(stderr, "= \"%s\"\n", val); #endif } /* skipblanks() - return a pointer to the first non-blank in the string */ static char * skipblanks( const char *ptr ) { if (ptr) while (*ptr == ' ' || *ptr == '\t') ptr++; return (char *)ptr; } /* vms_define() - assign a value to a logical name [define/process/user_mode] */ static U_Long vms_define( const char *log_name, const char *trans_val ) { Dsc log_dsc; static Descrip(lnmtable,"LNM$PROCESS_TABLE"); static U_Long attr = LNM$M_CONFINE; static Itm itemlist[] = { {0,LNM$_STRING,0,0}, {0,0} }; static unsigned char acmode = PSL$C_USER; unsigned len = strlen(log_name); /* avoid "define SYS$OUTPUT sys$output:" for redundant ">sys$output:" */ if (strncasecmp(log_name, trans_val, len) == 0 && (trans_val[len] == '\0' || trans_val[len] == ':')) return 0; log_dsc.adr = (char *)log_name; log_dsc.len = len; itemlist[0].buffer = (char *)trans_val; itemlist[0].len = strlen(trans_val); return SYS$CRELNM(&attr, &lnmtable, &log_dsc, &acmode, itemlist); } /* t_strstr -- strstr() substitute; search 'str' for 'sub' */ /* [strstr() was not present in VAXCRTL prior to VMS V5.0] */ static char *t_strstr ( const char *str, const char *sub ) { register const char *s0, *s1, *s2; /* special case: empty substring */ if (!*sub) return (char *)str; /* brute force method */ for (s0 = s1 = str; *s1; s1 = ++s0) { s2 = sub; while (*s1++ == *s2++) if (!*s2) return (char *)s0; /* full match */ } return (char *)0; /* not found */ }