>From da68998b0cc648bf7deecebcf5b3039c27dda645 Mon Sep 17 00:00:00 2001 From: John Malmberg Date: Thu, 27 Nov 2014 21:28:30 -0600 Subject: [PATCH] Fix bs-nl handling for VMS. This fix required a complete rewrite of the command parser vmsjobs.c child_execute_job. The old parser had too many incorrect assumptions about DCL commands and could not be repaired to extended. The parser now more closely parses VMS commands and handles quoted commands and redirection. Command File mode has been improved, but can not fully support bs-nl syntax. * commands.c: vms_comma_separator is now a run-time setting. * function.c: vms_comma_separator is now a run-time setting. * main.c: Add VMS environment variables for run-time settings. * vms_legacy_behavior - Force older behavior. * vms_comma_separator - Commas or spaces for separators. * vms_unix_simulation - Enhanced Posix shell simulation features. * makeint.h: Add VMS run-time option variables. * readme.vms: Update to current behavior. * vms_export_symbol.c: Set max symbol size correctly. * vmsjobs.c: child_execute_job() complete rewrite of VMS comand parsing. --- README.VMS | 150 +++++-- commands.c | 2 +- function.c | 16 +- main.c | 85 +++- makeint.h | 6 +- vms_export_symbol.c | 6 +- vmsjobs.c | 1303 ++++++++++++++++++++++++++++++++------------------- 7 files changed, 1020 insertions(+), 548 deletions(-) diff --git a/README.VMS b/README.VMS index bec405d..71110ee 100644 --- a/README.VMS +++ b/README.VMS @@ -16,18 +16,67 @@ Overview: -*-text-mode-*- The descriptions below are for running GNU make from DCL or equivalent. - Recipe differences: ------------------- GNU Make for OpenVMS can not currently run native Unix make files because of - differences in the implementation that it is not aware of the GNV packages. + differences in the implementation. I am trying to document the current behavior in this section. This is based on the information in the file NEWS. and running the test suite. TODO: More tests are needed to validate and demonstrate the OpenVMS expected behavior. + In some cases the older behavior of GNU Make when run from DCL is not + compatible with standard makefile behavior. + + This behavior can be changed when running GNU Make from DCL by setting + either DCL symbols or logical names of the format GNV$. The settings + are enabled with a string starting with one of '1', 'T', or 'E' for "1", + "TRUE", or "ENABLE". They are disabled with a '0', 'F', or 'D' for "1", + "FALSE", or "DISABLE". If they are not explicitly set to one of these + values, then they will be set to their default values. + + The name GNV$MAKE_OLD_VMS when enabled will cause GNU Make to behave as + much as the older method as can be done with out disabling VMS features. + When it is disabled GNU Make have the new behavior which more closely + matches Unix Make behavior. + + The default is currently the old behavior when running GNU Make from DCL. + In the future this may change. When running make from GNV Bash the new + behavior is the default. + + This is a global setting that sets the default behavior for several other + options that can be individually changed. Many of the individual settings + are to make it so that the self tests for GNU Make need less VMS specific + modifications. + + The name GNV$MAKE_COMMA when enabled will cause GNU Make to expect a comma + for a path separator and use a comma for the separator for a list of files. + When disabled, it will cause GNU Make to use a colon for a path separator + and a space for the separator for a list of files. The default is to be + enabled if the GNU Make is set to the older behavior. + + The name GNV$MAKE_SHELL_SIM when enabled will cause GNU Make to try to + simulate a Posix shell more closely. The following behaviors occur: + + * Single quotes are converted to double quotes and any double + quotes inside of them are doubled. No environment variable expansion + is simulated. + * FUTURE: A exit command status will be converted to a Posix Exit + where 0 is success and non-zero is failure. + * Untested: The $ character will cause environment variable expansion. + + VMS generally uses logical name search lists instead of path variables + where the resolution is handled by VMS independent of the program. Which + means that it is likely that nothing will notice if the default path + specifier is changed in the future. + + Currently the built in VMS specific macros and recipes depend on the comma + being used as a file list separator. + TODO: Remove this dependency as other functions in GNU Make depend on a + space being used as a separator. + The format for recipes are a combination of Unix macros, a subset of simulated UNIX commands, some shell emulation, and OpenVMS commands. This makes the resulting makefiles unique to the OpenVMS port of GNU make. @@ -68,24 +117,45 @@ Recipe differences: Any macros marked as exported are temporarily created as DCL symbols for child images to use. DCL symbol substitution is not done with these commands. - TODO: Add symbol substitution. + Untested: Symbol substitution. When a temporary DCL command file is used, DCL symbol substitution will work. - Command lines of excessive length are broken and written to a command file - in sys$scratch:. There's no limit to the lengths of commands (and no need - for .opt files :-) any more. + For VMS 7.3-1 and earlier, command lines are limited to 255 characters + or 1024 characters in a command file. + For VMS 7.3-2 and later, command lines are limited to 4059 characters + or 8192 characters in a command file. + + VMS limits each token of a command line to 256 characters, and limits + a command line to 127 tokens. + + Command lines above the limit length are written to a command file + in sys$scratch:. - The '<', '>' and '>>' redirection has been implemented by using - temporary command files. These will be described later. + In order to handle Unix style extensions to VMS DCL, GNU Make has + parsed the recipe commands and them modified them as needed. The + parser has been re-written to resolve numerous bugs in handling + valid VMS syntax and potential buffer overruns. + + The new parser may need whitespace characters where DCL does not require + it, and also may require that quotes are matched were DCL forgives if + they are not. There is a small chance that existing VMS specific makefiles + will be affected. + + The '<', '>' was previously implemented using command files. Now + GNU Make will check to see if the is already a VMS "PIPE" command and + if it is not, will convert the command to a VMS "PIPE" command. + + The '>>' redirection has been implemented by using a temporary command file. + This will be described later. The DCL symbol or logical name GNV$MAKE_USE_CMD_FILE when set to a string starting with one of '1','T', or 'E' for "1", "TRUE", or "ENABLE", then temporary DCL command files are always used for running commands. - In this case, the exported environment environment variables are - created by command file. BUG: Environment variables that hold values - with dollar signs in them are not exported correctly. + + Some recipe strings with embedded new lines will not be handled correctly + when a command file is used. GNU Make generally does text comparisons for the targets and sources. The make program itself can handle either Unix or OpenVMS format filenames, but @@ -106,6 +176,9 @@ Recipe differences: as it does on Unix. The variables $^ and $@ separate files with commas instead of spaces. + This is controlled by the name GNV$MAKE_COMMA as documented in the + previous section. + While this may seem the natural thing to do with OpenVMS, it actually causes problems when trying to use other make functions that expect the files to be separated by spaces. If you run into this, you need the @@ -248,43 +321,52 @@ Runtime issues: Unix compatibilty features: --------------------------- + If the command 'echo' is seen, any single quotes on the line will be + converted to double quotes. + The variable $(CD) is implemented as a built in Change Directory command. This invokes the 'builtin_cd' Executing a 'set default' recipe doesn't do the trick, since it only affects the subprocess spawned for that command. - TODO: Need more info on how to use and side effects + + The 'builtin_cd' is generally expected to be on its own line. + The 'builtin_cd' either from the expansion of $(CD) or directly + put in a recipe line will be executed before any other commands in + that recipe line. DCL parameter substitution will not work for the + 'builtin_cd' command. + + Putting a 'builtin_cd' in a pipeline or an IF-THEN line should not be + done because the 'builtin_cd' is always executed + and executed first. The directory change is persistent. Unix shell style I/O redirection is supported. You can now write lines like: "mcr sys$disk:[]program.exe < input.txt > output.txt &> error.txt" - BUG: This support is not handling built in make macros with "<" in them - properly. - Posix shells have ":" as a null command. OpenVMS generates a DCL warning - when this is encountered. It would probably be simpler to have OpenVMS just - handle this instead of changing all the tests that use this feature. + Posix shells have ":" as a null command. These are now handled. https://savannah.gnu.org/bugs/index.php?41761 A note on appending the redirected output. A simple mechanism is implemented to make ">>" work in action lines. In OpenVMS there is no simple feature like ">>" to have DCL command or program output redirected and - appended to a file. GNU make for OpenVMS already implements the redirection - of output. If such a redirection is detected, an ">" on the action line, - GNU make creates a DCL command procedure to execute the action and to - redirect its output. Based on that, now ">>" is also recognized and a - similar but different command procedure is created to implement the - append. The main idea here is to create a temporary file which collects - the output and which is appended to the wanted output file. Then the - temporary file is deleted. This is all done in the command procedure to - keep changes in make small and simple. This obviously has some limitations - but it seems good enough compared with the current ">" implementation. - (And in my opinion, redirection is not really what GNU make has to do.) - With this approach, it may happen that the temporary file is not yet - appended and is left in SYS$SCRATCH. - - The temporary file names look like "CMDxxxxx.". Any time the created + appended to a file. GNU make for OpenVMS implements the redirection + of ">>" by using a command procedure. + + The current algorithm creates the output file if it does not exist and + then uses the DCL open/append to extend it. SYS$OUTPUT is then directed + to that file. + + The implementation supports only one redirected append output to a file + and that redirection is done before any other commands in that line + are executed, so it redirects all output for that command. + + The older implementation wrote the output to a temporary file in + in sys$scratch: and then attempted to append the file to the existing file. + The temporary file names looked like "CMDxxxxx.". Any time the created command procedure can not complete, this happens. Pressing Ctrl+Y to - abort make is one case. In case of Ctrl+Y the associated command - procedure is left in SYS$SCRATCH as well. Its name is CMDxxxxx.COM. + abort make is one case. + + In case of Ctrl+Y the associated command procedure is left in SYS$SCRATCH:. + The command procedures will be named gnv$make_cmd*.com. The CtrlY handler now uses $delprc to delete all children. This way also actions with DCL commands will be stopped. As before the CtrlY handler diff --git a/commands.c b/commands.c index 7123021..ec5853a 100644 --- a/commands.c +++ b/commands.c @@ -26,7 +26,7 @@ this program. If not, see . */ #endif #if VMS -# define FILE_LIST_SEPARATOR ',' +# define FILE_LIST_SEPARATOR (vms_comma_separator ? ',' : ' ') #else # define FILE_LIST_SEPARATOR ' ' #endif diff --git a/function.c b/function.c index 169c3a1..e51b24d 100644 --- a/function.c +++ b/function.c @@ -566,10 +566,12 @@ func_notdir_suffix (char *o, char **argv, const char *funcname) if (is_notdir || p >= p2) { #ifdef VMS - o = variable_buffer_output (o, ",", 1); -#else - o = variable_buffer_output (o, " ", 1); + if (vms_comma_separator) + o = variable_buffer_output (o, ",", 1); + else #endif + o = variable_buffer_output (o, " ", 1); + doneany = 1; } } @@ -637,10 +639,12 @@ func_basename_dir (char *o, char **argv, const char *funcname) o = variable_buffer_output (o, p2, len); #ifdef VMS - o = variable_buffer_output (o, ",", 1); -#else - o = variable_buffer_output (o, " ", 1); + if (vms_comma_separator) + o = variable_buffer_output (o, ",", 1); + else #endif + o = variable_buffer_output (o, " ", 1); + doneany = 1; } diff --git a/main.c b/main.c index 0cdb8a8..1ec1a4e 100644 --- a/main.c +++ b/main.c @@ -50,6 +50,36 @@ int __stack = 20000; /* Make sure we have 20K of stack space */ #ifdef VMS int vms_use_mcr_command = 0; int vms_always_use_cmd_file = 0; +int vms_gnv_shell = 0; +int vms_legacy_behavior = 0; +int vms_comma_separator = 0; +int vms_unix_simulation = 0; + +/* Evaluates if a VMS environment option is set, only look at first character */ +static int +get_vms_env_flag (const char *name, int default_value) +{ +char * value; +char x; + + value = getenv (name); + if (value == NULL) + return default_value; + + x = toupper (value[0]); + switch (x) + { + case '1': + case 'T': + case 'E': + return 1; + break; + case '0': + case 'F': + case 'D': + return 0; + } +} #endif void init_dir (void); @@ -1021,6 +1051,9 @@ msdos_return_to_initial_directory (void) } #endif /* __MSDOS__ */ + + + #ifdef _AMIGA int main (int argc, char **argv) @@ -1198,25 +1231,39 @@ main (int argc, char **argv, char **envp) set_program_name (argv[0]); program = program_name; { - const char *value; - value = getenv ("GNV$MAKE_USE_MCR"); - if (value != NULL) - vms_use_mcr_command = 1; - - value = getenv ("GNV$MAKE_USE_CMD_FILE"); - if (value != NULL) - switch (value[0]) - { - case '1': - case 'T': - case 't': - case 'e': - case 'E': - vms_always_use_cmd_file = 1; - break; - default: - vms_always_use_cmd_file = 0; - } + const char *shell; + shell = getenv ("SHELL"); + if (shell != NULL) + vms_gnv_shell = 1; + + vms_use_mcr_command = get_vms_env_flag ("GNV$MAKE_USE_MCR", 0); + + vms_always_use_cmd_file = get_vms_env_flag ("GNV$MAKE_USE_CMD_FILE", 0); + + /* Legacy behavior is on VMS is older behavior that needed to be + changed to be compatible with standard make behavior. + For now only completely disable when running under a Bash shell. + TODO: Update VMS built in recipes and macros to not need this + behavior, at which time the default may change. + */ + vms_legacy_behavior = get_vms_env_flag ("GNV$MAKE_OLD_VMS", + !vms_gnv_shell); + + /* VMS was changed to use a comma separator in the past, but that is + incompatible with built in functions that expect space separated + lists. Allow this to be selectively turned off. + */ + vms_comma_separator = get_vms_env_flag ("GNV$MAKE_COMMA", + vms_legacy_behavior); + + /* Some Posix shell syntax options are incompatible with VMS syntax. + VMS requires double quotes for strings and escapes quotes + differently. When this option is active, VMS will try + to simulate Posix shell simulations instead of using + VMS DCL behavior. + */ + vms_unix_simulation = get_vms_env_flag ("GNV$MAKE_SHELL_SIM", + !vms_legacy_behavior); } if (need_vms_symbol () && !vms_use_mcr_command) create_foreign_command (program_name, argv[0]); diff --git a/makeint.h b/makeint.h index 2009f41..343c30d 100644 --- a/makeint.h +++ b/makeint.h @@ -213,6 +213,10 @@ unsigned int get_path_max (void); extern int vms_use_mcr_command; extern int vms_always_use_cmd_file; +extern int vms_gnv_shell; +extern int vms_comma_separator; +extern int vms_legacy_behavior; +extern int vms_unix_simulation; #endif #ifndef __attribute__ @@ -346,7 +350,7 @@ char *strsignal (int signum); # define PATH_SEPARATOR_CHAR ';' #elif !defined(PATH_SEPARATOR_CHAR) # if defined (VMS) -# define PATH_SEPARATOR_CHAR ',' +# define PATH_SEPARATOR_CHAR (vms_comma_separator ? ',' : ':') # else # define PATH_SEPARATOR_CHAR ':' # endif diff --git a/vms_export_symbol.c b/vms_export_symbol.c index 2cc7367..b8eef11 100644 --- a/vms_export_symbol.c +++ b/vms_export_symbol.c @@ -64,7 +64,11 @@ LIB$DELETE_SYMBOL (const struct dsc$descriptor_s * symbol, const unsigned long * table); #define MAX_DCL_SYMBOL_LEN (255) -#define MAX_DCL_SYMBOL_VALUE (1024) +#if __CRTL_VER >= 70302000 && !defined(__VAX) +# define MAX_DCL_SYMBOL_VALUE (8192) +#else +# define MAX_DCL_SYMBOL_VALUE (1024) +#endif struct dcl_symbol { diff --git a/vmsjobs.c b/vmsjobs.c index df93a4d..3789098 100644 --- a/vmsjobs.c +++ b/vmsjobs.c @@ -38,9 +38,15 @@ decc$exit (int status); #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 1023 +# 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); @@ -50,11 +56,7 @@ static int vms_jobsefnmask = 0; int _is_unixy_shell (const char *path) { - if (path == NULL) - return 0; - - /* When in doubt assume a unix like shell */ - return 1; + return vms_gnv_shell; } #define VMS_GETMSG_MAX 256 @@ -95,72 +97,6 @@ vmsWaitForChildren (int *status) return; } -/* Set up IO redirection. */ - -static char * -vms_redirect (struct dsc$descriptor_s *desc, char *fname, char *ibuf) -{ - char *fptr; - char saved; - - ibuf++; - while (isspace ((unsigned char)*ibuf)) - ibuf++; - fptr = ibuf; - while (*ibuf && !isspace ((unsigned char)*ibuf)) - ibuf++; - saved = *ibuf; - *ibuf = 0; - if (strcmp (fptr, "/dev/null") != 0) - { - strcpy (fname, vmsify (fptr, 0)); - if (strchr (fname, '.') == 0) - strcat (fname, "."); - } - desc->dsc$w_length = strlen (fname); - desc->dsc$a_pointer = fname; - desc->dsc$b_dtype = DSC$K_DTYPE_T; - desc->dsc$b_class = DSC$K_CLASS_S; - - if (*fname == 0) - printf (_("Warning: Empty redirection\n")); - if (saved=='\0') - return ibuf; - *ibuf = saved; - return --ibuf; -} - - -/* found apostrophe at (p-1) - inc p until after closing apostrophe. -*/ - -static char * -vms_handle_apos (char *p) -{ - int alast; - alast = 0; - - while (*p != 0) - if (*p == '"') - if (alast) - { - alast = 0; - p++; - } - else - { - p++; - if (*p!='"') - break; - alast = 1; - } - else - p++; - - return p; -} - 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 @@ -168,15 +104,21 @@ static int ctrlYPressed= 0; inner mode level AST. */ static int -vmsHandleChildTerm(struct child *child) +vmsHandleChildTerm (struct child *child) { int exit_code; register struct child *lastc, *c; int child_failed; - vms_jobsefnmask &= ~(1 << (child->efn - 32)); + /* 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); + lib$free_ef (&child->efn); + } if (child->comname) { if (!ISDB (DB_JOBS) && !ctrlYPressed) @@ -202,7 +144,8 @@ vmsHandleChildTerm(struct child *child) /* Search for a child matching the deceased one. */ lastc = 0; -#if defined(RECURSIVEJOBS) /* I've had problems with recursive stuff and process handling */ +#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 @@ -307,47 +250,454 @@ tryToSetupYAst(void) chan = loc_chan; } -static int -nextnl(char *cmd, int l) +/* 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 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); \ + 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) { - int instring; - instring = 0; - while (cmd[l]) + /* 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 (cmd[l]=='"') - instring = !instring; - else if (cmd[l]=='\n' && !instring) - return ++l; - ++l; + if (*p == '\\') + { + switch(p[1]) + { + case 't': /* Convert tabs */ + *q = '\t'; + p++; + break; + case '\\': /* Just remove leading backslash */ + case '$': + p++; + *q = *p; + 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; + } + p++; + q++; } - return l; + *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; + + 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) +{ + char * q; + char * p; + int str_len; + q = token->text; + p = token->src; + + *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 + { + *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; + } + } + 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; + + 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; + } + cmd = xmalloc (MAX_DCL_CMD_LINE_LENGTH + 1); + + 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; + } + + while (cmd_tokens[cmd_tkn_index] != NULL) + { + + /* Check for buffer overflow */ + if (cmd_len > MAX_DCL_CMD_LINE_LENGTH) + { + errno = E2BIG; + break; + } + + /* Special handling for CD built-in */ + if (strncmp (cmd_tokens[cmd_tkn_index], "builtin_cd", 11) == 0) + { + int result; + 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; + } + } + + /* auto pipe needs spaces before semicolon */ + if (use_pipe_cmd == add_pipe) + if (cmd_tokens[cmd_tkn_index][0] == ';') + cmd[cmd_len++] = ' '; + + 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 (char *argv, struct child *child) { int i; - static struct dsc$descriptor_s cmddsc; - static struct dsc$descriptor_s pnamedsc; - static struct dsc$descriptor_s ifiledsc; - static struct dsc$descriptor_s ofiledsc; - static struct dsc$descriptor_s efiledsc; - int have_redirection = 0; - int have_append = 0; - int have_newline = 0; + static struct dsc$descriptor_s *cmd_dsc; + static struct dsc$descriptor_s pnamedsc; int spflags = CLI$M_NOWAIT; int status; - char *cmd = alloca (strlen (argv) + 512), *p, *q; - char ifile[256], ofile[256], efile[256]; int comnamelen; char procname[100]; - int in_string; + + 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 */ /* Parse IO redirection. */ - ifile[0] = 0; - ofile[0] = 0; - efile[0] = 0; child->comname = NULL; DB (DB_JOBS, ("child_execute_job (%s)\n", argv)); @@ -359,308 +709,370 @@ child_execute_job (char *argv, struct child *child) return 0; sprintf (procname, "GMAKE_%05x", getpid () & 0xfffff); - pnamedsc.dsc$w_length = strlen(procname); + 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; - in_string = 0; + /* 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. */ - for (p = argv, q = cmd; *p; p++, q++) + + /* 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) { - if (*p == '"') - in_string = !in_string; - if (in_string) - { - *q = *p; - continue; - } + /* We can not build this command so give up */ + if (token.cmd_errno != 0) + break; + + token.src = p; + switch (*p) { - case '#': - *p-- = 0; - *q-- = 0; + 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); + UPDATE_TOKEN; break; case '\\': - p++; - if (*p == '\n') + if (p[1] == '\n') + { + /* Line continuation, remove it */ + p += 2; + break; + } + + /* Ordinary character otherwise */ + p = parse_text (&token); + 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++; - if (isspace ((unsigned char)*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) { - do { p++; } while (isspace ((unsigned char)*p)); - p--; + /* VMS symbol substitution */ + p = parse_text (&token); + token.use_cmd_file = 1; + UPDATE_TOKEN; + break; } - *q = *p; + 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 (have_newline==0) + 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] == '>') { - p = vms_redirect (&ifiledsc, ifile, p); - *q = ' '; - have_redirection = 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 - *q = *p; + p = parse_char (&token, 1); + UPDATE_TOKEN; break; - case '>': - if (have_newline==0) + case '/': + /* Unix path or VMS option start, read until non-path symbol */ + p = parse_text (&token); + UPDATE_TOKEN; + break; + case ':': + if ((p[1] == 0) || isspace (p[1])) { - have_redirection = 1; - if (*(p-1) == '2') - { - q--; - if (strncmp (p, ">&1", 3) == 0) - { - p += 2; - strcpy (efile, "sys$output"); - efiledsc.dsc$w_length = strlen(efile); - efiledsc.dsc$a_pointer = efile; - efiledsc.dsc$b_dtype = DSC$K_DTYPE_T; - efiledsc.dsc$b_class = DSC$K_CLASS_S; - } - else - p = vms_redirect (&efiledsc, efile, p); - } - else + /* Unix Null command - treat as comment until next command */ + unix_echo_cmd = 0; + p++; + while (*p != 0) { - if (*(p+1) == '>') + if (*p == ';') { - have_append = 1; - p += 1; + /* Remove Null command from pipeline */ + p++; + break; } - p = vms_redirect (&ofiledsc, ofile, p); + p++; } - *q = ' '; + 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 (p[1] != '=') + p = parse_char (&token, 1); else - *q = *p; + 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); + UPDATE_TOKEN; break; - case '\n': - have_newline++; default: - *q = *p; + /* Skip repetative 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++; + break; + } + + p = parse_text (&token); + 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; } } - *q = *p; - while (isspace ((unsigned char)*--q)) - *q = '\0'; + /* 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; + } -#define VMS_EMPTY_ECHO "write sys$output \"\"" - if (have_newline == 0) + /* Save any redirection to append file */ + if (append_token != -1) { - /* multiple shells */ - if (strncmp(cmd, "builtin_", 8) == 0) + 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)) { - child->pid = 270163; - child->efn = 0; - child->cstatus = 1; - - DB(DB_JOBS, (_("BUILTIN [%s][%s]\n"), cmd, cmd + 8)); - - p = cmd + 8; - - if ((*(p) == 'c') && (*(p + 1) == 'd') - && ((*(p + 2) == ' ') || (*(p + 2) == '\t'))) - { - p += 3; - while ((*p == ' ') || (*p == '\t')) - p++; - DB(DB_JOBS, (_("BUILTIN CD %s\n"), p)); - if (chdir(p)) - return 0; - else - return 1; - } - else if ((*(p) == 'e') - && (*(p+1) == 'c') - && (*(p+2) == 'h') - && (*(p+3) == 'o') - && ((*(p+4) == ' ') || (*(p+4) == '\t') || (*(p+4) == '\0'))) - { - /* This is not a real builtin, it is a built in pre-processing - for the VMS/DCL echo (write sys$output) to ensure the to be echoed - string is correctly quoted (with the DCL quote character '"'). */ - char *vms_echo; - p += 4; - if (*p == '\0') - cmd = VMS_EMPTY_ECHO; - else - { - p++; - while ((*p == ' ') || (*p == '\t')) - p++; - if (*p == '\0') - cmd = VMS_EMPTY_ECHO; - else - { - vms_echo = alloca(strlen(p) + sizeof VMS_EMPTY_ECHO); - strcpy(vms_echo, VMS_EMPTY_ECHO); - vms_echo[sizeof VMS_EMPTY_ECHO - 2] = '\0'; - strcat(vms_echo, p); - strcat(vms_echo, "\""); - cmd = vms_echo; - } - } - DB (DB_JOBS, (_("BUILTIN ECHO %s->%s\n"), p, cmd)); - } - else - { - printf(_("Unknown builtin command '%s'\n"), cmd); - fflush(stdout); - return 0; - } + append_file = xmalloc (strlen (raw_append_file) + 1); + strcpy (append_file, raw_append_file); + strcat (append_file, "."); } - /* expand ':' aka 'do nothing' builtin for bash and friends */ - else if (cmd[0]==':') - cmd[0] = '!'; + else + append_file = strdup(raw_append_file); } - else + + cmd_dsc = build_vms_cmd (cmd_tokens, use_pipe_cmd, append_token); + if (cmd_dsc->dsc$a_pointer == NULL) { - /* todo: expand ':' aka 'do nothing' builtin for bash and friends */ - /* For 'one shell' expand all the - builtin_echo - to - write sys$output "" - where one is ......7 bytes longer. - At the same time ensure that the echo string is properly terminated. - For that, allocate a command buffer big enough for all possible expansions - (have_newline is the count), then expand, copy and terminate. */ - char *tmp_cmd; - int nloff = 0; - int vlen = 0; - int clen = 0; - int inecho; - - tmp_cmd = alloca(strlen(cmd) + (have_newline + 1) * 7 + 1); - tmp_cmd[0] = '\0'; - inecho = 0; - while (cmd[nloff]) - { - if (inecho) - { - if (clen < nloff - 1) - { - memcpy(&tmp_cmd[vlen], &cmd[clen], nloff - clen - 1); - vlen += nloff - clen - 1; - clen = nloff; - } - inecho = 0; - tmp_cmd[vlen] = '"'; - vlen++; - tmp_cmd[vlen] = '\n'; - vlen++; - } - if (strncmp(&cmd[nloff], "builtin_", 8) == 0) - { - /* ??? */ - child->pid = 270163; - child->efn = 0; - child->cstatus = 1; - - DB (DB_JOBS, (_("BUILTIN [%s][%s]\n"), &cmd[nloff], &cmd[nloff+8])); - p = &cmd[nloff + 8]; - if ((*(p) == 'e') - && (*(p + 1) == 'c') - && (*(p + 2) == 'h') - && (*(p + 3) == 'o') - && ((*(p + 4) == ' ') || (*(p + 4) == '\t') || (*(p + 4) == '\0'))) - { - if (clen < nloff - 1) - { - memcpy(&tmp_cmd[vlen], &cmd[clen], nloff - clen - 1); - vlen += nloff - clen - 1; - clen = nloff; - if (inecho) - { - inecho = 0; - tmp_cmd[vlen] = '"'; - vlen++; - } - tmp_cmd[vlen] = '\n'; - vlen++; - } - inecho = 1; - p += 4; - while ((*p == ' ') || (*p == '\t')) - p++; - clen = p - cmd; - memcpy(&tmp_cmd[vlen], VMS_EMPTY_ECHO, - sizeof VMS_EMPTY_ECHO - 2); - vlen += sizeof VMS_EMPTY_ECHO - 2; - } - else - { - printf (_("Builtin command is unknown or unsupported in .ONESHELL: '%s'\n"), &cmd[nloff]); - fflush(stdout); - return 0; - } - } - nloff = nextnl(cmd, nloff + 1); - } - if (clen < nloff) + if (cmd_dsc->dsc$w_length < 0) { - memcpy(&tmp_cmd[vlen], &cmd[clen], nloff - clen); - vlen += nloff - clen; - clen = nloff; - if (inecho) - { - inecho = 0; - tmp_cmd[vlen] = '"'; - vlen++; - } + 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; } - tmp_cmd[vlen] = '\0'; - - cmd = tmp_cmd; + /* 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. - Then all the make environment variables are written as DCL symbol - assignments into the command file as well, so that they are visible - in the sub-process but do not affect the current process. 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 the command contains a newline, or if redirection - is desired. Forcing commands with newlines into DCLs allows to - store search lists on user mode logicals. */ - if (vms_always_use_cmd_file || strlen (cmd) > (MAX_DCL_LINE_LENGTH - 30) - || (have_redirection != 0) - || (have_newline != 0)) + 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; - char c; - char *sep; - int alevel = 0; /* apostrophe level */ - int tmpstrlen; - char *tmpstr; - if (strlen (cmd) == 0) - { - printf (_("Error, empty command\n")); - fflush (stdout); - return 0; - } + int cmd_len; - outfile = output_tmpfile (&child->comname, "sys$scratch:CMDXXXXXX.COM"); + outfile = output_tmpfile (&child->comname, + "sys$scratch:gnv$make_cmdXXXXXX.com"); /* 012345678901234567890 */ -#define TMP_OFFSET 12 -#define TMP_LEN 9 if (outfile == 0) pfatal_with_name (_("fopen (temporary file)")); comnamelen = strlen (child->comname); - tmpstr = &child->comname[TMP_OFFSET]; - tmpstrlen = TMP_LEN; + /* 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 @@ -674,158 +1086,72 @@ child_execute_job (char *argv, struct child *child) 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, "$ %.*s_1 = \"''f$verify(0)'\"\n", tmpstrlen, tmpstr); - if (ifile[0]) - { - fprintf (outfile, "$ assign/user %s sys$input\n", ifile); - DB (DB_JOBS, (_("Redirected input from %s\n"), ifile)); - ifiledsc.dsc$w_length = 0; - } + 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"); - if (efile[0]) + /* Handle append redirection */ + if (append_file != NULL) { - fprintf (outfile, "$ define sys$error %s\n", efile); - DB (DB_JOBS, (_("Redirected error to %s\n"), efile)); - efiledsc.dsc$w_length = 0; + /* 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); } - if (ofile[0]) - if (have_append) - { - fprintf (outfile, "$ define sys$output %.*s\n", comnamelen-3, child->comname); - fprintf (outfile, "$ on error then $ goto %.*s\n", tmpstrlen, tmpstr); - DB (DB_JOBS, (_("Append output to %s\n"), ofile)); - ofiledsc.dsc$w_length = 0; - } - else - { - fprintf (outfile, "$ define sys$output %s\n", ofile); - DB (DB_JOBS, (_("Redirected output to %s\n"), ofile)); - ofiledsc.dsc$w_length = 0; - } - - /* Export the child environment into DCL symbols */ - if (vms_always_use_cmd_file || (child->environment != 0)) - { - char **ep = child->environment; - char *valstr; - while (*ep != 0) - { - valstr = strchr(*ep, '='); - if (valstr == NULL) - continue; - fprintf(outfile, "$ %.*s=\"%s\"\n", valstr - *ep, *ep, - valstr + 1); - ep++; - } - } + fprintf (outfile, "$ gnv$$make_verify = f$verify(gnv$$make_verify)\n"); - fprintf (outfile, "$ %.*s_ = f$verify(%.*s_1)\n", tmpstrlen, tmpstr, tmpstrlen, tmpstr); - - /* TODO: give 78 a name! Whether 78 is a good number is another question. - Trim, split and write the command lines. - Splitting of a command is done after 78 output characters at an - appropriate place (after strings, after comma or space and - before slash): appending a hyphen indicates that the DCL command - is being continued. - Trimming is to skip any whitespace around - including - a - leading $ from the command to ensure writing exactly one "$ " - at the beginning of the line of the output file. Trimming is - done when a new command is seen, indicated by a '\n' (outside - of a string). - The buffer so far is written and reset, when a new command is - seen, when a split was done and at the end of the command. + /* TODO: Only for ONESHELL there will be several commands separated by - '\n'. But there can always be multiple continuation lines. */ - p = sep = q = cmd; - for (c = '\n'; c; c = *q++) - { - switch (c) - { - case '\n': - if (q > p) - { - fwrite(p, 1, q - p, outfile); - p = q; - } - fputc('$', outfile); - fputc(' ', outfile); - while (isspace((unsigned char) *p)) - p++; - if (*p == '$') - p++; - while (isspace((unsigned char) *p)) - p++; - q = sep = p; - break; - case '"': - q = vms_handle_apos(q); - sep = q; - break; - case ',': - case ' ': - sep = q; - break; - case '/': - case '\0': - sep = q - 1; - break; - default: - break; - } - if (sep - p > 78) - { - /* Enough stuff for a line. */ - fwrite(p, 1, sep - p, outfile); - p = sep; - if (*sep) - { - /* The command continues. */ - fputc('-', outfile); - } - fputc('\n', outfile); - } - } + '\n'. But there can always be multiple continuation lines. + */ - if (*p) - { - fwrite(p, 1, --q - p, outfile); - fputc('\n', outfile); - } + fprintf (outfile, "%s\n", cmd_dsc->dsc$a_pointer); + fprintf (outfile, "$ gnv$$make_status_2 = $status\n"); + fprintf (outfile, "$ goto gnv$$make_exit\n"); - if (have_append) + /* 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, "$ %.*s: ! 'f$verify(0)\n", tmpstrlen, tmpstr); - fprintf (outfile, "$ %.*s_2 = $status\n", tmpstrlen, tmpstr); - fprintf (outfile, "$ on error then $ exit\n"); fprintf (outfile, "$ deassign sys$output\n"); - if (efile[0]) - fprintf (outfile, "$ deassign sys$error\n"); - fprintf (outfile, "$ append:=append\n"); - fprintf (outfile, "$ delete:=delete\n"); - fprintf (outfile, "$ append/new %.*s %s\n", comnamelen-3, child->comname, ofile); - fprintf (outfile, "$ delete %.*s;*\n", comnamelen-3, child->comname); - fprintf (outfile, "$ exit '%.*s_2 + (0*f$verify(%.*s_1))\n", tmpstrlen, tmpstr, tmpstrlen, tmpstr); - DB (DB_JOBS, (_("Append %.*s and cleanup\n"), comnamelen-3, child->comname)); + 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); - sprintf (cmd, "$ @%s", child->comname); + 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"), cmd)); + DB (DB_JOBS, (_("Executing %s instead\n"), child->comname)); } - cmddsc.dsc$w_length = strlen(cmd); - cmddsc.dsc$a_pointer = cmd; - cmddsc.dsc$b_dtype = DSC$K_DTYPE_T; - cmddsc.dsc$b_class = DSC$K_CLASS_S; - child->efn = 0; while (child->efn < 32 || child->efn > 63) { - status = lib$get_ef ((unsigned long *)&child->efn); - if (!(status & 1)) + status = LIB$GET_EF ((unsigned long *)&child->efn); + if (!$VMS_STATUS_SUCCESS (status)) { if (child->comname) { @@ -837,12 +1163,12 @@ child_execute_job (char *argv, struct child *child) } } - sys$clref (child->efn); + SYS$CLREF (child->efn); vms_jobsefnmask |= (1 << (child->efn - 32)); /* Export the child environment into DCL symbols */ - if (!vms_always_use_cmd_file && child->environment != 0) + if (child->environment != 0) { char **ep = child->environment; while (*ep != 0) @@ -904,9 +1230,9 @@ child_execute_job (char *argv, struct child *child) if (!setupYAstTried) tryToSetupYAst(); - child->vms_launch_status = lib$spawn (&cmddsc, /* cmd-string */ - (ifiledsc.dsc$w_length == 0)?0:&ifiledsc, /* input-file */ - (ofiledsc.dsc$w_length == 0)?0:&ofiledsc, /* output-file */ + 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, @@ -920,9 +1246,9 @@ child_execute_job (char *argv, struct child *child) vmsHandleChildTerm (child); } #else - child->vms_launch_status = lib$spawn (&cmddsc, - (ifiledsc.dsc$w_length == 0)?0:&ifiledsc, - (ofiledsc.dsc$w_length == 0)?0:&ofiledsc, + child->vms_launch_status = lib$spawn (cmd_dsc, + NULL, + NULL, &spflags, &pnamedsc, &child->pid, &child->cstatus, &child->efn, @@ -931,6 +1257,11 @@ child_execute_job (char *argv, struct child *child) 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) @@ -944,7 +1275,7 @@ child_execute_job (char *argv, struct child *child) } /* Restore the VMS symbols that were changed */ - if (!vms_always_use_cmd_file && child->environment != 0) + if (child->environment != 0) { char **ep = child->environment; while (*ep != 0) -- 1.7.9