| /* --------------- Moved here from job.c --------------- | 
 |    This file must be #included in job.c, as it accesses static functions. | 
 |  | 
 | Copyright (C) 1996-2022 Free Software Foundation, Inc. | 
 | This file is part of GNU Make. | 
 |  | 
 | GNU Make 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 3 of the License, or (at your option) any later | 
 | version. | 
 |  | 
 | GNU Make 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, see <http://www.gnu.org/licenses/>.  */ | 
 |  | 
 | #include <string.h> | 
 | #include <descrip.h> | 
 | #include <clidef.h> | 
 |  | 
 | /* TODO - VMS specific header file conditionally included in makeint.h */ | 
 |  | 
 | #include <stsdef.h> | 
 | #include <ssdef.h> | 
 | void | 
 | decc$exit (int status); | 
 |  | 
 | /* Lowest legal non-success VMS exit code is 8 */ | 
 | /* GNU make only defines codes 0, 1, 2 */ | 
 | /* So assume any exit code > 8 is a VMS exit code */ | 
 |  | 
 | #ifndef MAX_EXPECTED_EXIT_CODE | 
 | # define MAX_EXPECTED_EXIT_CODE 7 | 
 | #endif | 
 |  | 
 |  | 
 | #if __CRTL_VER >= 70302000 && !defined(__VAX) | 
 | # define MAX_DCL_LINE_LENGTH 4095 | 
 | # define MAX_DCL_CMD_LINE_LENGTH 8192 | 
 | #else | 
 | # define MAX_DCL_LINE_LENGTH 255 | 
 | # define MAX_DCL_CMD_LINE_LENGTH 1024 | 
 | #endif | 
 | #define MAX_DCL_TOKEN_LENGTH 255 | 
 | #define MAX_DCL_TOKENS 127 | 
 |  | 
 | enum auto_pipe { nopipe, add_pipe, dcl_pipe }; | 
 |  | 
 | char *vmsify (char *name, int type); | 
 |  | 
 | static int vms_jobsefnmask = 0; | 
 |  | 
 | /* returns whether path is assumed to be a unix like shell. */ | 
 | int | 
 | _is_unixy_shell (const char *path) | 
 | { | 
 |   return vms_gnv_shell; | 
 | } | 
 |  | 
 | #define VMS_GETMSG_MAX 256 | 
 | static char vms_strsignal_text[VMS_GETMSG_MAX + 2]; | 
 |  | 
 | char * | 
 | vms_strsignal (int status) | 
 | { | 
 |   if (status <= MAX_EXPECTED_EXIT_CODE) | 
 |     sprintf (vms_strsignal_text, "lib$spawn returned %x", status); | 
 |   else | 
 |     { | 
 |       int vms_status; | 
 |       unsigned short * msg_len; | 
 |       unsigned char out[4]; | 
 |       vms_status = SYS$GETMSG (status, &msg_len, | 
 |                                vms_strsignal_text, 7, *out); | 
 |     } | 
 |  | 
 |   return vms_strsignal_text; | 
 | } | 
 |  | 
 |  | 
 | /* Wait for nchildren children to terminate */ | 
 | static void | 
 | vmsWaitForChildren (int *status) | 
 | { | 
 |   while (1) | 
 |     { | 
 |       if (!vms_jobsefnmask) | 
 |         { | 
 |           *status = 0; | 
 |           return; | 
 |         } | 
 |  | 
 |       *status = sys$wflor (32, vms_jobsefnmask); | 
 |     } | 
 |   return; | 
 | } | 
 |  | 
 | static int ctrlYPressed= 0; | 
 | /* This is called at main or AST level. It is at AST level for DONTWAITFORCHILD | 
 |    and at main level otherwise. In any case it is called when a child process | 
 |    terminated. At AST level it won't get interrupted by anything except a | 
 |    inner mode level AST. | 
 | */ | 
 | static int | 
 | vmsHandleChildTerm (struct childbase *cbase) | 
 | { | 
 |   struct child *child = (struct child*)cbase; | 
 |   struct child *lastc, *c; | 
 |   int child_failed; | 
 |   int exit_code; | 
 |  | 
 |   /* The child efn is 0 when a built-in or null command is executed | 
 |      successfully with out actually creating a child. | 
 |   */ | 
 |   if (child->efn > 0) | 
 |   { | 
 |     vms_jobsefnmask &= ~(1 << (child->efn - 32)); | 
 |  | 
 |     lib$free_ef (&child->efn); | 
 |   } | 
 |   if (child->comname) | 
 |     { | 
 |       if (!ISDB (DB_JOBS) && !ctrlYPressed) | 
 |         unlink (child->comname); | 
 |       free (child->comname); | 
 |     } | 
 |  | 
 |   (void) sigblock (fatal_signal_mask); | 
 |  | 
 |   /* First check to see if this is a POSIX exit status and handle */ | 
 |   if ((child->cstatus & VMS_POSIX_EXIT_MASK) == VMS_POSIX_EXIT_MASK) | 
 |     { | 
 |       exit_code = (child->cstatus >> 3) & 255; | 
 |       if (exit_code != MAKE_SUCCESS) | 
 |         child_failed = 1; | 
 |     } | 
 |   else | 
 |     { | 
 |       child_failed = !$VMS_STATUS_SUCCESS (child->cstatus); | 
 |       if (child_failed) | 
 |         exit_code = child->cstatus; | 
 |     } | 
 |  | 
 |   /* Search for a child matching the deceased one.  */ | 
 |   lastc = 0; | 
 | #if defined(RECURSIVEJOBS) | 
 |   /* I've had problems with recursive stuff and process handling */ | 
 |   for (c = children; c != 0 && c != child; lastc = c, c = c->next) | 
 |     ; | 
 | #else | 
 |   c = child; | 
 | #endif | 
 |  | 
 |   if ($VMS_STATUS_SUCCESS (child->vms_launch_status)) | 
 |     { | 
 |       /* Convert VMS success status to 0 for UNIX code to be happy */ | 
 |       child->vms_launch_status = 0; | 
 |     } | 
 |  | 
 |   /* Set the state flag to say the commands have finished.  */ | 
 |   c->file->command_state = cs_finished; | 
 |   notice_finished_file (c->file); | 
 |  | 
 |   (void) sigsetmask (sigblock (0) & ~(fatal_signal_mask)); | 
 |  | 
 |   return 1; | 
 | } | 
 |  | 
 | /* VMS: | 
 |    Spawn a process executing the command in ARGV and return its pid. */ | 
 |  | 
 | /* local helpers to make ctrl+c and ctrl+y working, see below */ | 
 | #include <iodef.h> | 
 | #include <libclidef.h> | 
 | #include <ssdef.h> | 
 |  | 
 | static int ctrlMask= LIB$M_CLI_CTRLY; | 
 | static int oldCtrlMask; | 
 | static int setupYAstTried= 0; | 
 | static unsigned short int chan= 0; | 
 |  | 
 | static void | 
 | reEnableAst(void) | 
 | { | 
 |   lib$enable_ctrl (&oldCtrlMask,0); | 
 | } | 
 |  | 
 | static int | 
 | astYHandler (void) | 
 | { | 
 |   struct child *c; | 
 |   for (c = children; c != 0; c = c->next) | 
 |     if (c->pid > 0) | 
 |       sys$delprc (&c->pid, 0, 0); | 
 |   ctrlYPressed= 1; | 
 |   kill (getpid(),SIGQUIT); | 
 |   return SS$_NORMAL; | 
 | } | 
 |  | 
 | static void | 
 | tryToSetupYAst(void) | 
 | { | 
 |   $DESCRIPTOR(inputDsc,"SYS$COMMAND"); | 
 |   int     status; | 
 |   struct { | 
 |     short int       status, count; | 
 |     int     dvi; | 
 |   } iosb; | 
 |   unsigned short int loc_chan; | 
 |  | 
 |   setupYAstTried++; | 
 |  | 
 |   if (chan) | 
 |     loc_chan= chan; | 
 |   else | 
 |     { | 
 |       status= sys$assign(&inputDsc,&loc_chan,0,0); | 
 |       if (!(status&SS$_NORMAL)) | 
 |         { | 
 |           lib$signal(status); | 
 |           return; | 
 |         } | 
 |     } | 
 |   status= sys$qiow (0, loc_chan, IO$_SETMODE|IO$M_CTRLYAST,&iosb,0,0, | 
 |                     astYHandler,0,0,0,0,0); | 
 |   if (status==SS$_NORMAL) | 
 |     status= iosb.status; | 
 |   if (status!=SS$_NORMAL) | 
 |     { | 
 |       if (!chan) | 
 |         sys$dassgn(loc_chan); | 
 |       if (status!=SS$_ILLIOFUNC && status!=SS$_NOPRIV) | 
 |         lib$signal(status); | 
 |       return; | 
 |     } | 
 |  | 
 |   /* called from AST handler ? */ | 
 |   if (setupYAstTried>1) | 
 |     return; | 
 |   if (atexit(reEnableAst)) | 
 |     fprintf (stderr, | 
 |              _("-warning, you may have to re-enable CTRL-Y handling from DCL.\n")); | 
 |   status= lib$disable_ctrl (&ctrlMask, &oldCtrlMask); | 
 |   if (!(status&SS$_NORMAL)) | 
 |     { | 
 |       lib$signal(status); | 
 |       return; | 
 |     } | 
 |   if (!chan) | 
 |     chan = loc_chan; | 
 | } | 
 |  | 
 | /* Check if a token is too long */ | 
 | #define INC_TOKEN_LEN_OR_RETURN(x) {token->length++; \ | 
 |   if (token->length >= MAX_DCL_TOKEN_LENGTH) \ | 
 |     { token->cmd_errno = ERANGE; return x; }} | 
 |  | 
 | #define INC_TOKEN_LEN_OR_BREAK {token->length++; \ | 
 |   if (token->length >= MAX_DCL_TOKEN_LENGTH) \ | 
 |     { token->cmd_errno = ERANGE; break; }} | 
 |  | 
 | #define ADD_TOKEN_LEN_OR_RETURN(add_len, x) {token->length += add_len; \ | 
 |   if (token->length >= MAX_DCL_TOKEN_LENGTH) \ | 
 |     { token->cmd_errno = ERANGE; return x; }} | 
 |  | 
 | /* Check if we are out of space for more tokens */ | 
 | #define V_NEXT_TOKEN { if (cmd_tkn_index < MAX_DCL_TOKENS) \ | 
 |   cmd_tokens[++cmd_tkn_index] = NULL; \ | 
 |   else { token.cmd_errno = E2BIG; break; } \ | 
 |   token.length = 0;} | 
 |  | 
 |  | 
 | #define UPDATE_TOKEN {cmd_tokens[cmd_tkn_index] = strdup(token.text); \ | 
 |   V_NEXT_TOKEN;} | 
 |  | 
 | #define EOS_ERROR(x) { if (*x == 0) { token->cmd_errno = ERANGE; break; }} | 
 |  | 
 | struct token_info | 
 |   { | 
 |     char *text;       /* Parsed text */ | 
 |     int length;       /* Length of parsed text */ | 
 |     char *src;        /* Pointer to source text */ | 
 |     int cmd_errno;    /* Error status of parse */ | 
 |     int use_cmd_file; /* Force use of a command file */ | 
 |   }; | 
 |  | 
 |  | 
 | /* Extract a Posix single quoted string from input line */ | 
 | static char * | 
 | posix_parse_sq (struct token_info *token) | 
 | { | 
 |   /* A Posix quoted string with no expansion unless in a string | 
 |      Unix simulation means no lexical functions present. | 
 |   */ | 
 |   char * q; | 
 |   char * p; | 
 |   q = token->text; | 
 |   p = token->src; | 
 |  | 
 |   *q++ = '"'; | 
 |   p++; | 
 |   INC_TOKEN_LEN_OR_RETURN (p); | 
 |  | 
 |   while (*p != '\'' && (token->length < MAX_DCL_TOKEN_LENGTH)) | 
 |     { | 
 |       EOS_ERROR (p); | 
 |       if (*p == '"') | 
 |         { | 
 |           /* Embedded double quotes need to be doubled */ | 
 |           *q++ = '"'; | 
 |           INC_TOKEN_LEN_OR_BREAK; | 
 |           *q = '"'; | 
 |         } | 
 |       else | 
 |         *q = *p; | 
 |  | 
 |       q++; | 
 |       p++; | 
 |       INC_TOKEN_LEN_OR_BREAK; | 
 |     } | 
 |   *q++ = '"'; | 
 |   p++; | 
 |   INC_TOKEN_LEN_OR_RETURN (p); | 
 |   *q = 0; | 
 |   return p; | 
 | } | 
 |  | 
 | /* Extract a Posix double quoted string from input line */ | 
 | static char * | 
 | posix_parse_dq (struct token_info *token) | 
 | { | 
 |   /* Unix mode:  Any embedded \" becomes doubled. | 
 |                  \t is tab, \\, \$ leading character stripped. | 
 |                  $ character replaced with \' unless escaped. | 
 |   */ | 
 |   char * q; | 
 |   char * p; | 
 |   q = token->text; | 
 |   p = token->src; | 
 |   *q++ = *p++; | 
 |   INC_TOKEN_LEN_OR_RETURN (p); | 
 |   while (*p != 0) | 
 |     { | 
 |       if (*p == '\\') | 
 |         { | 
 |           switch(p[1]) | 
 |             { | 
 |             case 't':     /* Convert tabs */ | 
 |               *q = '\t'; | 
 |               p++; | 
 |               break; | 
 |             case '\\':     /* Just remove leading backslash */ | 
 |             case '$': | 
 |               p++; | 
 |               *q = *p; | 
 |               break; | 
 |             case '"': | 
 |               p++; | 
 |               *q = *p; | 
 |               *q++ = '"'; | 
 |               INC_TOKEN_LEN_OR_BREAK; | 
 |             default:      /* Pass through unchanged */ | 
 |               *q++ = *p++; | 
 |               INC_TOKEN_LEN_OR_BREAK; | 
 |             } | 
 |           INC_TOKEN_LEN_OR_BREAK; | 
 |         } | 
 |       else if (*p == '$' && isalpha ((unsigned char) p[1])) | 
 |         { | 
 |           /* A symbol we should be able to substitute */ | 
 |           *q++ = '\''; | 
 |           INC_TOKEN_LEN_OR_BREAK; | 
 |           *q = '\''; | 
 |           INC_TOKEN_LEN_OR_BREAK; | 
 |           token->use_cmd_file = 1; | 
 |         } | 
 |       else | 
 |         { | 
 |           *q = *p; | 
 |           INC_TOKEN_LEN_OR_BREAK; | 
 |           if (*p == '"') | 
 |             { | 
 |               p++; | 
 |               q++; | 
 |               break; | 
 |             } | 
 |         } | 
 |       p++; | 
 |       q++; | 
 |     } | 
 |   *q = 0; | 
 |   return p; | 
 | } | 
 |  | 
 | /* Extract a VMS quoted string or substitution string from input line */ | 
 | static char * | 
 | vms_parse_quotes (struct token_info *token) | 
 | { | 
 |   /* VMS mode, the \' means that a symbol substitution is starting | 
 |      so while you might think you can just copy until the next | 
 |      \'.  Unfortunately the substitution can be a lexical function | 
 |      which can contain embedded strings and lexical functions. | 
 |      Messy, so both types need to be handled together. | 
 |   */ | 
 |   char * q; | 
 |   char * p; | 
 |   q = token->text; | 
 |   p = token->src; | 
 |   int parse_level[MAX_DCL_TOKENS + 1]; | 
 |   int nest = 0; | 
 |  | 
 |   parse_level[0] = *p; | 
 |   if (parse_level[0] == '\'') | 
 |     token->use_cmd_file = 1; | 
 |  | 
 |   *q++ = *p++; | 
 |   INC_TOKEN_LEN_OR_RETURN (p); | 
 |  | 
 |  | 
 |   /* Copy everything until after the next single quote at nest == 0 */ | 
 |   while (token->length < MAX_DCL_TOKEN_LENGTH) | 
 |     { | 
 |       EOS_ERROR (p); | 
 |       *q = *p; | 
 |       INC_TOKEN_LEN_OR_BREAK; | 
 |       if ((*p == parse_level[nest]) && (p[1] != '"')) | 
 |         { | 
 |           if (nest == 0) | 
 |             { | 
 |               *q++ = *p++; | 
 |               break; | 
 |             } | 
 |           nest--; | 
 |         } | 
 |       else | 
 |         { | 
 |           switch(*p) | 
 |             { | 
 |             case '\\': | 
 |               /* Handle continuation on to next line */ | 
 |               if (p[1] != '\n') | 
 |                 break; | 
 |               p++; | 
 |               p++; | 
 |               *q = *p; | 
 |               break; | 
 |             case '(': | 
 |               /* Parenthesis only in single quote level */ | 
 |               if (parse_level[nest] == '\'') | 
 |                 { | 
 |                   nest++; | 
 |                   parse_level[nest] == ')'; | 
 |                 } | 
 |               break; | 
 |             case '"': | 
 |               /* Double quotes only in parenthesis */ | 
 |               if (parse_level[nest] == ')') | 
 |                 { | 
 |                   nest++; | 
 |                   parse_level[nest] == '"'; | 
 |                 } | 
 |               break; | 
 |             case '\'': | 
 |               /* Symbol substitution only in double quotes */ | 
 |               if ((p[1] == '\'') && (parse_level[nest] == '"')) | 
 |                 { | 
 |                   nest++; | 
 |                   parse_level[nest] == '\''; | 
 |                   *p++ = *q++; | 
 |                   token->use_cmd_file = 1; | 
 |                   INC_TOKEN_LEN_OR_BREAK; | 
 |                   break; | 
 |                 } | 
 |               *q = *p; | 
 |             } | 
 |         } | 
 |       p++; | 
 |       q++; | 
 |       /* Pass through doubled double quotes */ | 
 |       if ((*p == '"') && (p[1] == '"') && (parse_level[nest] == '"')) | 
 |       { | 
 |         *p++ = *q++; | 
 |         INC_TOKEN_LEN_OR_BREAK; | 
 |         *p++ = *q++; | 
 |         INC_TOKEN_LEN_OR_BREAK; | 
 |       } | 
 |     } | 
 |   *q = 0; | 
 |   return p; | 
 | } | 
 |  | 
 | /* Extract a $ string from the input line */ | 
 | static char * | 
 | posix_parse_dollar (struct token_info *token) | 
 | { | 
 |   /* $foo becomes 'foo' */ | 
 |   char * q; | 
 |   char * p; | 
 |   q = token->text; | 
 |   p = token->src; | 
 |   token->use_cmd_file = 1; | 
 |  | 
 |   p++; | 
 |   *q++ = '\''; | 
 |   INC_TOKEN_LEN_OR_RETURN (p); | 
 |  | 
 |   while ((isalnum ((unsigned char) *p)) || (*p == '_')) | 
 |     { | 
 |       *q++ = *p++; | 
 |       INC_TOKEN_LEN_OR_BREAK; | 
 |     } | 
 |   *q++ = '\''; | 
 |   while (1) | 
 |     { | 
 |       INC_TOKEN_LEN_OR_BREAK; | 
 |       break; | 
 |     } | 
 |   *q = 0; | 
 |   return p; | 
 | } | 
 |  | 
 | const char *vms_filechars = "0123456789abcdefghijklmnopqrstuvwxyz" \ | 
 |    "ABCDEFGHIJKLMNOPQRSTUVWXYZ[]<>:/_-.$"; | 
 |  | 
 | /* Simple text copy */ | 
 | static char * | 
 | parse_text (struct token_info *token, int assignment_hack) | 
 | { | 
 |   char * q; | 
 |   char * p; | 
 |   int str_len; | 
 |   q = token->text; | 
 |   p = token->src; | 
 |  | 
 |   /* If assignment hack, then this text needs to be double quoted. */ | 
 |   if (vms_unix_simulation && (assignment_hack == 2)) | 
 |     { | 
 |       *q++ = '"'; | 
 |       INC_TOKEN_LEN_OR_RETURN (p); | 
 |     } | 
 |  | 
 |   *q++ = *p++; | 
 |   INC_TOKEN_LEN_OR_RETURN (p); | 
 |  | 
 |   while (*p != 0) | 
 |     { | 
 |       str_len = strspn (p, vms_filechars); | 
 |       if (str_len == 0) | 
 |         { | 
 |           /* Pass through backslash escapes in Unix simulation | 
 |              probably will not work anyway. | 
 |              All any character after a ^ otherwise to support EFS. | 
 |           */ | 
 |           if (vms_unix_simulation && (p[0] == '\\') && (p[1] != 0)) | 
 |             str_len = 2; | 
 |           else if ((p[0] == '^') && (p[1] != 0)) | 
 |             str_len = 2; | 
 |           else if (!vms_unix_simulation && (p[0] == ';')) | 
 |             str_len = 1; | 
 |  | 
 |           if (str_len == 0) | 
 |             { | 
 |               /* If assignment hack, then this needs to be double quoted. */ | 
 |               if (vms_unix_simulation && (assignment_hack == 2)) | 
 |               { | 
 |                 *q++ = '"'; | 
 |                 INC_TOKEN_LEN_OR_RETURN (p); | 
 |               } | 
 |               *q = 0; | 
 |               return p; | 
 |             } | 
 |         } | 
 |       if (str_len > 0) | 
 |         { | 
 |           ADD_TOKEN_LEN_OR_RETURN (str_len, p); | 
 |           strncpy (q, p, str_len); | 
 |           p += str_len; | 
 |           q += str_len; | 
 |           *q = 0; | 
 |         } | 
 |     } | 
 |   /* If assignment hack, then this text needs to be double quoted. */ | 
 |   if (vms_unix_simulation && (assignment_hack == 2)) | 
 |     { | 
 |       *q++ = '"'; | 
 |       INC_TOKEN_LEN_OR_RETURN (p); | 
 |     } | 
 |   return p; | 
 | } | 
 |  | 
 | /* single character copy */ | 
 | static char * | 
 | parse_char (struct token_info *token, int count) | 
 | { | 
 |   char * q; | 
 |   char * p; | 
 |   q = token->text; | 
 |   p = token->src; | 
 |  | 
 |   while (count > 0) | 
 |     { | 
 |       *q++ = *p++; | 
 |       INC_TOKEN_LEN_OR_RETURN (p); | 
 |       count--; | 
 |     } | 
 |   *q = 0; | 
 |   return p; | 
 | } | 
 |  | 
 | /* Build a command string from the collected tokens | 
 |    and process built-ins now | 
 | */ | 
 | static struct dsc$descriptor_s * | 
 | build_vms_cmd (char **cmd_tokens, | 
 |                enum auto_pipe use_pipe_cmd, | 
 |                int append_token) | 
 | { | 
 |   struct dsc$descriptor_s *cmd_dsc; | 
 |   int cmd_tkn_index; | 
 |   char * cmd; | 
 |   int cmd_len; | 
 |   int semicolon_seen; | 
 |  | 
 |   cmd_tkn_index = 0; | 
 |   cmd_dsc = xmalloc (sizeof (struct dsc$descriptor_s)); | 
 |  | 
 |   /* Empty command? */ | 
 |   if (cmd_tokens[0] == NULL) | 
 |     { | 
 |       cmd_dsc->dsc$a_pointer = NULL; | 
 |       cmd_dsc->dsc$w_length = 0; | 
 |       return cmd_dsc; | 
 |     } | 
 |  | 
 |   /* Max DCL command + 1 extra token and trailing space */ | 
 |   cmd = xmalloc (MAX_DCL_CMD_LINE_LENGTH + 256); | 
 |  | 
 |   cmd[0] = '$'; | 
 |   cmd[1] = 0; | 
 |   cmd_len = 1; | 
 |  | 
 |   /* Handle real or auto-pipe */ | 
 |   if (use_pipe_cmd == add_pipe) | 
 |     { | 
 |       /* We need to auto convert to a pipe command */ | 
 |       strcat (cmd, "pipe "); | 
 |       cmd_len += 5; | 
 |     } | 
 |  | 
 |   semicolon_seen = 0; | 
 |   while (cmd_tokens[cmd_tkn_index] != NULL) | 
 |     { | 
 |  | 
 |       /* Check for buffer overflow */ | 
 |       if (cmd_len > MAX_DCL_CMD_LINE_LENGTH) | 
 |         { | 
 |           errno = E2BIG; | 
 |           break; | 
 |         } | 
 |  | 
 |       /* Eliminate double ';' */ | 
 |       if (semicolon_seen && (cmd_tokens[cmd_tkn_index][0] == ';')) | 
 |         { | 
 |           semicolon_seen = 0; | 
 |           free (cmd_tokens[cmd_tkn_index++]); | 
 |           if (cmd_tokens[cmd_tkn_index] == NULL) | 
 |             break; | 
 |         } | 
 |  | 
 |       /* Special handling for CD built-in */ | 
 |       if (strncmp (cmd_tokens[cmd_tkn_index], "builtin_cd", 11) == 0) | 
 |         { | 
 |           int result; | 
 |           semicolon_seen = 0; | 
 |           free (cmd_tokens[cmd_tkn_index]); | 
 |           cmd_tkn_index++; | 
 |           if (cmd_tokens[cmd_tkn_index] == NULL) | 
 |             break; | 
 |           DB(DB_JOBS, (_("BUILTIN CD %s\n"), cmd_tokens[cmd_tkn_index])); | 
 |  | 
 |           /* TODO: chdir fails with some valid syntaxes */ | 
 |           result = chdir (cmd_tokens[cmd_tkn_index]); | 
 |           if (result != 0) | 
 |             { | 
 |               /* TODO: Handle failure better */ | 
 |               free (cmd); | 
 |               while (cmd_tokens[cmd_tkn_index] == NULL) | 
 |                 free (cmd_tokens[cmd_tkn_index++]); | 
 |               cmd_dsc->dsc$w_length = -1; | 
 |               cmd_dsc->dsc$a_pointer = NULL; | 
 |               return cmd_dsc; | 
 |             } | 
 |         } | 
 |       else if (strncmp (cmd_tokens[cmd_tkn_index], "exit", 5) == 0) | 
 |         { | 
 |           /* Copy the exit command */ | 
 |           semicolon_seen = 0; | 
 |           strcpy (&cmd[cmd_len], cmd_tokens[cmd_tkn_index]); | 
 |           cmd_len += strlen (cmd_tokens[cmd_tkn_index]); | 
 |           free (cmd_tokens[cmd_tkn_index++]); | 
 |           if (cmd_len > MAX_DCL_CMD_LINE_LENGTH) | 
 |             { | 
 |               errno = E2BIG; | 
 |               break; | 
 |             } | 
 |  | 
 |           /* Optional whitespace */ | 
 |           if (isspace ((unsigned char) cmd_tokens[cmd_tkn_index][0])) | 
 |             { | 
 |               strcpy (&cmd[cmd_len], cmd_tokens[cmd_tkn_index]); | 
 |               cmd_len += strlen (cmd_tokens[cmd_tkn_index]); | 
 |               free (cmd_tokens[cmd_tkn_index++]); | 
 |               if (cmd_len > MAX_DCL_CMD_LINE_LENGTH) | 
 |               { | 
 |                 errno = E2BIG; | 
 |                 break; | 
 |               } | 
 |             } | 
 |  | 
 |           /* There should be a status, but it is optional */ | 
 |           if (cmd_tokens[cmd_tkn_index][0] == ';') | 
 |             continue; | 
 |  | 
 |           /* If Unix simulation, add '((' */ | 
 |           if (vms_unix_simulation) | 
 |             { | 
 |               strcpy (&cmd[cmd_len], "(("); | 
 |               cmd_len += 2; | 
 |               if (cmd_len > MAX_DCL_CMD_LINE_LENGTH) | 
 |                 { | 
 |                   errno = E2BIG; | 
 |                   break; | 
 |                 } | 
 |             } | 
 |  | 
 |           /* Add the parameter */ | 
 |           strcpy (&cmd[cmd_len], cmd_tokens[cmd_tkn_index]); | 
 |           cmd_len += strlen (cmd_tokens[cmd_tkn_index]); | 
 |           free (cmd_tokens[cmd_tkn_index++]); | 
 |           if (cmd_len > MAX_DCL_CMD_LINE_LENGTH) | 
 |             { | 
 |               errno = E2BIG; | 
 |               break; | 
 |             } | 
 |  | 
 |           /* Add " * 8) .and. %x7f8) .or. %x1035a002" */ | 
 |           if (vms_unix_simulation) | 
 |             { | 
 |               const char *end_str = " * 8) .and. %x7f8) .or. %x1035a002"; | 
 |               strcpy (&cmd[cmd_len], end_str); | 
 |               cmd_len += strlen (end_str); | 
 |               if (cmd_len > MAX_DCL_CMD_LINE_LENGTH) | 
 |                 { | 
 |                   errno = E2BIG; | 
 |                   break; | 
 |                 } | 
 |             } | 
 |           continue; | 
 |         } | 
 |  | 
 |       /* auto pipe needs spaces before semicolon */ | 
 |       if (use_pipe_cmd == add_pipe) | 
 |         if (cmd_tokens[cmd_tkn_index][0] == ';') | 
 |           { | 
 |             cmd[cmd_len++] = ' '; | 
 |             semicolon_seen = 1; | 
 |             if (cmd_len > MAX_DCL_CMD_LINE_LENGTH) | 
 |               { | 
 |                 errno = E2BIG; | 
 |                 break; | 
 |               } | 
 |           } | 
 |         else | 
 |           { | 
 |             char ch; | 
 |             ch = cmd_tokens[cmd_tkn_index][0]; | 
 |             if (!(ch == ' ' || ch == '\t')) | 
 |               semicolon_seen = 0; | 
 |           } | 
 |  | 
 |       strcpy (&cmd[cmd_len], cmd_tokens[cmd_tkn_index]); | 
 |       cmd_len += strlen (cmd_tokens[cmd_tkn_index]); | 
 |  | 
 |       free (cmd_tokens[cmd_tkn_index++]); | 
 |  | 
 |       /* Skip the append tokens if they exist */ | 
 |       if (cmd_tkn_index == append_token) | 
 |         { | 
 |           free (cmd_tokens[cmd_tkn_index++]); | 
 |           if (isspace ((unsigned char) cmd_tokens[cmd_tkn_index][0])) | 
 |             free (cmd_tokens[cmd_tkn_index++]); | 
 |           free (cmd_tokens[cmd_tkn_index++]); | 
 |         } | 
 |     } | 
 |  | 
 |   cmd[cmd_len] = 0; | 
 |   cmd_dsc->dsc$w_length = cmd_len; | 
 |   cmd_dsc->dsc$a_pointer = cmd; | 
 |   cmd_dsc->dsc$b_dtype = DSC$K_DTYPE_T; | 
 |   cmd_dsc->dsc$b_class = DSC$K_CLASS_S; | 
 |  | 
 |   return cmd_dsc; | 
 | } | 
 |  | 
 | pid_t | 
 | child_execute_job (struct childbase *child, int good_stdin UNUSED, char *argv) | 
 | { | 
 |   int i; | 
 |  | 
 |   static struct dsc$descriptor_s *cmd_dsc; | 
 |   static struct dsc$descriptor_s pnamedsc; | 
 |   int spflags = CLI$M_NOWAIT; | 
 |   int status; | 
 |   int comnamelen; | 
 |   char procname[100]; | 
 |  | 
 |   char *p; | 
 |   char *cmd_tokens[(MAX_DCL_TOKENS * 2) + 1]; /* whitespace does not count */ | 
 |   char token_str[MAX_DCL_TOKEN_LENGTH + 1]; | 
 |   struct token_info token; | 
 |   int cmd_tkn_index; | 
 |   int paren_level = 0; | 
 |   enum auto_pipe use_pipe_cmd = nopipe; | 
 |   int append_token = -1; | 
 |   char *append_file = NULL; | 
 |   int unix_echo_cmd = 0;  /* Special handle Unix echo command */ | 
 |   int assignment_hack = 0; /* Handle x=y command as piped command */ | 
 |  | 
 |   /* Parse IO redirection.  */ | 
 |  | 
 |   child->comname = NULL; | 
 |  | 
 |   DB (DB_JOBS, ("child_execute_job (%s)\n", argv)); | 
 |  | 
 |   while (isspace ((unsigned char)*argv)) | 
 |     argv++; | 
 |  | 
 |   if (*argv == 0) | 
 |     { | 
 |       /* Only a built-in or a null command - Still need to run term AST */ | 
 |       child->cstatus = VMS_POSIX_EXIT_MASK; | 
 |       child->vms_launch_status = SS$_NORMAL; | 
 |       child->efn = 0; | 
 |       vmsHandleChildTerm (child); | 
 |       /* TODO what is this "magic number" */ | 
 |       return 270163; /* Special built-in */ | 
 |     } | 
 |  | 
 |   sprintf (procname, "GMAKE_%05x", getpid () & 0xfffff); | 
 |   pnamedsc.dsc$w_length = strlen (procname); | 
 |   pnamedsc.dsc$a_pointer = procname; | 
 |   pnamedsc.dsc$b_dtype = DSC$K_DTYPE_T; | 
 |   pnamedsc.dsc$b_class = DSC$K_CLASS_S; | 
 |  | 
 |   /* Old */ | 
 |   /* Handle comments and redirection. | 
 |      For ONESHELL, the redirection must be on the first line. Any other | 
 |      redirection token is handled by DCL, that is, the pipe command with | 
 |      redirection can be used, but it should not be used on the first line | 
 |      for ONESHELL. */ | 
 |  | 
 |   /* VMS parser notes: | 
 |      1. A token is any of DCL verbs, qualifiers, parameters, or punctuation. | 
 |      2. Only MAX_DCL_TOKENS per line in both one line or command file mode. | 
 |      3. Each token limited to MAC_DCL_TOKEN_LENGTH | 
 |      4. If the line to DCL is greater than MAX_DCL_LINE_LENGTH then a | 
 |         command file must be used. | 
 |      5. Currently a command file must be used symbol substitution is to | 
 |         be performed. | 
 |      6. Currently limiting command files to 2 * MAX_DCL_TOKENS. | 
 |  | 
 |      Build both a command file token list and command line token list | 
 |      until it is determined that the command line limits are exceeded. | 
 |   */ | 
 |  | 
 |   cmd_tkn_index = 0; | 
 |   cmd_tokens[cmd_tkn_index] = NULL; | 
 |   p = argv; | 
 |  | 
 |   token.text = token_str; | 
 |   token.length = 0; | 
 |   token.cmd_errno = 0; | 
 |   token.use_cmd_file = 0; | 
 |  | 
 |   while (*p != 0) | 
 |     { | 
 |       /* We can not build this command so give up */ | 
 |       if (token.cmd_errno != 0) | 
 |         break; | 
 |  | 
 |       token.src = p; | 
 |  | 
 |       switch (*p) | 
 |         { | 
 |         case '\'': | 
 |           if (vms_unix_simulation || unix_echo_cmd) | 
 |             { | 
 |               p = posix_parse_sq (&token); | 
 |               UPDATE_TOKEN; | 
 |               break; | 
 |             } | 
 |  | 
 |           /* VMS mode, the \' means that a symbol substitution is starting | 
 |              so while you might think you can just copy until the next | 
 |              \'.  Unfortunately the substitution can be a lexical function | 
 |              which can contain embedded strings and lexical functions. | 
 |              Messy. | 
 |           */ | 
 |           p = vms_parse_quotes (&token); | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |         case '"': | 
 |           if (vms_unix_simulation) | 
 |             { | 
 |               p = posix_parse_dq (&token); | 
 |               UPDATE_TOKEN; | 
 |               break; | 
 |             } | 
 |  | 
 |           /* VMS quoted string, can contain lexical functions with | 
 |              quoted strings and nested lexical functions. | 
 |           */ | 
 |           p = vms_parse_quotes (&token); | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |  | 
 |         case '$': | 
 |           if (vms_unix_simulation) | 
 |             { | 
 |               p = posix_parse_dollar (&token); | 
 |               UPDATE_TOKEN; | 
 |               break; | 
 |             } | 
 |  | 
 |           /* Otherwise nothing special */ | 
 |           p = parse_text (&token, 0); | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |         case '\\': | 
 |           if (p[1] == '\n') | 
 |             { | 
 |               /* Line continuation, remove it */ | 
 |               p += 2; | 
 |               break; | 
 |             } | 
 |  | 
 |           /* Ordinary character otherwise */ | 
 |           if (assignment_hack != 0) | 
 |             assignment_hack++; | 
 |           if (assignment_hack > 2) | 
 |             { | 
 |               assignment_hack = 0;          /* Reset */ | 
 |               if (use_pipe_cmd == nopipe)   /* force pipe use */ | 
 |                 use_pipe_cmd = add_pipe; | 
 |               token_str[0] = ';';              /* add ; token */ | 
 |               token_str[1] = 0; | 
 |               UPDATE_TOKEN; | 
 |             } | 
 |           p = parse_text (&token, assignment_hack); | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |         case '!': | 
 |         case '#': | 
 |           /* Unix '#' is VMS '!' which comments out the rest of the line. | 
 |              Historically the rest of the line has been skipped. | 
 |              Not quite the right thing to do, as the f$verify lexical | 
 |              function works in comments.  But this helps keep the line | 
 |              lengths short. | 
 |           */ | 
 |           unix_echo_cmd = 0; | 
 |           while (*p != '\n' && *p != 0) | 
 |             p++; | 
 |           break; | 
 |         case '(': | 
 |           /* Subshell, equation, or lexical function argument start */ | 
 |           p = parse_char (&token, 1); | 
 |           UPDATE_TOKEN; | 
 |           paren_level++; | 
 |           break; | 
 |         case ')': | 
 |           /* Close out a paren level */ | 
 |           p = parse_char (&token, 1); | 
 |           UPDATE_TOKEN; | 
 |           paren_level--; | 
 |           /* TODO: Should we diagnose if paren_level goes negative? */ | 
 |           break; | 
 |         case '&': | 
 |           if (isalpha ((unsigned char) p[1]) && !vms_unix_simulation) | 
 |             { | 
 |               /* VMS symbol substitution */ | 
 |               p = parse_text (&token, 0); | 
 |               token.use_cmd_file = 1; | 
 |               UPDATE_TOKEN; | 
 |               break; | 
 |             } | 
 |           if (use_pipe_cmd == nopipe) | 
 |             use_pipe_cmd = add_pipe; | 
 |           if (p[1] != '&') | 
 |             p = parse_char (&token, 1); | 
 |           else | 
 |             p = parse_char (&token, 2); | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |         case '|': | 
 |           if (use_pipe_cmd == nopipe) | 
 |             use_pipe_cmd = add_pipe; | 
 |           if (p[1] != '|') | 
 |             p = parse_char (&token, 1); | 
 |           else | 
 |             p = parse_char (&token, 2); | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |         case ';': | 
 |           /* Separator - convert to a pipe command. */ | 
 |           unix_echo_cmd = 0; | 
 |         case '<': | 
 |           if (use_pipe_cmd == nopipe) | 
 |             use_pipe_cmd = add_pipe; | 
 |           p = parse_char (&token, 1); | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |         case '>': | 
 |           if (use_pipe_cmd == nopipe) | 
 |             use_pipe_cmd = add_pipe; | 
 |           if (p[1] == '>') | 
 |             { | 
 |               /* Parsing would have been simple until support for the >> | 
 |                  append redirect was added. | 
 |                  Implementation needs: | 
 |                  * if not exist output file create empty | 
 |                  * open/append gnv$make_temp??? output_file | 
 |                  * define/user sys$output gnv$make_temp??? | 
 |                  ** And all this done before the command previously tokenized. | 
 |                  * command previously tokenized | 
 |                  * close gnv$make_temp??? | 
 |               */ | 
 |               p = parse_char (&token, 2); | 
 |               append_token = cmd_tkn_index; | 
 |               token.use_cmd_file = 1; | 
 |             } | 
 |           else | 
 |             p = parse_char (&token, 1); | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |         case '/': | 
 |           /* Unix path or VMS option start, read until non-path symbol */ | 
 |           if (assignment_hack != 0) | 
 |             assignment_hack++; | 
 |           if (assignment_hack > 2) | 
 |             { | 
 |               assignment_hack = 0;          /* Reset */ | 
 |               if (use_pipe_cmd == nopipe)   /* force pipe use */ | 
 |                 use_pipe_cmd = add_pipe; | 
 |               token_str[0] = ';';              /* add ; token */ | 
 |               token_str[1] = 0; | 
 |               UPDATE_TOKEN; | 
 |             } | 
 |           p = parse_text (&token, assignment_hack); | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |         case ':': | 
 |           if ((p[1] == 0) || isspace ((unsigned char) p[1])) | 
 |             { | 
 |               /* Unix Null command - treat as comment until next command */ | 
 |               unix_echo_cmd = 0; | 
 |               p++; | 
 |               while (*p != 0) | 
 |                 { | 
 |                   if (*p == ';') | 
 |                     { | 
 |                       /* Remove Null command from pipeline */ | 
 |                       p++; | 
 |                       break; | 
 |                     } | 
 |                   p++; | 
 |                 } | 
 |               break; | 
 |             } | 
 |  | 
 |           /* String assignment */ | 
 |           /* := :== or : */ | 
 |           if (p[1] != '=') | 
 |             p = parse_char (&token, 1); | 
 |           else if (p[2] != '=') | 
 |             p = parse_char (&token, 2); | 
 |           else | 
 |             p = parse_char (&token, 3); | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |         case '=': | 
 |           /* = or == */ | 
 |           /* If this is not an echo statement, this could be a shell | 
 |              assignment.  VMS requires the target to be quoted if it | 
 |              is not a macro substitution */ | 
 |           if (!unix_echo_cmd && vms_unix_simulation && (assignment_hack == 0)) | 
 |             assignment_hack = 1; | 
 |           if (p[1] != '=') | 
 |             p = parse_char (&token, 1); | 
 |           else | 
 |             p = parse_char (&token, 2); | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |         case '+': | 
 |         case '-': | 
 |         case '*': | 
 |           p = parse_char (&token, 1); | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |         case '.': | 
 |           /* .xxx. operation, VMS does not require the trailing . */ | 
 |           p = parse_text (&token, 0); | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |         default: | 
 |           /* Skip repetitive whitespace */ | 
 |           if (isspace ((unsigned char) *p)) | 
 |             { | 
 |               p = parse_char (&token, 1); | 
 |  | 
 |               /* Force to a space or a tab */ | 
 |               if ((token_str[0] != ' ') || | 
 |                   (token_str[0] != '\t')) | 
 |                 token_str[0] = ' '; | 
 |               UPDATE_TOKEN; | 
 |  | 
 |               while (isspace ((unsigned char) *p)) | 
 |                 p++; | 
 |               if (assignment_hack != 0) | 
 |                 assignment_hack++; | 
 |               break; | 
 |             } | 
 |  | 
 |           if (assignment_hack != 0) | 
 |             assignment_hack++; | 
 |           if (assignment_hack > 2) | 
 |             { | 
 |               assignment_hack = 0;          /* Reset */ | 
 |               if (use_pipe_cmd == nopipe)   /* force pipe use */ | 
 |                 use_pipe_cmd = add_pipe; | 
 |               token_str[0] = ';';              /* add ; token */ | 
 |               token_str[1] = 0; | 
 |               UPDATE_TOKEN; | 
 |             } | 
 |           p = parse_text (&token, assignment_hack); | 
 |           if (strncasecmp (token.text, "echo", 4) == 0) | 
 |             unix_echo_cmd = 1; | 
 |           else if (strncasecmp (token.text, "pipe", 4) == 0) | 
 |             use_pipe_cmd = dcl_pipe; | 
 |           UPDATE_TOKEN; | 
 |           break; | 
 |         } | 
 |     } | 
 |  | 
 |   /* End up here with a list of tokens to build a command line. | 
 |      Deal with errors detected during parsing. | 
 |    */ | 
 |   if (token.cmd_errno != 0) | 
 |     { | 
 |       while (cmd_tokens[cmd_tkn_index] == NULL) | 
 |         free (cmd_tokens[cmd_tkn_index++]); | 
 |       child->cstatus = VMS_POSIX_EXIT_MASK | (MAKE_TROUBLE << 3); | 
 |       child->vms_launch_status = SS$_ABORT; | 
 |       child->efn = 0; | 
 |       errno = token.cmd_errno; | 
 |       return -1; | 
 |     } | 
 |  | 
 |   /* Save any redirection to append file */ | 
 |   if (append_token != -1) | 
 |     { | 
 |       int file_token; | 
 |       char * lastdot; | 
 |       char * lastdir; | 
 |       char * raw_append_file; | 
 |       file_token = append_token; | 
 |       file_token++; | 
 |       if (isspace ((unsigned char) cmd_tokens[file_token][0])) | 
 |         file_token++; | 
 |       raw_append_file = vmsify (cmd_tokens[file_token], 0); | 
 |       /* VMS DCL needs a trailing dot if null file extension */ | 
 |       lastdot = strrchr(raw_append_file, '.'); | 
 |       lastdir = strrchr(raw_append_file, ']'); | 
 |       if (lastdir == NULL) | 
 |         lastdir = strrchr(raw_append_file, '>'); | 
 |       if (lastdir == NULL) | 
 |         lastdir = strrchr(raw_append_file, ':'); | 
 |       if ((lastdot == NULL) || (lastdot > lastdir)) | 
 |         { | 
 |           append_file = xmalloc (strlen (raw_append_file) + 1); | 
 |           strcpy (append_file, raw_append_file); | 
 |           strcat (append_file, "."); | 
 |         } | 
 |       else | 
 |         append_file = strdup(raw_append_file); | 
 |     } | 
 |  | 
 |   cmd_dsc = build_vms_cmd (cmd_tokens, use_pipe_cmd, append_token); | 
 |   if (cmd_dsc->dsc$a_pointer == NULL) | 
 |     { | 
 |       if (cmd_dsc->dsc$w_length < 0) | 
 |         { | 
 |           free (cmd_dsc); | 
 |           child->cstatus = VMS_POSIX_EXIT_MASK | (MAKE_TROUBLE << 3); | 
 |           child->vms_launch_status = SS$_ABORT; | 
 |           child->efn = 0; | 
 |           return -1; | 
 |         } | 
 |  | 
 |       /* Only a built-in or a null command - Still need to run term AST */ | 
 |       free (cmd_dsc); | 
 |       child->cstatus = VMS_POSIX_EXIT_MASK; | 
 |       child->vms_launch_status = SS$_NORMAL; | 
 |       child->efn = 0; | 
 |       vmsHandleChildTerm (child); | 
 |       /* TODO what is this "magic number" */ | 
 |       return 270163; /* Special built-in */ | 
 |     } | 
 |  | 
 |   if (cmd_dsc->dsc$w_length > MAX_DCL_LINE_LENGTH) | 
 |     token.use_cmd_file = 1; | 
 |  | 
 |   DB(DB_JOBS, (_("DCL: %s\n"), cmd_dsc->dsc$a_pointer)); | 
 |  | 
 |   /* Enforce the creation of a command file if "vms_always_use_cmd_file" is | 
 |      non-zero. | 
 |      Further, this way DCL reads the input stream and therefore does | 
 |      'forced' symbol substitution, which it doesn't do for one-liners when | 
 |      they are 'lib$spawn'ed. | 
 |  | 
 |      Otherwise the behavior is: | 
 |  | 
 |      Create a *.com file if either the command is too long for | 
 |      lib$spawn, or if a redirect appending to a file is desired, or | 
 |      symbol substitition. | 
 |   */ | 
 |  | 
 |   if (vms_always_use_cmd_file || token.use_cmd_file) | 
 |     { | 
 |       FILE *outfile; | 
 |       int cmd_len; | 
 |  | 
 |       outfile = get_tmpfile (&child->comname); | 
 |       if (outfile == 0) | 
 |         pfatal_with_name (_("fopen (temporary file)")); | 
 |       comnamelen = strlen (child->comname); | 
 |  | 
 |       /* The whole DCL "script" is executed as one action, and it behaves as | 
 |          any DCL "script", that is errors stop it but warnings do not. Usually | 
 |          the command on the last line, defines the exit code.  However, with | 
 |          redirections there is a prolog and possibly an epilog to implement | 
 |          the redirection.  Both are part of the script which is actually | 
 |          executed. So if the redirection encounters an error in the prolog, | 
 |          the user actions will not run; if in the epilog, the user actions | 
 |          ran, but output is not captured. In both error cases, the error of | 
 |          redirection is passed back and not the exit code of the actions. The | 
 |          user should be able to enable DCL "script" verification with "set | 
 |          verify". However, the prolog and epilog commands are not shown. Also, | 
 |          if output redirection is used, the verification output is redirected | 
 |          into that file as well. */ | 
 |       fprintf (outfile, "$ gnv$$make_verify = \"''f$verify(0)'\"\n"); | 
 |       fprintf (outfile, "$ gnv$$make_pid = f$getjpi(\"\",\"pid\")\n"); | 
 |       fprintf (outfile, "$ on error then $ goto gnv$$make_error\n"); | 
 |  | 
 |       /* Handle append redirection */ | 
 |       if (append_file != NULL) | 
 |         { | 
 |           /* If file does not exist, create it */ | 
 |           fprintf (outfile, | 
 |                    "$ gnv$$make_al = \"gnv$$make_append''gnv$$make_pid'\"\n"); | 
 |           fprintf (outfile, | 
 |                    "$ if f$search(\"%s\") .eqs. \"\" then create %s\n", | 
 |                    append_file, append_file); | 
 |  | 
 |           fprintf (outfile, | 
 |                    "$ open/append 'gnv$$make_al' %s\n", append_file); | 
 |  | 
 |           /* define sys$output to that file */ | 
 |           fprintf (outfile, | 
 |                    "$ define/user sys$output 'gnv$$make_al'\n"); | 
 |           DB (DB_JOBS, (_("Append output to %s\n"), append_file)); | 
 |           free(append_file); | 
 |         } | 
 |  | 
 |       fprintf (outfile, "$ gnv$$make_verify = f$verify(gnv$$make_verify)\n"); | 
 |  | 
 |       /* TODO: | 
 |          Only for ONESHELL there will be several commands separated by | 
 |          '\n'. But there can always be multiple continuation lines. | 
 |       */ | 
 |  | 
 |       fprintf (outfile, "%s\n", cmd_dsc->dsc$a_pointer); | 
 |       fprintf (outfile, "$ gnv$$make_status_2 = $status\n"); | 
 |       fprintf (outfile, "$ goto gnv$$make_exit\n"); | 
 |  | 
 |       /* Exit and clean up */ | 
 |       fprintf (outfile, "$ gnv$$make_error: ! 'f$verify(0)\n"); | 
 |       fprintf (outfile, "$ gnv$$make_status_2 = $status\n"); | 
 |  | 
 |       if (append_token != -1) | 
 |         { | 
 |           fprintf (outfile, "$ deassign sys$output\n"); | 
 |           fprintf (outfile, "$ close 'gnv$$make_al'\n"); | 
 |  | 
 |           DB (DB_JOBS, | 
 |               (_("Append %.*s and cleanup\n"), comnamelen-3, child->comname)); | 
 |         } | 
 |       fprintf (outfile, "$ gnv$$make_exit: ! 'f$verify(0)\n"); | 
 |       fprintf (outfile, | 
 |              "$ exit 'gnv$$make_status_2' + (0*f$verify(gnv$$make_verify))\n"); | 
 |  | 
 |       fclose (outfile); | 
 |  | 
 |       free (cmd_dsc->dsc$a_pointer); | 
 |       cmd_dsc->dsc$a_pointer = xmalloc (256 + 4); | 
 |       sprintf (cmd_dsc->dsc$a_pointer, "$ @%s", child->comname); | 
 |       cmd_dsc->dsc$w_length = strlen (cmd_dsc->dsc$a_pointer); | 
 |  | 
 |       DB (DB_JOBS, (_("Executing %s instead\n"), child->comname)); | 
 |     } | 
 |  | 
 |   child->efn = 0; | 
 |   while (child->efn < 32 || child->efn > 63) | 
 |     { | 
 |       status = LIB$GET_EF ((unsigned long *)&child->efn); | 
 |       if (!$VMS_STATUS_SUCCESS (status)) | 
 |         { | 
 |           if (child->comname) | 
 |             { | 
 |               if (!ISDB (DB_JOBS)) | 
 |                 unlink (child->comname); | 
 |               free (child->comname); | 
 |             } | 
 |           return -1; | 
 |         } | 
 |     } | 
 |  | 
 |   SYS$CLREF (child->efn); | 
 |  | 
 |   vms_jobsefnmask |= (1 << (child->efn - 32)); | 
 |  | 
 |   /* Export the child environment into DCL symbols */ | 
 |   if (child->environment != 0) | 
 |     { | 
 |       char **ep = child->environment; | 
 |       while (*ep != 0) | 
 |         { | 
 |           vms_putenv_symbol (*ep); | 
 |           *ep++; | 
 |         } | 
 |     } | 
 |  | 
 |   /* | 
 |     LIB$SPAWN  [command-string] | 
 |     [,input-file] | 
 |     [,output-file] | 
 |     [,flags] | 
 |     [,process-name] | 
 |     [,process-id] [,completion-status-address] [,byte-integer-event-flag-num] | 
 |     [,AST-address] [,varying-AST-argument] | 
 |     [,prompt-string] [,cli] [,table] | 
 |   */ | 
 |  | 
 | #ifndef DONTWAITFORCHILD | 
 |   /* | 
 |    * Code to make ctrl+c and ctrl+y working. | 
 |    * The problem starts with the synchronous case where after lib$spawn is | 
 |    * called any input will go to the child. But with input re-directed, | 
 |    * both control characters won't make it to any of the programs, neither | 
 |    * the spawning nor to the spawned one. Hence the caller needs to spawn | 
 |    * with CLI$M_NOWAIT to NOT give up the input focus. A sys$waitfr | 
 |    * has to follow to simulate the wanted synchronous behaviour. | 
 |    * The next problem is ctrl+y which isn't caught by the crtl and | 
 |    * therefore isn't converted to SIGQUIT (for a signal handler which is | 
 |    * already established). The only way to catch ctrl+y, is an AST | 
 |    * assigned to the input channel. But ctrl+y handling of DCL needs to be | 
 |    * disabled, otherwise it will handle it. Not to mention the previous | 
 |    * ctrl+y handling of DCL needs to be re-established before make exits. | 
 |    * One more: At the time of LIB$SPAWN signals are blocked. SIGQUIT will | 
 |    * make it to the signal handler after the child "normally" terminates. | 
 |    * This isn't enough. It seems reasonable for simple command lines like | 
 |    * a 'cc foobar.c' spawned in a subprocess but it is unacceptable for | 
 |    * spawning make. Therefore we need to abort the process in the AST. | 
 |    * | 
 |    * Prior to the spawn it is checked if an AST is already set up for | 
 |    * ctrl+y, if not one is set up for a channel to SYS$COMMAND. In general | 
 |    * this will work except if make is run in a batch environment, but there | 
 |    * nobody can press ctrl+y. During the setup the DCL handling of ctrl+y | 
 |    * is disabled and an exit handler is established to re-enable it. | 
 |    * If the user interrupts with ctrl+y, the assigned AST will fire, force | 
 |    * an abort to the subprocess and signal SIGQUIT, which will be caught by | 
 |    * the already established handler and will bring us back to common code. | 
 |    * After the spawn (now /nowait) a sys$waitfr simulates the /wait and | 
 |    * enables the ctrl+y be delivered to this code. And the ctrl+c too, | 
 |    * which the crtl converts to SIGINT and which is caught by the common | 
 |    * signal handler. Because signals were blocked before entering this code | 
 |    * sys$waitfr will always complete and the SIGQUIT will be processed after | 
 |    * it (after termination of the current block, somewhere in common code). | 
 |    * And SIGINT too will be delayed. That is ctrl+c can only abort when the | 
 |    * current command completes. Anyway it's better than nothing :-) | 
 |    */ | 
 |  | 
 |   if (!setupYAstTried) | 
 |     tryToSetupYAst(); | 
 |   child->vms_launch_status = lib$spawn (cmd_dsc,               /* cmd-string */ | 
 |                      NULL, /* input-file */ | 
 |                      NULL, /* output-file */ | 
 |                      &spflags,                                 /* flags */ | 
 |                      &pnamedsc,                                /* proc name */ | 
 |                      &child->pid, &child->cstatus, &child->efn, | 
 |                      0, 0, | 
 |                      0, 0, 0); | 
 |  | 
 |   status = child->vms_launch_status; | 
 |   if ($VMS_STATUS_SUCCESS (status)) | 
 |     { | 
 |       status = sys$waitfr (child->efn); | 
 |       vmsHandleChildTerm (child); | 
 |     } | 
 | #else | 
 |   child->vms_launch_status = lib$spawn (cmd_dsc, | 
 |                       NULL, | 
 |                       NULL, | 
 |                       &spflags, | 
 |                       &pnamedsc, | 
 |                       &child->pid, &child->cstatus, &child->efn, | 
 |                       vmsHandleChildTerm, child, | 
 |                       0, 0, 0); | 
 |    status = child->vms_launch_status; | 
 | #endif | 
 |  | 
 |   /* Free the pointer if not a command file */ | 
 |   if (!vms_always_use_cmd_file && !token.use_cmd_file) | 
 |     free (cmd_dsc->dsc$a_pointer); | 
 |   free (cmd_dsc); | 
 |  | 
 |   if (!$VMS_STATUS_SUCCESS (status)) | 
 |     { | 
 |       switch (status) | 
 |         { | 
 |         case SS$_EXQUOTA: | 
 |           errno = EPROCLIM; | 
 |           break; | 
 |         default: | 
 |           errno = EFAIL; | 
 |         } | 
 |     } | 
 |  | 
 |   /* Restore the VMS symbols that were changed */ | 
 |   if (child->environment != 0) | 
 |     { | 
 |       char **ep = child->environment; | 
 |       while (*ep != 0) | 
 |         { | 
 |           vms_restore_symbol (*ep); | 
 |           *ep++; | 
 |         } | 
 |     } | 
 |  | 
 |   /* TODO what is this "magic number" */ | 
 |   return (status & 1) ? 270163 : -1 ; | 
 | } |