(root)/
gawk-5.2.2/
vms/
vms_args.c
       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, 2022, 2023,
       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 3, 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
     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
     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
     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 *
     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
     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] */
     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  }