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