1 /* vms_args.c -- command line parsing, to emulate shell i/o redirection.
2 [ Escape sequence parsing now suppressed. ]
3
4 Copyright (C) 1991-1996, 1997, 2011, 2014, 2016
5 the Free Software Foundation, Inc.
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software Foundation,
19 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
20
21 /*
22 * [.vms]vms_arg_fixup - emulate shell's command line processing: handle
23 * stdio redirection, backslash escape sequences, and file wildcard
24 * expansion. Should be called immediately upon image startup.
25 *
26 * Pat Rankin, Nov'89
27 * rankin@pactechdata.com
28 *
29 * <ifile - open 'ifile' (readonly) as 'stdin'
30 * >nfile - create 'nfile' as 'stdout' (stream-lf format)
31 * >>ofile - append to 'ofile' for 'stdout'; create it if necessary
32 * >&efile - point 'stderr' (SYS$ERROR) at 'efile', but don't open
33 * >$vfile - create 'vfile' as 'stdout', using rms attributes
34 * appropriate for a standard text file (variable length
35 * records with implied carriage control)
36 * >+vfile - create 'vfile' as 'stdout' in binary mode (using
37 * variable length records with implied carriage control)
38 * 2>&1 - special case: direct error messages into output file
39 * 1>&2 - special case: direct output data to error destination
40 * <<sentinal - error; reading stdin until 'sentinal' not supported
41 * <-, >- - error: stdin/stdout closure not implemented
42 * | anything - error; pipes not implemented
43 * & <end-of-line> - error; background execution not implemented
44 *
45 * any\Xany - convert 'X' as appropriate; \000 will not work as
46 * intended since subsequent processing will misinterpret
47 *
48 * any*any - perform wildcard directory lookup to find file(s)
49 * any%any - " " ('%' is vms wildcard for '?' [ie, /./])
50 * any?any - treat like 'any%any' unless no files match
51 * *, %, ? - if no file(s) match, leave original value in arg list
52 *
53 *
54 * Notes: a redirection operator can have optional white space between it
55 * and its filename; the operator itself *must* be preceded by white
56 * space so that it starts a separate argument. '<' is ambiguous
57 * since "<dir>file" is a valid VMS file specification; leading '<' is
58 * assumed to be stdin--use "\<dir>file" to override. '>$' is local
59 * kludge to force stdout to be created with text file RMS attributes
60 * instead of stream format; file sharing is disabled for stdout
61 * regardless. Multiple instances of stdin or stdout or stderr are
62 * treated as fatal errors rather than using the first or last. If a
63 * wildcard file specification is detected, it is expanded into a list
64 * of filenames which match; if there are no matches, the original
65 * file-spec is left in the argument list rather than having it expand
66 * into thin air. No attempt is made to identify and make $(var)
67 * environment substitutions--must draw the line somewhere!
68 *
69 * Oct'91, gawk 2.13.3
70 * Open '<' with full sharing allowed, so that we can read batch logs
71 * and other open files. Create record-format output ('>$') with read
72 * sharing permited, so that others can read our output file to check
73 * progess. For stream output ('>' or '>>'), sharing is disallowed
74 * (for performance reasons).
75 *
76 * Sep'94, gawk 2.15.6 [pr]
77 * Add '>+' to force binary mode output, to enable better control
78 * for the user when the output destination is a mailbox or socket.
79 * (ORS = "\r\n" for tcp/ip.) Contributed by Per Steinar Iversen.
80 *
81 * Jan'11, gawk 4.0.0 [pr]
82 * If AWK_LIBRARY is undefined, define it to be SYS$LIBRARY: so
83 * that the default value of AWKPATH ends with a valid directory.
84 */
85
86 #include "awk.h" /* really "../awk.h" */
87 #include "vms.h"
88 #include <lnmdef.h>
89
90 void v_add_arg(int, const char *);
91 static char *skipblanks(const char *);
92 static void vms_expand_wildcards(const char *);
93 static U_Long vms_define(const char *, const char *);
94 static char *t_strstr(const char *, const char *);
95 #define strstr t_strstr /* strstr() missing from vaxcrtl for V4.x */
96
97 static int v_argc, v_argz = 0;
98 static char **v_argv;
99
100 /* vms_arg_fixup() - scan argv[] for i/o redirection and wildcards and also */
101 /* rebuild it with those removed or expanded, respectively */
102 void
vms_arg_fixup(int * pargc,char *** pargv)103 vms_arg_fixup( int *pargc, char ***pargv )
104 {
105 const char *f_in, *f_out, *f_err,
106 *out_mode, *rms_rfm, *rms_shr, *rms_mrs;
107 char **argv = *pargv;
108 int i, argc = *pargc;
109 int err_to_out_redirect = 0, out_to_err_redirect = 0;
110 char * shell;
111 int using_shell;
112
113 /* make sure AWK_LIBRARY has a value */
114 if (!getenv("AWK_LIBRARY"))
115 vms_define("AWK_LIBRARY", "SYS$LIBRARY:");
116
117 /* Check if running under a shell instead of DCL */
118 using_shell = 1;
119 shell = getenv("SHELL");
120 if (shell != NULL) {
121 if (strcmp(shell, "DCL") == 0) {
122 using_shell = 0;
123 }
124 } else {
125 using_shell = 0;
126 }
127 if (using_shell) {
128 return;
129 }
130 #ifdef CHECK_DECSHELL /* don't define this if linking with DECC$SHR */
131 if (shell$is_shell())
132 return; /* don't do anything if we're running DEC/Shell */
133 #endif
134 #ifndef NO_DCL_CMD
135 for (i = 1; i < argc ; i++) /* check for dash or other non-VMS args */
136 if (strchr("->\\|", *argv[i])) break; /* found => (i < argc) */
137 if (i >= argc && (v_argc = vms_gawk()) > 0) { /* vms_gawk => dcl_parse */
138 /* if we successfully parsed the command, replace original argv[] */
139 argc = v_argc, argv = v_argv;
140 v_argz = v_argc = 0, v_argv = NULL;
141 }
142 #endif
143 v_add_arg(v_argc = 0, argv[0]); /* store arg #0 (image name) */
144
145 f_in = f_out = f_err = NULL; /* stdio setup (no filenames yet) */
146 out_mode = "w"; /* default access for stdout */
147 rms_rfm = "rfm=stmlf"; /* stream_LF format */
148 rms_shr = "shr=nil"; /* no sharing (for '>' output file) */
149 rms_mrs = "mrs=0"; /* no maximum record size */
150
151 for (i = 1; i < argc; i++) {
152 char *p, *fn;
153 int is_arg;
154
155 is_arg = 0; /* current arg does not begin with dash */
156 p = argv[i]; /* current arg */
157 switch (*p) {
158 case '<': /* stdin */
159 /*[should try to determine whether this is really a directory
160 spec using <>; for now, force user to quote them with '\<']*/
161 if ( f_in ) {
162 fatal("multiple specification of '<' for stdin");
163 } else if (*++p == '<') { /* '<<' is not supported */
164 fatal("'<<' not available for stdin");
165 } else {
166 p = skipblanks(p);
167 fn = (*p ? p : argv[++i]); /* use next arg if necessary */
168 if (i >= argc || *fn == '-')
169 fatal("invalid i/o redirection, null filespec after '<'");
170 else
171 f_in = fn; /* save filename for stdin */
172 }
173 break;
174 case '>': { /* stdout or stderr */
175 /*[vms-specific kludge '>$' added to force stdout to be created
176 as record-oriented text file instead of in stream-lf format]*/
177 int is_out = 1; /* assume stdout */
178 if (*++p == '>') /* '>>' => append */
179 out_mode = "a", p++;
180 else if (*p == '&') /* '>&' => stderr */
181 is_out = 0, p++;
182 else if (*p == '$') /* '>$' => kludge for record format */
183 rms_rfm = "rfm=var", rms_shr = "shr=get,upi",
184 rms_mrs = "mrs=32767", p++;
185 else if (*p == '+') /* '>+' => kludge for binary output */
186 out_mode = "wb", rms_rfm = "rfm=var",
187 rms_mrs = "mrs=32767", p++;
188 else /* '>' => create */
189 {} /* use default values initialized prior to loop */
190 p = skipblanks(p);
191 fn = (*p ? p : argv[++i]); /* use next arg if necessary */
192 if (i >= argc || *fn == '-') {
193 fatal("invalid i/o redirection, null filespec after '>'");
194 } else if (is_out) {
195 if (out_to_err_redirect)
196 fatal("conflicting specifications for stdout");
197 else if (f_out)
198 fatal("multiple specification of '>' for stdout");
199 else
200 f_out = fn; /* save filename for stdout */
201 } else {
202 if (err_to_out_redirect)
203 fatal("conflicting specifications for stderr");
204 else if (f_err)
205 fatal("multiple specification of '>&' for stderr");
206 else
207 f_err = fn; /* save filename for stderr */
208 }
209 } break;
210 case '2': /* check for ``2>&1'' special case'' */
211 if (strcmp(p, "2>&1") != 0)
212 goto ordinary_arg;
213 else if (f_err || out_to_err_redirect)
214 fatal("conflicting specifications for stderr");
215 else {
216 err_to_out_redirect = 1;
217 f_err = "SYS$OUTPUT:";
218 } break;
219 case '1': /* check for ``1>&2'' special case'' */
220 if (strcmp(p, "1>&2") != 0)
221 goto ordinary_arg;
222 else if (f_out || err_to_out_redirect)
223 fatal("conflicting specifications for stdout");
224 else {
225 out_to_err_redirect = 1;
226 /* f_out = "SYS$ERROR:"; */
227 } break;
228 case '|': /* pipe */
229 /* command pipelines are not supported */
230 fatal("command pipes not available ('|' encountered)");
231 break;
232 case '&': /* background */
233 /*[we could probably spawn or fork ourself--maybe someday]*/
234 if (*(p+1) == '\0' && i == argc - 1) {
235 fatal("background tasks not available ('&' encountered)");
236 break;
237 } else { /* fall through */
238 ; /*NOBREAK*/
239 }
240 case '-': /* argument */
241 is_arg = 1; /*(=> skip wildcard check)*/
242 default: /* other (filespec assumed) */
243 ordinary_arg:
244 /* process escape sequences or expand wildcards */
245 v_add_arg(++v_argc, p); /* include this arg */
246 p = strchr(p, '\\'); /* look for backslash */
247 if (p != NULL) { /* does it have escape sequence(s)? */
248 #if 0 /* disable escape parsing; it's now done elsewhere within gawk */
249 register int c;
250 char *q = v_argv[v_argc] + (p - argv[i]);
251 do {
252 c = *p++;
253 if (c == '\\')
254 c = parse_escape(&p);
255 *q++ = (c >= 0 ? (char)c : '\\');
256 } while (*p != '\0');
257 *q = '\0';
258 #endif /*0*/
259 } else if (!is_arg && strchr(v_argv[v_argc], '=') == NULL) {
260 vms_expand_wildcards(v_argv[v_argc]);
261 }
262 break;
263 } /* end switch */
264 } /* loop */
265
266 /*
267 * Now process any/all I/O options encountered above.
268 */
269
270 /* must do stderr first, or vaxcrtl init might not see it */
271 /*[ catch 22: we'll also redirect errors encountered doing <in or >out ]*/
272 if (f_err) { /* define logical name but don't open file */
273 int len = strlen(f_err);
274 if (len >= (sizeof "SYS$OUTPUT" - sizeof "")
275 && strncasecmp(f_err, "SYS$OUTPUT:", len) == 0)
276 err_to_out_redirect = 1;
277 else
278 (void) vms_define("SYS$ERROR", f_err);
279 }
280 /* do stdin before stdout, so if we bomb we won't make empty output file */
281 if (f_in) { /* [re]open file and define logical name */
282 if (freopen(f_in, "r", stdin,
283 "ctx=rec", "shr=get,put,del,upd",
284 "mrs=32767", "mbc=32", "mbf=2"))
285 (void) vms_define("SYS$INPUT", f_in);
286 else
287 fatal("<%s (%s)", f_in, strerror(errno));
288 }
289 if (f_out) {
290 if (freopen(f_out, out_mode, stdout,
291 rms_rfm, rms_shr, rms_mrs,
292 "rat=cr", "mbc=32", "mbf=2"))
293 (void) vms_define("SYS$OUTPUT", f_out);
294 else
295 fatal(">%s%s (%s)", (*out_mode == 'a' ? ">" : ""),
296 f_out, strerror(errno));
297 }
298 if (err_to_out_redirect) { /* special case for ``2>&1'' construct */
299 (void) dup2(1, 2); /* make file 2 (stderr) share file 1 (stdout) */
300 (void) vms_define("SYS$ERROR", "SYS$OUTPUT:");
301 } else if (out_to_err_redirect) { /* ``1>&2'' */
302 (void) dup2(2, 1); /* make file 1 (stdout) share file 2 (stderr) */
303 (void) vms_define("SYS$OUTPUT", "SYS$ERROR:");
304 }
305
306 #ifndef NO_DCL_CMD
307 /* if we replaced argv[] with our own, we can release it now */
308 if (argv != *pargv)
309 free((void *)argv), argv = NULL;
310 #endif
311 *pargc = ++v_argc; /* increment to account for argv[0] */
312 *pargv = v_argv;
313 return;
314 }
315
316 /* vms_expand_wildcards() - check a string for wildcard punctuation; */
317 /* if it has any, attempt a directory lookup */
318 /* and store resulting name(s) in argv array */
319 static void
vms_expand_wildcards(const char * prospective_filespec)320 vms_expand_wildcards( const char *prospective_filespec )
321 {
322 char *p, spec_buf[255+1], res_buf[255+1];
323 struct dsc$descriptor_s spec, result;
324 void *context;
325 register int len = strlen(prospective_filespec);
326
327 if (len >= sizeof spec_buf)
328 return; /* can't be valid--or at least we can't handle it */
329 strcpy(spec_buf, prospective_filespec); /* copy the arg */
330 p = strchr(spec_buf, '?');
331 if (p != NULL) /* change '?' single-char wildcard to '%' */
332 do *p++ = '%', p = strchr(p, '?');
333 while (p != NULL);
334 else if (strchr(spec_buf, '*') == strchr(spec_buf, '%') /* => both NULL */
335 && strstr(spec_buf, "...") == NULL)
336 return; /* no wildcards present; don't attempt file lookup */
337 spec.dsc$w_length = len;
338 spec.dsc$a_pointer = spec_buf;
339 spec.dsc$b_dtype = DSC$K_DTYPE_T;
340 spec.dsc$b_class = DSC$K_CLASS_S;
341 result.dsc$w_length = sizeof res_buf - 1;
342 result.dsc$a_pointer = res_buf;
343 result.dsc$b_dtype = DSC$K_DTYPE_T;
344 result.dsc$b_class = DSC$K_CLASS_S;
345
346 /* The filespec is already in v_argv[v_argc]; if we fail to match anything,
347 we'll just leave it there (unlike most shells, where it would evaporate).
348 */
349 len = -1; /* overload 'len' with flag value */
350 context = NULL; /* init */
351 while (vmswork(LIB$FIND_FILE(&spec, &result, &context))) {
352 for (len = sizeof(res_buf)-1; len > 0 && res_buf[len-1] == ' '; len--) ;
353 res_buf[len] = '\0'; /* terminate after discarding trailing blanks */
354 v_add_arg(v_argc++, strdup(res_buf)); /* store result */
355 }
356 (void)LIB$FIND_FILE_END(&context);
357 if (len >= 0) /* (still -1 => never entered loop) */
358 --v_argc; /* undo final post-increment */
359 return;
360 }
361
362 /* v_add_arg() - store string pointer in v_argv[]; expand array if necessary */
363 void
v_add_arg(int idx,const char * val)364 v_add_arg( int idx, const char *val )
365 {
366 #ifdef DEBUG_VMS
367 fprintf(stderr, "v_add_arg: v_argv[%d] ", idx);
368 #endif
369 if (idx + 1 >= v_argz) { /* 'v_argz' is the current size of v_argv[] */
370 int old_size = v_argz;
371
372 v_argz = idx + 10; /* increment by arbitrary amount */
373 if (old_size == 0)
374 v_argv = (char **)malloc((unsigned)(v_argz * sizeof(char **)));
375 else
376 v_argv = (char **)realloc((char *)v_argv,
377 (unsigned)(v_argz * sizeof(char **)));
378 if (v_argv == NULL) { /* error */
379 fatal("%s: %s: can't allocate memory (%s)", "vms_args",
380 "v_argv", strerror(errno));
381 } else {
382 while (old_size < v_argz) v_argv[old_size++] = NULL;
383 }
384 }
385 v_argv[idx] = (char *)val;
386 #ifdef DEBUG_VMS
387 fprintf(stderr, "= \"%s\"\n", val);
388 #endif
389 }
390
391 /* skipblanks() - return a pointer to the first non-blank in the string */
392 static char *
skipblanks(const char * ptr)393 skipblanks( const char *ptr )
394 {
395 if (ptr)
396 while (*ptr == ' ' || *ptr == '\t')
397 ptr++;
398 return (char *)ptr;
399 }
400
401 /* vms_define() - assign a value to a logical name [define/process/user_mode] */
402 static U_Long
vms_define(const char * log_name,const char * trans_val)403 vms_define( const char *log_name, const char *trans_val )
404 {
405 struct dsc$descriptor_s log_dsc;
406 static Descrip(lnmtable,"LNM$PROCESS_TABLE");
407 static U_Long attr = LNM$M_CONFINE;
408 static Itm itemlist[] = { {0,LNM$_STRING,0,0}, {0,0} };
409 static unsigned char acmode = PSL$C_USER;
410 unsigned len = strlen(log_name);
411
412 /* avoid "define SYS$OUTPUT sys$output:" for redundant ">sys$output:" */
413 if (strncasecmp(log_name, trans_val, len) == 0
414 && (trans_val[len] == '\0' || trans_val[len] == ':'))
415 return 0;
416
417 log_dsc.dsc$a_pointer = (char *)log_name;
418 log_dsc.dsc$w_length = len;
419 log_dsc.dsc$b_dtype = DSC$K_DTYPE_T;
420 log_dsc.dsc$b_class = DSC$K_CLASS_S;
421 itemlist[0].buffer = (char *)trans_val;
422 itemlist[0].len = strlen(trans_val);
423 return SYS$CRELNM(&attr, &lnmtable, &log_dsc, &acmode, itemlist);
424 }
425
426 /* t_strstr -- strstr() substitute; search 'str' for 'sub' */
427 /* [strstr() was not present in VAXCRTL prior to VMS V5.0] */
t_strstr(const char * str,const char * sub)428 static char *t_strstr ( const char *str, const char *sub )
429 {
430 register const char *s0, *s1, *s2;
431
432 /* special case: empty substring */
433 if (!*sub) return (char *)str;
434
435 /* brute force method */
436 for (s0 = s1 = str; *s1; s1 = ++s0) {
437 s2 = sub;
438 while (*s1++ == *s2++)
439 if (!*s2) return (char *)s0; /* full match */
440 }
441 return (char *)0; /* not found */
442 }
443