summaryrefslogtreecommitdiff
path: root/job.c
diff options
context:
space:
mode:
Diffstat (limited to 'job.c')
-rw-r--r--job.c722
1 files changed, 37 insertions, 685 deletions
diff --git a/job.c b/job.c
index 48048d1..75a0133 100644
--- a/job.c
+++ b/job.c
@@ -172,10 +172,6 @@ extern int wait ();
#endif /* Don't have `union wait'. */
-#ifdef VMS
-static int vms_jobsefnmask = 0;
-#endif /* !VMS */
-
#ifndef HAVE_UNISTD_H
extern int dup2 ();
extern int execve ();
@@ -203,9 +199,6 @@ static void start_job_command PARAMS ((struct child *child));
static int load_too_high PARAMS ((void));
static int job_next_command PARAMS ((struct child *));
static int start_waiting_job PARAMS ((struct child *));
-#ifdef VMS
-static void vmsWaitForChildren PARAMS ((int *));
-#endif
/* Chain of all live (or recently deceased) children. */
@@ -231,6 +224,9 @@ int unixy_shell = 1;
unsigned long job_counter = 0;
+/* Number of jobserver tokens this instance is currently using. */
+
+unsigned int jobserver_tokens = 0;
#ifdef WINDOWS32
/*
@@ -400,95 +396,6 @@ child_error (char *target_name, int exit_code, int exit_sig, int coredump,
#endif /* VMS */
}
-#ifdef VMS
-/* 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;
-}
-
-/* Set up IO redirection. */
-
-char *
-vms_redirect (struct dsc$descriptor_s *desc, char *fname, char *ibuf)
-{
- char *fptr;
- extern char *vmsify ();
-
- ibuf++;
- while (isspace ((unsigned char)*ibuf))
- ibuf++;
- fptr = ibuf;
- while (*ibuf && !isspace ((unsigned char)*ibuf))
- 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"));
- return ibuf;
-}
-
-
-/* found apostrophe at (p-1)
- inc p until after closing apostrophe.
- */
-
-static char *
-vms_handle_apos (char *p)
-{
- int alast;
-
-#define SEPCHARS ",/()= "
-
- alast = 0;
-
- while (*p != 0)
- {
- if (*p == '"')
- {
- if (alast)
- {
- alast = 0;
- p++;
- }
- else
- {
- p++;
- if (strchr (SEPCHARS, *p))
- break;
- alast = 1;
- }
- }
- else
- p++;
- }
-
- return p;
-}
-
-#endif
-
/* Handle a dead child. This handler may or may not ever be installed.
@@ -525,8 +432,6 @@ child_handler (int sig UNUSED)
extern int shell_function_pid, shell_function_completed;
-static int reap_lock = 0;
-
/* Reap all dead children, storing the returned status and the new command
state (`cs_finished') in the `file' member of the `struct child' for the
dead child, and removing the child from the chain. In addition, if BLOCK
@@ -547,9 +452,6 @@ reap_children (int block, int err)
# define REAP_MORE dead_children
#endif
- if (reap_lock)
- fatal (NILF, _("INTERNAL: reap_children invoked while reap_lock set."));
-
/* As long as:
We have at least one child outstanding OR a shell function in progress,
@@ -631,6 +533,7 @@ reap_children (int block, int err)
if (any_local)
{
#ifdef VMS
+ static void vmsWaitForChildren PARAMS ((int *));
vmsWaitForChildren (&status);
pid = c->pid;
#else
@@ -904,11 +807,14 @@ reap_children (int block, int err)
static void
free_child (struct child *child)
{
- /* If this child is the only one it was our "free" job, so don't put a
- token back for it. This child has already been removed from the list,
- so if there any left this wasn't the last one. */
+ if (!jobserver_tokens)
+ fatal (NILF, "INTERNAL: Freeing child 0x%08lx (%s) but no tokens left!\n",
+ (unsigned long int) child, child->file->name);
+
+ /* If we're using the jobserver and this child is not the only outstanding
+ job, put a token back into the pipe for it. */
- if (job_fds[1] >= 0 && children)
+ if (job_fds[1] >= 0 && jobserver_tokens > 1)
{
char token = '+';
int r;
@@ -923,6 +829,8 @@ free_child (struct child *child)
(unsigned long int) child, child->file->name));
}
+ --jobserver_tokens;
+
if (handling_fatal_signal) /* Don't bother free'ing if about to die. */
return;
@@ -961,7 +869,7 @@ block_sigs (void)
#endif
}
-#ifdef POSIX
+#ifdef POSIX
void
unblock_sigs (void)
{
@@ -1249,7 +1157,6 @@ start_job_command (struct child *child)
child->remote = 0;
#ifdef VMS
-
if (!child_execute_job (argv, child)) {
/* Fork failed! */
perror_with_name ("vfork", "");
@@ -1475,7 +1382,6 @@ start_waiting_job (struct child *c)
}
/* Start the first command; reap_children will run later command lines. */
- reap_lock = 1;
start_job_command (c);
switch (f->command_state)
@@ -1506,8 +1412,6 @@ start_waiting_job (struct child *c)
break;
}
- reap_lock = 0;
-
return 1;
}
@@ -1676,7 +1580,7 @@ new_job (struct file *file)
children ? "" : "don't "));
/* If we don't already have a job started, use our "free" token. */
- if (!children)
+ if (!jobserver_tokens)
break;
/* Read a token. As long as there's no token available we'll block.
@@ -1711,10 +1615,20 @@ new_job (struct file *file)
/* Reap anything that's currently waiting. */
reap_children (0, 0);
- /* If our "free" token has become available, use it. */
- if (!children)
+ /* Kick off any jobs we have waiting for an opportunity that
+ can run now (ie waiting for load). */
+ start_waiting_jobs ();
+
+ /* If our "free" slot has become available, use it; we don't need an
+ actual token. */
+ if (!jobserver_tokens)
break;
+ /* There must be at least one child already, or we have no business
+ waiting for a token. */
+ if (!children)
+ fatal (NILF, "INTERNAL: no children as we go to sleep on read\n");
+
/* Set interruptible system calls, and read() for a job token. */
set_child_handler_action_flags (0);
got_token = read (job_rfd, &token, 1);
@@ -1743,6 +1657,8 @@ new_job (struct file *file)
}
#endif
+ ++jobserver_tokens;
+
/* The job is now primed. Start it running.
(This will notice if there are in fact no commands.) */
(void) start_waiting_job (c);
@@ -1904,578 +1820,9 @@ start_waiting_jobs (void)
}
#ifndef WINDOWS32
-#ifdef VMS
-#include <descrip.h>
-#include <clidef.h>
-
-/* This is called as an AST when a child process dies (it won't get
- interrupted by anything except a higher level AST).
-*/
-int vmsHandleChildTerm(struct child *child)
-{
- int status;
- register struct child *lastc, *c;
- int child_failed;
-
- vms_jobsefnmask &= ~(1 << (child->efn - 32));
-
- lib$free_ef(&child->efn);
-
- (void) sigblock (fatal_signal_mask);
-
- child_failed = !(child->cstatus & 1 || ((child->cstatus & 7) == 0));
-
- /* 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 (child_failed && !c->noerror && !ignore_errors_flag)
- {
- /* The commands failed. Write an error message,
- delete non-precious targets, and abort. */
- child_error (c->file->name, c->cstatus, 0, 0, 0);
- c->file->update_status = 1;
- delete_child_targets (c);
- }
- else
- {
- if (child_failed)
- {
- /* The commands failed, but we don't care. */
- child_error (c->file->name, c->cstatus, 0, 0, 1);
- child_failed = 0;
- }
-
-#if defined(RECURSIVEJOBS) /* I've had problems with recursive stuff and process handling */
- /* If there are more commands to run, try to start them. */
- start_job (c);
-
- switch (c->file->command_state)
- {
- case cs_running:
- /* Successfully started. */
- break;
-
- case cs_finished:
- if (c->file->update_status != 0) {
- /* We failed to start the commands. */
- delete_child_targets (c);
- }
- break;
-
- default:
- error (NILF, _("internal error: `%s' command_state"),
- c->file->name);
- abort ();
- break;
- }
-#endif /* RECURSIVEJOBS */
- }
-
- /* Set the state flag to say the commands have finished. */
- c->file->command_state = cs_finished;
- notice_finished_file (c->file);
-
-#if defined(RECURSIVEJOBS) /* I've had problems with recursive stuff and process handling */
- /* Remove the child from the chain and free it. */
- if (lastc == 0)
- children = c->next;
- else
- lastc->next = c->next;
- free_child (c);
-#endif /* RECURSIVEJOBS */
-
- /* There is now another slot open. */
- if (job_slots_used > 0)
- --job_slots_used;
-
- /* If the job failed, and the -k flag was not given, die. */
- if (child_failed && !keep_going_flag)
- die (EXIT_FAILURE);
-
- (void) sigsetmask (sigblock (0) & ~(fatal_signal_mask));
-
- return 1;
-}
-
-/* VMS:
- Spawn a process executing the command in ARGV and return its pid. */
-
-#define MAXCMDLEN 200
-
-/* 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 int pidToAbort= 0;
-static int chan= 0;
-
-static void reEnableAst(void) {
- lib$enable_ctrl (&oldCtrlMask,0);
-}
-
-static astHandler (void) {
- if (pidToAbort) {
- sys$forcex (&pidToAbort, 0, SS$_ABORT);
- pidToAbort= 0;
- }
- kill (getpid(),SIGQUIT);
-}
-
-static void tryToSetupYAst(void) {
- $DESCRIPTOR(inputDsc,"SYS$COMMAND");
- int status;
- struct {
- short int status, count;
- int dvi;
- } iosb;
-
- setupYAstTried++;
-
- if (!chan) {
- status= sys$assign(&inputDsc,&chan,0,0);
- if (!(status&SS$_NORMAL)) {
- lib$signal(status);
- return;
- }
- }
- status= sys$qiow (0, chan, IO$_SETMODE|IO$M_CTRLYAST,&iosb,0,0,
- astHandler,0,0,0,0,0);
- if (status==SS$_NORMAL)
- status= iosb.status;
- if (status==SS$_ILLIOFUNC || status==SS$_NOPRIV) {
- sys$dassgn(chan);
-#ifdef CTRLY_ENABLED_ANYWAY
- fprintf (stderr,
- _("-warning, CTRL-Y will leave sub-process(es) around.\n"));
-#else
- return;
-#endif
- }
- else if (!(status&SS$_NORMAL)) {
- sys$dassgn(chan);
- 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;
- }
-}
-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_newline = 0;
-
- int spflags = CLI$M_NOWAIT;
- int status;
- char *cmd = alloca (strlen (argv) + 512), *p, *q;
- char ifile[256], ofile[256], efile[256];
- char *comname = 0;
- char procname[100];
- int in_string;
-
- /* Parse IO redirection. */
-
- ifile[0] = 0;
- ofile[0] = 0;
- efile[0] = 0;
-
- DB (DB_JOBS, ("child_execute_job (%s)\n", argv));
-
- while (isspace ((unsigned char)*argv))
- argv++;
-
- if (*argv == 0)
- return 0;
-
- 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;
-
- in_string = 0;
- /* Handle comments and redirection. */
- for (p = argv, q = cmd; *p; p++, q++)
- {
- if (*p == '"')
- in_string = !in_string;
- if (in_string)
- {
- *q = *p;
- continue;
- }
- switch (*p)
- {
- case '#':
- *p-- = 0;
- *q-- = 0;
- break;
- case '\\':
- p++;
- if (*p == '\n')
- p++;
- if (isspace ((unsigned char)*p))
- {
- do { p++; } while (isspace ((unsigned char)*p));
- p--;
- }
- *q = *p;
- break;
- case '<':
- p = vms_redirect (&ifiledsc, ifile, p);
- *q = ' ';
- have_redirection = 1;
- break;
- case '>':
- have_redirection = 1;
- if (*(p-1) == '2')
- {
- q--;
- if (strncmp (p, ">&1", 3) == 0)
- {
- p += 3;
- 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
- {
- p = vms_redirect (&ofiledsc, ofile, p);
- }
- *q = ' ';
- break;
- case '\n':
- have_newline = 1;
- default:
- *q = *p;
- break;
- }
- }
- *q = *p;
- while (isspace ((unsigned char)*--q))
- *q = '\0';
-
- if (strncmp (cmd, "builtin_", 8) == 0)
- {
- 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) == 'r')
- && (*(p+1) == 'm')
- && ((*(p+2) == ' ') || (*(p+2) == '\t')))
- {
- int in_arg;
-
- /* rm */
- p += 3;
- while ((*p == ' ') || (*p == '\t'))
- p++;
- in_arg = 1;
-
- DB (DB_JOBS, (_("BUILTIN RM %s\n"), p));
- while (*p)
- {
- switch (*p)
- {
- case ' ':
- case '\t':
- if (in_arg)
- {
- *p++ = ';';
- in_arg = 0;
- }
- break;
- default:
- break;
- }
- p++;
- }
- }
- else
- {
- printf(_("Unknown builtin command '%s'\n"), cmd);
- fflush(stdout);
- return 0;
- }
- }
-
- /* 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 (strlen (cmd) > MAXCMDLEN
- || (have_redirection != 0)
- || (have_newline != 0))
- {
- FILE *outfile;
- char c;
- char *sep;
- int alevel = 0; /* apostrophe level */
-
- if (strlen (cmd) == 0)
- {
- printf (_("Error, empty command\n"));
- fflush (stdout);
- return 0;
- }
-
- outfile = open_tmpfile (&comname, "sys$scratch:CMDXXXXXX.COM");
- if (outfile == 0)
- pfatal_with_name (_("fopen (temporary file)"));
-
- 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;
- }
-
- if (efile[0])
- {
- fprintf (outfile, "$ define sys$error %s\n", efile);
- DB (DB_JOBS, (_("Redirected error to %s\n"), efile));
- efiledsc.dsc$w_length = 0;
- }
-
- if (ofile[0])
- {
- fprintf (outfile, "$ define sys$output %s\n", ofile);
- DB (DB_JOBS, (_("Redirected output to %s\n"), ofile));
- ofiledsc.dsc$w_length = 0;
- }
-
- p = sep = q = cmd;
- for (c = '\n'; c; c = *q++)
- {
- switch (c)
- {
- case '\n':
- /* At a newline, skip any whitespace around a leading $
- from the command and issue exactly one $ into the DCL. */
- while (isspace ((unsigned char)*p))
- p++;
- if (*p == '$')
- p++;
- while (isspace ((unsigned char)*p))
- p++;
- fwrite (p, 1, q - p, outfile);
- fputc ('$', outfile);
- fputc (' ', outfile);
- /* Reset variables. */
- p = sep = q;
- break;
-
- /* Nice places for line breaks are after strings, after
- comma or space and before slash. */
- 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);
- }
- }
-
- fwrite (p, 1, q - p, outfile);
- fputc ('\n', outfile);
-
- fclose (outfile);
-
- sprintf (cmd, "$ @%s", comname);
-
- DB (DB_JOBS, (_("Executing %s instead\n"), cmd));
- }
-
- 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))
- return 0;
- }
-
- sys$clref (child->efn);
-
- vms_jobsefnmask |= (1 << (child->efn - 32));
-
-/*
- 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();
- status = lib$spawn (&cmddsc, /* cmd-string */
- (ifiledsc.dsc$w_length == 0)?0:&ifiledsc, /* input-file */
- (ofiledsc.dsc$w_length == 0)?0:&ofiledsc, /* output-file */
- &spflags, /* flags */
- &pnamedsc, /* proc name */
- &child->pid, &child->cstatus, &child->efn,
- 0, 0,
- 0, 0, 0);
- if (status & 1)
- {
- pidToAbort= child->pid;
- status= sys$waitfr (child->efn);
- pidToAbort= 0;
- vmsHandleChildTerm(child);
- }
-#else
- status = lib$spawn (&cmddsc,
- (ifiledsc.dsc$w_length == 0)?0:&ifiledsc,
- (ofiledsc.dsc$w_length == 0)?0:&ofiledsc,
- &spflags,
- &pnamedsc,
- &child->pid, &child->cstatus, &child->efn,
- vmsHandleChildTerm, child,
- 0, 0, 0);
-#endif
-
- if (!(status & 1))
- {
- printf (_("Error spawning, %d\n") ,status);
- fflush (stdout);
- switch (status)
- {
- case 0x1c:
- errno = EPROCLIM;
- break;
- default:
- errno = EFAIL;
- }
- }
-
- if (comname && !ISDB (DB_JOBS))
- unlink (comname);
-
- return (status & 1);
-}
-
-#else /* !VMS */
/* EMX: Start a child process. This function returns the new pid. */
-# if defined __MSDOS__ || defined __EMX__
+# if defined __MSDOS__ || defined __EMX__
int
child_execute_job (int stdin_fd, int stdout_fd, char **argv, char **envp)
{
@@ -2555,7 +1902,6 @@ child_execute_job (int stdin_fd, int stdout_fd, char **argv, char **envp)
exec_command (argv, envp);
}
#endif /* !AMIGA && !__MSDOS__ */
-#endif /* !VMS */
#endif /* !WINDOWS32 */
#ifndef _AMIGA
@@ -2566,7 +1912,7 @@ child_execute_job (int stdin_fd, int stdout_fd, char **argv, char **envp)
# ifdef __EMX__
int
# else
- void
+void
# endif
exec_command (char **argv, char **envp)
{
@@ -3630,3 +2976,9 @@ dup2 (int old, int new)
return fd;
}
#endif /* !HAPE_DUP2 && !_AMIGA */
+
+/* On VMS systems, include special VMS functions. */
+
+#ifdef VMS
+#include "vmsjobs.c"
+#endif