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