diff options
Diffstat (limited to 'src/kmk/vmsjobs.c')
-rw-r--r-- | src/kmk/vmsjobs.c | 1468 |
1 files changed, 1468 insertions, 0 deletions
diff --git a/src/kmk/vmsjobs.c b/src/kmk/vmsjobs.c new file mode 100644 index 0000000..f45c8a8 --- /dev/null +++ b/src/kmk/vmsjobs.c @@ -0,0 +1,1468 @@ +/* --------------- Moved here from job.c --------------- + This file must be #included in job.c, as it accesses static functions. + +Copyright (C) 1996-2016 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 child *child) +{ + int exit_code; + register struct child *lastc, *c; + int child_failed; + + /* 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) + 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 imbedded \" 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 (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 ony 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 (*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 (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 (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; +} + +int +child_execute_job (struct child *child, 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; + /* TODO what is this "magic number" */ + child->pid = 270163; /* Special built-in */ + child->efn = 0; + vmsHandleChildTerm (child); + return 1; + } + + 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 (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 (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 (*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 (*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; + /* TODO what is this "magic number" */ + child->pid = 270163; /* Special built-in */ + child->efn = 0; + errno = token.cmd_errno; + return 0; + } + + /* 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 (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; + /* TODO what is this "magic number" */ + child->pid = 270163; /* Special built-in */ + child->efn = 0; + return 0; + } + + /* 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; + /* TODO what is this "magic number" */ + child->pid = 270163; /* Special built-in */ + child->efn = 0; + vmsHandleChildTerm (child); + return 1; + } + + 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 = output_tmpfile (&child->comname, + "sys$scratch:gnv$make_cmdXXXXXX.com"); + /* 012345678901234567890 */ + 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 0; + } + } + + 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++; + } + } + + return (status & 1); +} |