mirror of
https://git.savannah.gnu.org/git/make.git
synced 2024-11-24 20:20:35 +00:00
1468 lines
42 KiB
C
1468 lines
42 KiB
C
/* --------------- Moved here from job.c ---------------
|
|
This file must be #included in job.c, as it accesses static functions.
|
|
|
|
Copyright (C) 1996-2017 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;
|
|
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 = get_tmpfile (&child->comname,
|
|
"sys$scratch:gnv$make_cmdXXXXXX.com");
|
|
/* 123456789012345678901234567890 */
|
|
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);
|
|
}
|