* Copyright (c) 1988, 1989, 1990, 1993
* The Regents of the University of California. All rights reserved.
* Copyright (c) 1989 by Berkeley Softworks
* This code is derived from software contributed to Berkeley by
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
static char sccsid
[] = "@(#)job.c 8.3 (Berkeley) 4/28/95";
* handle the creation etc. of our child processes.
* Job_Make Start the creation of the given target.
* Job_CatchChildren Check for and handle the termination of any
* children. This must be called reasonably
* frequently to keep the whole make going at
* a decent clip, since job table entries aren't
* removed until their process is caught this way.
* Its single argument is TRUE if the function
* should block waiting for a child to terminate.
* Job_CatchOutput Print any output our children have produced.
* Should also be called fairly frequently to
* keep the user informed of what's going on.
* If no output is waiting, it will block for
* a time given by the SEL_* constants, below,
* or until output is ready.
* Job_Init Called to intialize this module. in addition,
* any commands attached to the .BEGIN target
* are executed before this function returns.
* Hence, the makefile must have been parsed
* before this function is called.
* Job_Full Return TRUE if the job table is filled.
* Job_Empty Return TRUE if the job table is completely
* Job_ParseShell Given the line following a .SHELL target, parse
* the line as a shell specification. Returns
* FAILURE if the spec was incorrect.
* Job_End Perform any final processing which needs doing.
* This includes the execution of any commands
* which have been/were attached to the .END
* target. It should only be called when the
* Job_AbortAll Abort all currently running jobs. It doesn't
* handle output or do anything for the jobs,
* just kills them. It should only be called in
* an emergency, as it were.
* Job_CheckCommands Verify that the commands for a target are
* ok. Provide them if necessary and possible.
* Job_Touch Update a target without really updating it.
* Job_Wait Wait for all currently-running jobs to finish.
* error handling variables
static int errors
= 0; /* number of errors reported */
static int aborting
= 0; /* why is the make aborting? */
#define ABORT_ERROR 1 /* Because of an error */
#define ABORT_INTERRUPT 2 /* Because it was interrupted */
#define ABORT_WAIT 3 /* Waiting for jobs to finish */
* post-make command processing. The node postCommands is really just the
* .END target but we keep it around to avoid having to search for it
static GNode
*postCommands
; /* node containing commands to execute when
* everything else is done */
static int numCommands
; /* The number of commands actually printed
* for a target. Should this number be
* 0, no shell will be executed. */
* Return values from JobStart.
#define JOB_RUNNING 0 /* Job is running */
#define JOB_ERROR 1 /* Error in starting the job */
#define JOB_FINISHED 2 /* The job is already finished */
#define JOB_STOPPED 3 /* The job is stopped */
* tfile is the name of a file into which all shell commands are put. It is
* used over by removing it before the child shell is executed. The XXXXX in
* the string are replaced by the pid of the make process in a 5-character
* field with leading zeroes.
static char tfile
[] = TMPPAT
;
* Descriptions for various shells.
static Shell shells
[] = {
* CSH description. The csh can do echo control by playing
* with the setting of the 'echo' shell variable. Sadly,
* however, it is unable to do error control nicely.
TRUE
, "unset verbose", "set verbose", "unset verbose", 10,
FALSE
, "echo \"%s\"\n", "csh -c \"%s || exit 0\"",
* SH description. Echo control is also possible and, under
* sun UNIX anyway, one can even control error checking.
TRUE
, "set -", "set -v", "set -", 5,
FALSE
, "echo \"%s\"\n", "sh -c '%s || exit 0'\n",
FALSE
, (char *)0, (char *)0, (char *)0, 0,
FALSE
, (char *)0, (char *)0,
static Shell
*commandShell
= &shells
[DEFSHELL
];/* this is the shell to
* commands in the Makefile.
* Job_ParseShell function */
static char *shellPath
= (char *) NULL
, /* full pathname of
*shellName
; /* last component of shell */
static int maxJobs
; /* The most children we can run at once */
static int maxLocal
; /* The most local ones we can have */
int nJobs
; /* The number of children currently running */
int nLocal
; /* The number of local children */
Lst jobs
; /* The structures that describe them */
Boolean jobFull
; /* Flag to tell when the job table is full. It
* is set TRUE when (1) the total number of
* running jobs equals the maximum allowed or
* (2) a job can only be run locally, but
* nLocal equals maxLocal */
static fd_set outputs
; /* Set of descriptors of pipes connected to
* the output channels of children */
GNode
*lastNode
; /* The node for which output was most recently
char *targFmt
; /* Format string to use to head output from a
* job when it's not the most-recent job heard
#define TARG_FMT "--- %s ---\n" /* Default format */
* When JobStart attempts to run a job remotely but can't, and isn't allowed
* to run the job locally, or when Job_CatchChildren detects a job that has
* been migrated home, the job is placed on the stoppedJobs queue to be run
* when the next job finishes.
Lst stoppedJobs
; /* Lst of Job structures describing
* jobs that were stopped due to concurrency
* limits or migration home */
#if defined(USE_PGRP) && defined(SYSV)
#define KILL(pid,sig) killpg (-(pid),(sig))
#define KILL(pid,sig) killpg ((pid),(sig))
#define KILL(pid,sig) kill ((pid),(sig))
static int JobCondPassSig
__P((ClientData
, ClientData
));
static void JobPassSig
__P((int));
static int JobCmpPid
__P((ClientData
, ClientData
));
static int JobPrintCommand
__P((ClientData
, ClientData
));
static int JobSaveCommand
__P((ClientData
, ClientData
));
static void JobFinish
__P((Job
*, union wait
));
static void JobExec
__P((Job
*, char **));
static void JobMakeArgv
__P((Job
*, char **));
static void JobRestart
__P((Job
*));
static int JobStart
__P((GNode
*, int, Job
*));
static void JobDoOutput
__P((Job
*, Boolean
));
static Shell
*JobMatchShell
__P((char *));
static void JobInterrupt
__P((int));
*-----------------------------------------------------------------------
* Pass a signal to a job if the job is remote or if USE_PGRP
* None, except the job may bite it.
*-----------------------------------------------------------------------
JobCondPassSig(jobp
, signop
)
ClientData jobp
; /* Job to biff */
ClientData signop
; /* Signal to send it */
int signo
= *(int *) signop
;
if (job
->flags
& JOB_REMOTE
) {
(void)Rmt_Signal(job
, signo
);
* Assume that sending the signal to job->pid will signal any remote
*-----------------------------------------------------------------------
* Pass a signal on to all remote jobs and to all local jobs if
* USE_PGRP is defined, then die ourselves.
* We die by the same signal.
*-----------------------------------------------------------------------
int signo
; /* The signal number we've received */
Lst_ForEach(jobs
, JobCondPassSig
, (ClientData
)(long)signo
);
* Deal with proper cleanup based on the signal received. We only run
* the .INTERRUPT target if the signal was in fact an interrupt. The other
* three termination signals are more of a "get out *now*" command.
} else if ((signo
== SIGHUP
) || (signo
== SIGTERM
) || (signo
== SIGQUIT
)) {
* Leave gracefully if SIGQUIT, rather than core dumping.
* Send ourselves the signal now we've given the message to everyone else.
* Note we block everything else possible while we're getting the signal.
* This ensures that all our jobs get continued when we wake up before
* we take any other signal.
(void) sigsetmask(~0 & ~(1 << (signo
-1)));
Lst_ForEach(jobs
, JobCondPassSig
, (ClientData
) &signo
);
signal(signo
, JobPassSig
);
*-----------------------------------------------------------------------
* Compare the pid of the job with the given pid and return 0 if they
* are equal. This function is called from Job_CatchChildren via
* Lst_Find to find the job descriptor of the finished job.
*-----------------------------------------------------------------------
ClientData job
; /* job to examine */
ClientData pid
; /* process id desired */
return ( *(int *) pid
- ((Job
*) job
)->pid
);
*-----------------------------------------------------------------------
* Put out another command for the given job. If the command starts
* with an @ or a - we process it specially. In the former case,
* so long as the -s and -n flags weren't given to make, we stick
* a shell-specific echoOff command in the script. In the latter,
* we ignore errors for the entire job, unless the shell has error
* If the command is just "..." we take all future commands for this
* job to be commands to be executed once the entire graph has been
* made and return non-zero to signal that the end of the commands
* was reached. These commands are later attached to the postCommands
* node and executed by Job_End when all things are done.
* This function is called from JobStart via Lst_ForEach.
* Always 0, unless the command was "..."
* If the command begins with a '-' and the shell has no error control,
* the JOB_IGNERR flag is set in the job descriptor.
* If the command is "..." and we're not ignoring such things,
* tailCmds is set to the successor node of the cmd.
* numCommands is incremented if the command is actually printed.
*-----------------------------------------------------------------------
JobPrintCommand (cmdp
, jobp
)
ClientData cmdp
; /* command string to print */
ClientData jobp
; /* job for which to print it */
Boolean noSpecials
; /* true if we shouldn't worry about
* inserting special commands into
Boolean shutUp
= FALSE
; /* true if we put a no echo command
* into the command file */
Boolean errOff
= FALSE
; /* true if we turned error checking
* off before printing the command
* and need to turn it back on */
char *cmdTemplate
; /* Template to use when printing the
char *cmdStart
; /* Start of expanded command */
LstNode cmdNode
; /* Node for replacing the command */
char *cmd
= (char *) cmdp
;
noSpecials
= (noExecute
&& ! (job
->node
->type
& OP_MAKE
));
if (strcmp (cmd
, "...") == 0) {
job
->node
->type
|= OP_SAVE_CMDS
;
if ((job
->flags
& JOB_IGNDOTS
) == 0) {
job
->tailCmds
= Lst_Succ (Lst_Member (job
->node
->commands
,
#define DBPRINTF(fmt, arg) if (DEBUG(JOB)) printf (fmt, arg); fprintf (job->cmdFILE, fmt, arg)
* For debugging, we replace each command with the result of expanding
* the variables in the command.
cmdNode
= Lst_Member (job
->node
->commands
, (ClientData
)cmd
);
cmdStart
= cmd
= Var_Subst (NULL
, cmd
, job
->node
, FALSE
);
Lst_Replace (cmdNode
, (ClientData
)cmdStart
);
* Check for leading @' and -'s to control echoing and error checking.
while (*cmd
== '@' || *cmd
== '-') {
while (isspace((unsigned char) *cmd
))
if (! (job
->flags
& JOB_SILENT
) && !noSpecials
&&
commandShell
->hasEchoCtl
) {
DBPRINTF ("%s\n", commandShell
->echoOff
);
if ( ! (job
->flags
& JOB_IGNERR
) && !noSpecials
) {
if (commandShell
->hasErrCtl
) {
* we don't want the error-control commands showing
* up either, so we turn off echoing while executing
* them. We could put another field in the shell
* structure to tell JobDoOutput to look for this
* string too, but why make it any more complex than
if (! (job
->flags
& JOB_SILENT
) && !shutUp
&&
commandShell
->hasEchoCtl
) {
DBPRINTF ("%s\n", commandShell
->echoOff
);
DBPRINTF ("%s\n", commandShell
->ignErr
);
DBPRINTF ("%s\n", commandShell
->echoOn
);
DBPRINTF ("%s\n", commandShell
->ignErr
);
} else if (commandShell
->ignErr
&&
(*commandShell
->ignErr
!= '\0'))
* The shell has no error control, so we need to be
* weird to get it to ignore any errors from the command.
* If echoing is turned on, we turn it off and use the
* errCheck template to echo the command. Leave echoing
* off so the user doesn't see the weirdness we go through
* to ignore errors. Set cmdTemplate to use the weirdness
* instead of the simple "%s\n" template.
if (! (job
->flags
& JOB_SILENT
) && !shutUp
&&
commandShell
->hasEchoCtl
) {
DBPRINTF ("%s\n", commandShell
->echoOff
);
DBPRINTF (commandShell
->errCheck
, cmd
);
cmdTemplate
= commandShell
->ignErr
;
* The error ignoration (hee hee) is already taken care
* of by the ignErr template, so pretend error checking
DBPRINTF (cmdTemplate
, cmd
);
* If echoing is already off, there's no point in issuing the
* echoOff command. Otherwise we issue it and pretend it was on
* for the whole command...
if (!shutUp
&& !(job
->flags
& JOB_SILENT
) && commandShell
->hasEchoCtl
){
DBPRINTF ("%s\n", commandShell
->echoOff
);
DBPRINTF ("%s\n", commandShell
->errCheck
);
DBPRINTF ("%s\n", commandShell
->echoOn
);
*-----------------------------------------------------------------------
* Save a command to be executed when everything else is done.
* Callback function for JobFinish...
* The command is tacked onto the end of postCommands's commands list.
*-----------------------------------------------------------------------
cmd
= (ClientData
) Var_Subst (NULL
, (char *) cmd
, (GNode
*) gn
, FALSE
);
(void)Lst_AtEnd (postCommands
->commands
, cmd
);
*-----------------------------------------------------------------------
* Do final processing for the given job including updating
* parents and starting new jobs as available/necessary. Note
* that we pay no attention to the JOB_IGNERR flag here.
* This is because when we're called because of a noexecute flag
* or something, jstat.w_status is 0 and when called from
* Job_CatchChildren, the status is zeroed if it s/b ignored.
* Some nodes may be put on the toBeMade queue.
* Final commands for the job are placed on postCommands.
* If we got an error and are aborting (aborting == ABORT_ERROR) and
* the job list is now empty, we are done for the day.
* If we recognized an error (errors !=0), we set the aborting flag
* to ABORT_ERROR so no more jobs will be started.
*-----------------------------------------------------------------------
Job
*job
; /* job to finish */
union wait status
; /* sub-why job went away */
if ((WIFEXITED(status
) &&
(((status
.w_retcode
!= 0) && !(job
->flags
& JOB_IGNERR
)))) ||
(WIFSIGNALED(status
) && (status
.w_termsig
!= SIGCONT
)))
* If it exited non-zero and either we're doing things our
* way or we're not ignoring errors, the job is finished.
* Similarly, if the shell died because of a signal
* the job is also finished. In these
* cases, finish out the job's output before printing the exit
FD_CLR(job
->inPipe
, &outputs
);
#endif /* RMT_WILL_WATCH */
if (job
->outPipe
!= job
->inPipe
) {
(void)close (job
->outPipe
);
(void)close (job
->inPipe
);
(void)close (job
->outFd
);
if (job
->cmdFILE
!= NULL
&& job
->cmdFILE
!= stdout
) {
} else if (WIFEXITED(status
) && status
.w_retcode
!= 0) {
* Deal with ignored errors in -B mode. We need to print a message
* telling of the ignored error as well as setting status.w_status
* to 0 so the next command gets run. To do this, we set done to be
* TRUE if in -B mode and the job exited non-zero. Note we don't
* want to close down any of the streams until we know we're at the
* No need to close things down or anything.
(WIFSIGNALED(status
) && (status
.w_termsig
== SIGCONT
)) ||
if (!usePipes
&& (job
->flags
& JOB_IGNERR
)) {
* If output is going to a file and this job is ignoring
* errors, arrange to have the exit status sent to the
out
= fdopen (job
->outFd
, "w");
if (status
.w_retcode
!= 0) {
if (usePipes
&& job
->node
!= lastNode
) {
fprintf (out
, targFmt
, job
->node
->name
);
fprintf (out
, "*** Error code %d%s\n", status
.w_retcode
,
(job
->flags
& JOB_IGNERR
) ? " (ignored)" : "");
if (job
->flags
& JOB_IGNERR
) {
if (usePipes
&& job
->node
!= lastNode
) {
fprintf (out
, targFmt
, job
->node
->name
);
fprintf (out
, "*** Completed successfully\n");
} else if (WIFSTOPPED(status
)) {
if (usePipes
&& job
->node
!= lastNode
) {
fprintf (out
, targFmt
, job
->node
->name
);
if (! (job
->flags
& JOB_REMIGRATE
)) {
fprintf (out
, "*** Stopped -- signal %d\n", status
.w_stopsig
);
job
->flags
|= JOB_RESUME
;
(void)Lst_AtEnd(stoppedJobs
, (ClientData
)job
);
} else if (status
.w_termsig
== SIGCONT
) {
* If the beastie has continued, shift the Job from the stopped
* list to the running one (or re-stop it if concurrency is
* exceeded) and go and get another child.
if (job
->flags
& (JOB_RESUME
|JOB_REMIGRATE
|JOB_RESTART
)) {
if (usePipes
&& job
->node
!= lastNode
) {
fprintf (out
, targFmt
, job
->node
->name
);
fprintf (out
, "*** Continued\n");
if (! (job
->flags
& JOB_CONTINUING
)) {
Lst_AtEnd(jobs
, (ClientData
)job
);
if (! (job
->flags
& JOB_REMOTE
)) {
printf("Job queue is full.\n");
if (usePipes
&& job
->node
!= lastNode
) {
fprintf (out
, targFmt
, job
->node
->name
);
fprintf (out
, "*** Signal %d\n", status
.w_termsig
);
* Now handle the -B-mode stuff. If the beast still isn't finished,
* try and restart the job on the next command. If JobStart says it's
* ok, it's ok. If there's an error, this puppy is done.
if ((status
.w_status
== 0) &&
!Lst_IsAtEnd (job
->node
->commands
))
switch (JobStart (job
->node
,
job
->flags
& JOB_IGNDOTS
,
* If we got back a JOB_FINISHED code, JobStart has already
* called Make_Update and freed the job descriptor. We set
* done to false here to avoid fake cycles and double frees.
* JobStart needs to do the update so we can proceed up the
* graph when given the -n flag..
(aborting
!= ABORT_ERROR
) &&
(aborting
!= ABORT_INTERRUPT
) &&
* As long as we aren't aborting and the job didn't return a non-zero
* status that we shouldn't ignore, we call Make_Update to update
* the parents. In addition, any saved commands for the node are placed
if (job
->tailCmds
!= NILLNODE
) {
Lst_ForEachFrom (job
->node
->commands
, job
->tailCmds
,
} else if (status
.w_status
) {
while (!errors
&& !jobFull
&& !Lst_IsEmpty(stoppedJobs
)) {
JobRestart((Job
*)Lst_DeQueue(stoppedJobs
));
* Set aborting if any error.
if (errors
&& !keepgoing
&& (aborting
!= ABORT_INTERRUPT
)) {
* If we found any errors in this batch of children and the -k flag
* wasn't given, we set the aborting flag so no more jobs get
if ((aborting
== ABORT_ERROR
) && Job_Empty()) {
* If we are aborting and the job table is now empty, we finish.
*-----------------------------------------------------------------------
* Touch the given target. Called by JobStart when the -t flag was
* The data modification of the file is changed. In addition, if the
* file did not exist, it is created.
*-----------------------------------------------------------------------
GNode
*gn
; /* the node of the file to touch */
Boolean silent
; /* TRUE if should not print messages */
int streamID
; /* ID of stream opened to do the touch */
struct timeval times
[2]; /* Times for utimes() call */
if (gn
->type
& (OP_JOIN
|OP_USE
|OP_EXEC
|OP_OPTIONAL
)) {
* .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets
* and, as such, shouldn't really be created.
printf ("touch %s\n", gn
->name
);
if (gn
->type
& OP_ARCHV
) {
} else if (gn
->type
& OP_LIB
) {
char *file
= gn
->path
? gn
->path
: gn
->name
;
times
[0].tv_sec
= times
[1].tv_sec
= now
;
times
[0].tv_usec
= times
[1].tv_usec
= 0;
if (utimes(file
, times
) < 0){
streamID
= open (file
, O_RDWR
| O_CREAT
, 0666);
* Read and write a byte to the file to change the
* modification time, then close the file.
if (read(streamID
, &c
, 1) == 1) {
lseek(streamID
, 0L, L_SET
);
printf("*** couldn't touch %s: %s", file
, strerror(errno
));
*-----------------------------------------------------------------------
* Make sure the given node has all the commands it needs.
* TRUE if the commands list is/was ok.
* The node will have commands from the .DEFAULT rule added to it
*-----------------------------------------------------------------------
Job_CheckCommands (gn
, abortProc
)
GNode
*gn
; /* The target whose commands need
void (*abortProc
) __P((char *, ...));
/* Function to abort with message */
if (OP_NOP(gn
->type
) && Lst_IsEmpty (gn
->commands
) &&
(gn
->type
& OP_LIB
) == 0) {
* No commands. Look for .DEFAULT rule from which we might infer
if ((DEFAULT
!= NILGNODE
) && !Lst_IsEmpty(DEFAULT
->commands
)) {
* Make only looks for a .DEFAULT if the node was never the
* target of an operator, so that's what we do too. If
* a .DEFAULT was given, we substitute its commands for gn's
* commands and set the IMPSRC variable to be the target's name
* The DEFAULT node acts like a transformation rule, in that
* gn also inherits any attributes or sources attached to
Make_HandleUse(DEFAULT
, gn
);
Var_Set (IMPSRC
, Var_Value (TARGET
, gn
, &p1
), gn
);
} else if (Dir_MTime (gn
) == 0) {
* The node wasn't the target of an operator we have no .DEFAULT
* rule to go on and the target doesn't already exist. There's
* nothing more we can do for this branch. If the -k flag wasn't
* given, we stop in our tracks, otherwise we just don't update
* this node's parents so they never get examined.
if (gn
->type
& OP_OPTIONAL
) {
printf ("make: don't know how to make %s (ignored)\n",
printf ("make: don't know how to make %s (continuing)\n",
(*abortProc
) ("make: don't know how to make %s. Stop",
*-----------------------------------------------------------------------
* Handle a pipe becoming readable. Callback function for Rmt_Watch
*-----------------------------------------------------------------------
JobLocalInput(stream
, job
)
int stream
; /* Stream that's ready (ignored) */
Job
*job
; /* Job to which the stream belongs */
#endif /* RMT_WILL_WATCH */
*-----------------------------------------------------------------------
* Execute the shell for the given job. Called from JobStart and
* A shell is executed, outputs is altered and the Job structure added
*-----------------------------------------------------------------------
Job
*job
; /* Job to execute */
int cpid
; /* ID of new child */
printf("Running %s %sly\n", job
->node
->name
,
job
->flags
&JOB_REMOTE
?"remote":"local");
for (i
= 0; argv
[i
] != (char *)NULL
; i
++) {
* Some jobs produce no output and it's disconcerting to have
* no feedback of their running (since they produce no output, the
* banner with their name in it never appears). This is an attempt to
* provide that feedback, even if nothing follows it.
if ((lastNode
!= job
->node
) && (job
->flags
& JOB_FIRST
) &&
!(job
->flags
& JOB_SILENT
))
printf(targFmt
, job
->node
->name
);
if (job
->flags
& JOB_REMOTE
) {
if ((cpid
= vfork()) == -1) {
* Must duplicate the input stream down to the child's input and
* reset it to the beginning (again). Since the stream was marked
* close-on-exec, we must clear that bit in the new input.
(void) dup2(fileno(job
->cmdFILE
), 0);
* Set up the child's output to be routed through the pipe
(void) dup2 (job
->outPipe
, 1);
* We're capturing output in a file, so we duplicate the
* descriptor to the temporary file into the standard
(void) dup2 (job
->outFd
, 1);
* The output channels are marked close on exec. This bit was
* duplicated by the dup2 (on some systems), so we have to clear
* it before routing the shell's error output to the same place as
* We want to switch the child into a different process family so
* we can kill it and all its descendants in one fell swoop,
* by killing its process family, but not commit suicide.
(void) setpgrp(0, getpid());
(void) execv (shellPath
, argv
);
(void) write (2, "Could not execute shell\n",
sizeof ("Could not execute shell"));
if (usePipes
&& (job
->flags
& JOB_FIRST
) ) {
* The first time a job is run for a node, we set the current
* position in the buffer to the beginning and mark another
* stream to watch in the outputs mask
Rmt_Watch(job
->inPipe
, JobLocalInput
, job
);
FD_SET(job
->inPipe
, &outputs
);
#endif /* RMT_WILL_WATCH */
if (job
->flags
& JOB_REMOTE
) {
* XXX: Used to not happen if CUSTOMS. Why?
if (job
->cmdFILE
!= stdout
) {
* Now the job is actually running, add it to the table.
(void)Lst_AtEnd (jobs
, (ClientData
)job
);
*-----------------------------------------------------------------------
* Create the argv needed to execute the shell for a given job.
*-----------------------------------------------------------------------
static char args
[10]; /* For merged arguments */
if ((commandShell
->exit
&& (*commandShell
->exit
!= '-')) ||
(commandShell
->echo
&& (*commandShell
->echo
!= '-')))
* At least one of the flags doesn't have a minus before it, so
* merge them together. Have to do this because the *(&(@*#*&#$#
* Bourne shell thinks its second argument is a file to source.
* Grrrr. Note the ten-character limitation on the combined arguments.
(void)sprintf(args
, "-%s%s",
((job
->flags
& JOB_IGNERR
) ? "" :
(commandShell
->exit
? commandShell
->exit
: "")),
((job
->flags
& JOB_SILENT
) ? "" :
(commandShell
->echo
? commandShell
->echo
: "")));
if (!(job
->flags
& JOB_IGNERR
) && commandShell
->exit
) {
argv
[argc
] = commandShell
->exit
;
if (!(job
->flags
& JOB_SILENT
) && commandShell
->echo
) {
argv
[argc
] = commandShell
->echo
;
argv
[argc
] = (char *)NULL
;
*-----------------------------------------------------------------------
* Restart a job that stopped for some reason.
* jobFull will be set if the job couldn't be run.
*-----------------------------------------------------------------------
Job
*job
; /* Job to restart */
if (job
->flags
& JOB_REMIGRATE
) {
printf("Remigrating %x\n", job
->pid
);
if (nLocal
!= maxLocal
) {
* Job cannot be remigrated, but there's room on the local
* machine, so resume the job and note that another
printf("resuming on local machine\n");
job
->flags
&= ~(JOB_REMIGRATE
|JOB_RESUME
);
* Job cannot be restarted. Mark the table as full and
* place the job back on the list of stopped jobs.
(void)Lst_AtFront(stoppedJobs
, (ClientData
)job
);
printf("Job queue is full.\n");
(void)Lst_AtEnd(jobs
, (ClientData
)job
);
printf("Job queue is full.\n");
} else if (job
->flags
& JOB_RESTART
) {
* Set up the control arguments to the shell. This is based on the
* flags set earlier for this job. If the JOB_IGNERR flag is clear,
* the 'exit' flag of the commandShell is used to cause it to exit
* upon receiving an error. If the JOB_SILENT flag is clear, the
* 'echo' flag of the commandShell is used to get it to start echoing
* as soon as it starts processing commands.
printf("Restarting %s...", job
->node
->name
);
if (((nLocal
>= maxLocal
) && ! (job
->flags
& JOB_SPECIAL
))) {
* Can't be exported and not allowed to run locally -- put it
* back on the hold queue and mark the table full
(void)Lst_AtFront(stoppedJobs
, (ClientData
)job
);
printf("Job queue is full.\n");
* Job may be run locally.
printf("running locally\n");
job
->flags
&= ~JOB_REMOTE
;
* The job has stopped and needs to be restarted. Why it stopped,
printf("Resuming %s...", job
->node
->name
);
if (((job
->flags
& JOB_REMOTE
) ||
(((job
->flags
& JOB_SPECIAL
)) &&
* If the job is remote, it's ok to resume it as long as the
* maximum concurrency won't be exceeded. If it's local and
* we haven't reached the local concurrency limit already (or the
* job must be run locally and maxLocal is 0), it's also ok to
if (job
->flags
& JOB_REMOTE
) {
error
= !Rmt_Signal(job
, SIGCONT
);
#endif /* RMT_WANTS_SIGNALS */
error
= (KILL(job
->pid
, SIGCONT
) != 0);
* Make sure the user knows we've continued the beast and
* actually put the thing in the job table.
job
->flags
|= JOB_CONTINUING
;
status
.w_termsig
= SIGCONT
;
job
->flags
&= ~(JOB_RESUME
|JOB_CONTINUING
);
Error("couldn't resume %s: %s",
job
->node
->name
, strerror(errno
));
* Job cannot be restarted. Mark the table as full and
* place the job back on the list of stopped jobs.
(void)Lst_AtFront(stoppedJobs
, (ClientData
)job
);
printf("Job queue is full.\n");
*-----------------------------------------------------------------------
* Start a target-creation process going for the target described
* JOB_ERROR if there was an error in the commands, JOB_FINISHED
* if there isn't actually anything left to do for the job and
* JOB_RUNNING if the job has been started.
* A new Job node is created and added to the list of running
* jobs. PMake is forked and a child shell created.
*-----------------------------------------------------------------------
JobStart (gn
, flags
, previous
)
GNode
*gn
; /* target to create */
int flags
; /* flags for the job to override normal ones.
* e.g. JOB_SPECIAL or JOB_IGNDOTS */
Job
*previous
; /* The previous Job structure for this node,
register Job
*job
; /* new job descriptor */
char *argv
[4]; /* Argument vector to shell */
static int jobno
= 0; /* job number of catching output in a file */
Boolean cmdsOK
; /* true if the nodes commands were all right */
Boolean local
; /* Set true if the job was run locally */
Boolean noExec
; /* Set true if we decide not to run the job */
if (previous
!= (Job
*)NULL
) {
previous
->flags
&= ~ (JOB_FIRST
|JOB_IGNERR
|JOB_SILENT
|JOB_REMOTE
);
job
= (Job
*) emalloc (sizeof (Job
));
if (job
== (Job
*)NULL
) {
Punt("JobStart out of memory");
job
->tailCmds
= NILLNODE
;
* Set the initial value of the flags for this job based on the global
* ones and the node's attributes... Any flags supplied by the caller
* are also added to the field.
job
->flags
|= JOB_IGNERR
;
job
->flags
|= JOB_SILENT
;
* Check the commands now so any attributes from .DEFAULT have a chance
if (job
->flags
& JOB_FIRST
) {
cmdsOK
= Job_CheckCommands(gn
, Error
);
* If the -n flag wasn't given, we open up OUR (not the child's)
* temporary file to stuff commands in it. The thing is rd/wr so we don't
* need to reopen it to feed it to the shell. If the -n flag *was* given,
* we just set the file to be stdout. Cute, huh?
if ((gn
->type
& OP_MAKE
) || (!noExecute
&& !touchFlag
)) {
* We're serious here, but if the commands were bogus, we're
job
->cmdFILE
= fopen (tfile
, "w+");
if (job
->cmdFILE
== (FILE *) NULL
) {
Punt ("Could not open %s", tfile
);
fcntl(fileno(job
->cmdFILE
), F_SETFD
, 1);
* Send the commands to the command file, flush all its buffers then
* rewind and remove the thing.
* used to be backwards; replace when start doing multiple commands
* Be compatible: If this is the first time for this node,
* verify its commands are ok and open the commands list for
* sequential access by later invocations of JobStart.
* Once that is done, we take the next command off the list
* and print it to the command file. If the command was an
* ellipsis, note that there's nothing more to execute.
if ((job
->flags
&JOB_FIRST
) && (Lst_Open(gn
->commands
) != SUCCESS
)){
LstNode ln
= Lst_Next (gn
->commands
);
JobPrintCommand ((char *)Lst_Datum (ln
), job
))
Lst_Close (gn
->commands
);
if (noExec
&& !(job
->flags
& JOB_FIRST
)) {
* If we're not going to execute anything, the job
* is done and we need to close down the various
* file descriptors we've opened for output, then
* call JobDoOutput to catch the final characters or
* send the file to the screen... Note that the i/o streams
* are only open if this isn't the first job.
* Note also that this could not be done in
* Job_CatchChildren b/c it wasn't clear if there were
* more commands to execute or not...
FD_CLR(job
->inPipe
, &outputs
);
if (job
->outPipe
!= job
->inPipe
) {
(void)close (job
->outPipe
);
(void)close (job
->inPipe
);
(void)close (job
->outFd
);
* We can do all the commands at once. hooray for sanity
Lst_ForEach (gn
->commands
, JobPrintCommand
, (ClientData
)job
);
* If we didn't print out any commands to the shell script,
* there's not much point in executing the shell, is there?
* Not executing anything -- just print all the commands to stdout
* in one fell swoop. This will still set up job->tailCmds correctly.
printf (targFmt
, gn
->name
);
* Only print the commands if they're ok, but don't die if they're
* not -- just let the user know they're bad and keep going. It
* doesn't do any harm in this case and may do some good.
Lst_ForEach(gn
->commands
, JobPrintCommand
, (ClientData
)job
);
* Don't execute the shell, thank you.
* Just touch the target and note that no shell should be executed.
* Set cmdFILE to stdout to make life easier. Check the commands, too,
* but don't die if they're no good -- it does no harm to keep working
Job_Touch (gn
, job
->flags
&JOB_SILENT
);
* If we're not supposed to execute a shell, don't.
* Unlink and close the command file if we opened one
if (job
->cmdFILE
!= stdout
) {
* We only want to work our way up the graph if we aren't here because
* the commands for the job were no good.
if (job
->tailCmds
!= NILLNODE
) {
Lst_ForEachFrom(job
->node
->commands
, job
->tailCmds
,
* Set up the control arguments to the shell. This is based on the flags
* set earlier for this job.
* If we're using pipes to catch output, create the pipe by which we'll
* get the shell's output. If we're using files, print out that we're
* starting a job and then set up its temporary-file name. This is just
* tfile with two extra digits tacked on -- jobno.
if (job
->flags
& JOB_FIRST
) {
(void)fcntl (job
->inPipe
, F_SETFD
, 1);
(void)fcntl (job
->outPipe
, F_SETFD
, 1);
printf ("Remaking `%s'\n", gn
->name
);
sprintf (job
->outFile
, "%s%02d", tfile
, jobno
);
jobno
= (jobno
+ 1) % 100;
job
->outFd
= open(job
->outFile
,O_WRONLY
|O_CREAT
|O_APPEND
,0600);
(void)fcntl (job
->outFd
, F_SETFD
, 1);
if (local
&& (((nLocal
>= maxLocal
) &&
!(job
->flags
& JOB_SPECIAL
) &&
* The job can only be run locally, but we've hit the limit of
* local concurrency, so put the job on hold until some other job
* finishes. Note that the special jobs (.BEGIN, .INTERRUPT and .END)
* may be run locally even when the local limit has been reached
* (e.g. when maxLocal == 0), though they will be exported if at
printf("Can only run job locally.\n");
job
->flags
|= JOB_RESTART
;
(void)Lst_AtEnd(stoppedJobs
, (ClientData
)job
);
if ((nLocal
>= maxLocal
) && local
) {
* If we're running this job locally as a special case (see above),
* at least say the table is full.
printf("Local job queue is full.\n");
*-----------------------------------------------------------------------
* This function is called at different times depending on
* whether the user has specified that output is to be collected
* via pipes or temporary files. In the former case, we are called
* whenever there is something to read on the pipe. We collect more
* output from the given job and store it in the job's outBuf. If
* this makes up a line, we print it tagged by the job's identifier,
* If output has been collected in a temporary file, we open the
* file and read it line by line, transfering it to our own
* output channel until the file is empty. At which point we
* remove the temporary file.
* In both cases, however, we keep our figurative eye out for the
* 'noPrint' line for the shell from which the output came. If
* we recognize a line, we don't print it. If the command is not
* alone on the line (the character after it is not \0 or \n), we
* do print whatever follows it.
* curPos may be shifted as may the contents of outBuf.
*-----------------------------------------------------------------------
JobDoOutput (job
, finish
)
register Job
*job
; /* the job whose output needs printing */
Boolean finish
; /* TRUE if this is the last time we'll be
Boolean gotNL
= FALSE
; /* true if got a newline */
register int nr
; /* number of bytes read */
register int i
; /* auxiliary index into outBuf */
register int max
; /* limit for i (end of current data) */
int nRead
; /* (Temporary) number of bytes read */
FILE *oFILE
; /* Stream pointer to shell's output file */
* Read as many bytes as will fit in the buffer.
nRead
= read (job
->inPipe
, &job
->outBuf
[job
->curPos
],
JOB_BUFSIZE
- job
->curPos
);
perror("JobDoOutput(piperead)");
* If we hit the end-of-file (the job is dead), we must flush its
* remaining output, so pretend we read a newline if there's any
* output remaining in the buffer.
* Also clear the 'finish' flag so we stop looping.
if ((nr
== 0) && (job
->curPos
!= 0)) {
job
->outBuf
[job
->curPos
] = '\n';
* Look for the last newline in the bytes we just got. If there is
* one, break out of the loop with 'i' as its index and gotNL set
for (i
= job
->curPos
+ nr
- 1; i
>= job
->curPos
; i
--) {
if (job
->outBuf
[i
] == '\n') {
} else if (job
->outBuf
[i
] == '\0') {
if (job
->curPos
== JOB_BUFSIZE
) {
* If we've run out of buffer space, we have no choice
* but to print the stuff. sigh.
* Need to send the output to the screen. Null terminate it
* first, overwriting the newline character if there was one.
* So long as the line isn't one we should filter (according
* to the shell description), we print the line, preceeded
* by a target banner if this target isn't the same as the
* one for which we last printed something.
* The rest of the data in the buffer are then shifted down
* to the start of the buffer and curPos is set accordingly.
if (commandShell
->noPrint
) {
ecp
= Str_FindSubstring(job
->outBuf
,
while (ecp
!= (char *)NULL
) {
if (job
->node
!= lastNode
) {
printf (targFmt
, job
->node
->name
);
* The only way there wouldn't be a newline after
* this line is if it were the last in the buffer.
* however, since the non-printable comes after it,
* there must be a newline, so we don't print one.
cp
= ecp
+ commandShell
->noPLen
;
if (cp
!= &job
->outBuf
[i
]) {
* Still more to print, look again after skipping
* the whitespace following the non-printable
while (*cp
== ' ' || *cp
== '\t' || *cp
== '\n') {
ecp
= Str_FindSubstring (cp
,
* There's still more in that thar buffer. This time, though,
* we know there's no newline at the end, so we add one of
if (job
->node
!= lastNode
) {
printf (targFmt
, job
->node
->name
);
/* shift the remaining characters down */
memcpy ( job
->outBuf
, &job
->outBuf
[i
+ 1], max
- (i
+ 1));
job
->curPos
= max
- (i
+ 1);
* We have written everything out, so we just start over
* from the start of the buffer. No copying. No nothing.
* If the finish flag is true, we must loop until we hit
* end-of-file on the pipe. This is guaranteed to happen eventually
* since the other end of the pipe is now closed (we closed it
* explicitly and the child has exited). When we do get an EOF,
* finish will be set FALSE and we'll fall through and out.
* We've been called to retrieve the output of the job from the
* temporary file where it's been squirreled away. This consists of
* opening the file, reading the output line by line, being sure not
* to print the noPrint line for the shell we used, then close and
* remove the temporary file. Very simple.
* Change to read in blocks and do FindSubString type things as for
* pipes? That would allow for "@echo -n..."
oFILE
= fopen (job
->outFile
, "r");
if (oFILE
!= (FILE *) NULL
) {
printf ("Results of making %s:\n", job
->node
->name
);
while (fgets (inLine
, sizeof(inLine
), oFILE
) != NULL
) {
register char *cp
, *ecp
, *endp
;
endp
= inLine
+ strlen(inLine
);
if (commandShell
->noPrint
) {
ecp
= Str_FindSubstring(cp
, commandShell
->noPrint
);
while (ecp
!= (char *)NULL
) {
* The only way there wouldn't be a newline after
* this line is if it were the last in the buffer.
* however, since the non-printable comes after it,
* there must be a newline, so we don't print one.
cp
= ecp
+ commandShell
->noPLen
;
* Still more to print, look again after skipping
* the whitespace following the non-printable
while (*cp
== ' ' || *cp
== '\t' || *cp
== '\n') {
ecp
= Str_FindSubstring(cp
, commandShell
->noPrint
);
* There's still more in that thar buffer. This time, though,
* we know there's no newline at the end, so we add one of
(void) unlink (job
->outFile
);
*-----------------------------------------------------------------------
* Handle the exit of a child. Called from Make_Make.
* The job descriptor is removed from the list of children.
* We do waits, blocking or not, according to the wisdom of our
* caller, until there are no more children to report. For each
* job, call JobFinish to finish things off. This will take care of
* putting jobs on the stoppedJobs queue.
*-----------------------------------------------------------------------
Job_CatchChildren (block
)
Boolean block
; /* TRUE if should block on the wait. */
int pid
; /* pid of dead child */
register Job
*job
; /* job descriptor for dead child */
LstNode jnode
; /* list element for finding job */
union wait status
; /* Exit/termination status */
* Don't even bother if we know there's no one around.
while ((pid
= wait3((int *)&status
, (block
?0:WNOHANG
)|WUNTRACED
,
(struct rusage
*)0)) > 0)
printf("Process %d exited or stopped.\n", pid
);
jnode
= Lst_Find (jobs
, (ClientData
)&pid
, JobCmpPid
);
if (WIFSIGNALED(status
) && (status
.w_termsig
== SIGCONT
)) {
jnode
= Lst_Find(stoppedJobs
, (ClientData
) &pid
, JobCmpPid
);
Error("Resumed child (%d) not in table", pid
);
job
= (Job
*)Lst_Datum(jnode
);
(void)Lst_Remove(stoppedJobs
, jnode
);
Error ("Child (%d) not in table?", pid
);
job
= (Job
*) Lst_Datum (jnode
);
(void)Lst_Remove (jobs
, jnode
);
if (jobFull
&& DEBUG(JOB
)) {
printf("Job queue is no longer full.\n");
*-----------------------------------------------------------------------
* Catch the output from our children, if we're using
* pipes do so. Otherwise just block time until we get a
* signal (most likely a SIGCHLD) since there's no point in
* just spinning when there's nothing to do and the reaping
* of a child can wait for a while.
* Output is read from pipes if we're piping.
* -----------------------------------------------------------------------
int pnJobs
; /* Previous nJobs */
* It is possible for us to be called with nJobs equal to 0. This happens
* if all the jobs finish and a job that is stopped cannot be run
* locally (eg if maxLocal is 0) and cannot be exported. The job will
* be placed back on the stoppedJobs queue, Job_Empty() will return false,
* Make_Run will call us again when there's nothing for which to wait.
* nJobs never changes, so we loop forever. Hence the check. It could
* be argued that we should sleep for a bit so as not to swamp the
* exportation system with requests. Perhaps we should.
* NOTE: IT IS THE RESPONSIBILITY OF Rmt_Wait TO CALL Job_CatchChildren
* IN A TIMELY FASHION TO CATCH ANY LOCALLY RUNNING JOBS THAT EXIT.
* It may use the variable nLocal to determine if it needs to call
* Job_CatchChildren (if nLocal is 0, there's nothing for which to
while (nJobs
!= 0 && pnJobs
== nJobs
) {
timeout
.tv_sec
= SEL_SEC
;
timeout
.tv_usec
= SEL_USEC
;
if ((nfds
= select (FD_SETSIZE
, &readfds
, (fd_set
*) 0, (fd_set
*) 0, &timeout
)) < 0)
if (Lst_Open (jobs
) == FAILURE
) {
Punt ("Cannot open job table");
while (nfds
&& (ln
= Lst_Next (jobs
)) != NILLNODE
) {
job
= (Job
*) Lst_Datum (ln
);
if (FD_ISSET(job
->inPipe
, &readfds
)) {
JobDoOutput (job
, FALSE
);
#endif /* RMT_WILL_WATCH */
*-----------------------------------------------------------------------
* Start the creation of a target. Basically a front-end for
* JobStart used by the Make module.
* Another job is started.
*-----------------------------------------------------------------------
(void)JobStart (gn
, 0, (Job
*)NULL
);
*-----------------------------------------------------------------------
* Initialize the process module
* lists and counters are initialized
*-----------------------------------------------------------------------
Job_Init (maxproc
, maxlocal
)
int maxproc
; /* the greatest number of jobs which may be
int maxlocal
; /* the greatest number of local jobs which may
GNode
*begin
; /* node for commands to do at the very start */
sprintf (tfile
, "/tmp/make%05d", getpid());
stoppedJobs
= Lst_Init(FALSE
);
* If only one job can run at a time, there's no need for a banner,
if (shellPath
== (char *) NULL
) {
* The user didn't specify a shell to use, so we are using the
* default one... Both the absolute path and the last component
* must be set. The last component is taken from the 'name' field
* of the default shell description pointed-to by commandShell.
* All default shells are located in _PATH_DEFSHELLDIR.
shellName
= commandShell
->name
;
shellPath
= str_concat (_PATH_DEFSHELLDIR
, shellName
, STR_ADDSLASH
);
if (commandShell
->exit
== (char *)NULL
) {
if (commandShell
->echo
== (char *)NULL
) {
* Catch the four signals that POSIX specifies if they aren't ignored.
* JobPassSig will take care of calling JobInterrupt if appropriate.
if (signal (SIGINT
, SIG_IGN
) != SIG_IGN
) {
signal (SIGINT
, JobPassSig
);
if (signal (SIGHUP
, SIG_IGN
) != SIG_IGN
) {
signal (SIGHUP
, JobPassSig
);
if (signal (SIGQUIT
, SIG_IGN
) != SIG_IGN
) {
signal (SIGQUIT
, JobPassSig
);
if (signal (SIGTERM
, SIG_IGN
) != SIG_IGN
) {
signal (SIGTERM
, JobPassSig
);
* There are additional signals that need to be caught and passed if
* either the export system wants to be told directly of signals or if
* we're giving each job its own process group (since then it won't get
* signals from the terminal driver as we own the terminal)
#if defined(RMT_WANTS_SIGNALS) || defined(USE_PGRP)
if (signal (SIGTSTP
, SIG_IGN
) != SIG_IGN
) {
signal (SIGTSTP
, JobPassSig
);
if (signal (SIGTTOU
, SIG_IGN
) != SIG_IGN
) {
signal (SIGTTOU
, JobPassSig
);
if (signal (SIGTTIN
, SIG_IGN
) != SIG_IGN
) {
signal (SIGTTIN
, JobPassSig
);
if (signal (SIGWINCH
, SIG_IGN
) != SIG_IGN
) {
signal (SIGWINCH
, JobPassSig
);
begin
= Targ_FindNode (".BEGIN", TARG_NOCREATE
);
JobStart (begin
, JOB_SPECIAL
, (Job
*)0);
Job_CatchChildren (!usePipes
);
#endif /* RMT_WILL_WATCH */
postCommands
= Targ_FindNode (".END", TARG_CREATE
);
*-----------------------------------------------------------------------
* See if the job table is full. It is considered full if it is OR
* if we are in the process of aborting OR if we have
* reached/exceeded our local quota. This prevents any more jobs
* TRUE if the job table is full, FALSE otherwise
*-----------------------------------------------------------------------
return (aborting
|| jobFull
);
*-----------------------------------------------------------------------
* See if the job table is empty. Because the local concurrency may
* be set to 0, it is possible for the job table to become empty,
* while the list of stoppedJobs remains non-empty. In such a case,
* we want to restart as many jobs as we can.
* TRUE if it is. FALSE if it ain't.
* -----------------------------------------------------------------------
if (!Lst_IsEmpty(stoppedJobs
) && !aborting
) {
* The job table is obviously not full if it has no jobs in
* it...Try and restart the stopped jobs.
while (!jobFull
&& !Lst_IsEmpty(stoppedJobs
)) {
JobRestart((Job
*)Lst_DeQueue(stoppedJobs
));
*-----------------------------------------------------------------------
* Find a matching shell in 'shells' given its final component.
* A pointer to the Shell structure.
*-----------------------------------------------------------------------
char *name
; /* Final component of shell path */
register Shell
*sh
; /* Pointer into shells table */
Shell
*match
; /* Longest-matching shell */
eoname
= name
+ strlen (name
);
for (sh
= shells
; sh
->name
!= NULL
; sh
++) {
for (cp1
= eoname
- strlen (sh
->name
), cp2
= sh
->name
;
*cp1
!= '\0' && *cp1
== *cp2
;
} else if (match
== (Shell
*) NULL
||
strlen (match
->name
) < strlen (sh
->name
)) {
return (match
== (Shell
*) NULL
? sh
: match
);
*-----------------------------------------------------------------------
* Parse a shell specification and set up commandShell, shellPath
* and shellName appropriately.
* FAILURE if the specification was incorrect.
* commandShell points to a Shell structure (either predefined or
* created from the shell spec), shellPath is the full path of the
* shell described by commandShell, while shellName is just the
* final component of shellPath.
* A shell specification consists of a .SHELL target, with dependency
* operator, followed by a series of blank-separated words. Double
* quotes can be used to use blanks in words. A backslash escapes
* anything (most notably a double-quote and a space) and
* provides the functionality it does in C. Each word consists of
* keyword and value separated by an equal sign. There should be no
* unnecessary spaces in the word. The keywords are as follows:
* path Location of shell. Overrides "name" if given
* quiet Command to turn off echoing.
* echo Command to turn echoing on
* filter Result of turning off echoing that shouldn't be
* echoFlag Flag to turn echoing on at the start
* errFlag Flag to turn error checking on at the start
* hasErrCtl True if shell has error checking control
* check Command to turn on error checking if hasErrCtl
* is TRUE or template of command to echo a command
* for which error checking is off if hasErrCtl is
* ignore Command to turn off error checking if hasErrCtl
* is TRUE or template of command to execute a
* command so as to ignore any errors it returns if
*-----------------------------------------------------------------------
char *line
; /* The shell spec */
Boolean fullSpec
= FALSE
;
while (isspace (*line
)) {
words
= brk_string (line
, &wordCount
, TRUE
);
memset ((Address
)&newShell
, 0, sizeof(newShell
));
* Parse the specification by keyword
for (path
= (char *)NULL
, argc
= wordCount
- 1, argv
= words
+ 1;
if (strncmp (*argv
, "path=", 5) == 0) {
} else if (strncmp (*argv
, "name=", 5) == 0) {
newShell
.name
= &argv
[0][5];
if (strncmp (*argv
, "quiet=", 6) == 0) {
newShell
.echoOff
= &argv
[0][6];
} else if (strncmp (*argv
, "echo=", 5) == 0) {
newShell
.echoOn
= &argv
[0][5];
} else if (strncmp (*argv
, "filter=", 7) == 0) {
newShell
.noPrint
= &argv
[0][7];
newShell
.noPLen
= strlen(newShell
.noPrint
);
} else if (strncmp (*argv
, "echoFlag=", 9) == 0) {
newShell
.echo
= &argv
[0][9];
} else if (strncmp (*argv
, "errFlag=", 8) == 0) {
newShell
.exit
= &argv
[0][8];
} else if (strncmp (*argv
, "hasErrCtl=", 10) == 0) {
newShell
.hasErrCtl
= !((c
!= 'Y') && (c
!= 'y') &&
(c
!= 'T') && (c
!= 't'));
} else if (strncmp (*argv
, "check=", 6) == 0) {
newShell
.errCheck
= &argv
[0][6];
} else if (strncmp (*argv
, "ignore=", 7) == 0) {
newShell
.ignErr
= &argv
[0][7];
Parse_Error (PARSE_FATAL
, "Unknown keyword \"%s\"",
if (path
== (char *)NULL
) {
* If no path was given, the user wants one of the pre-defined shells,
* yes? So we find the one s/he wants with the help of JobMatchShell
* and set things up the right way. shellPath will be set up by
if (newShell
.name
== (char *)NULL
) {
Parse_Error (PARSE_FATAL
, "Neither path nor name specified");
commandShell
= JobMatchShell (newShell
.name
);
shellName
= newShell
.name
;
* The user provided a path. If s/he gave nothing else (fullSpec is
* FALSE), try and find a matching shell in the ones we know of.
* Else we just take the specification at its word and copy it
* to a new location. In either case, we need to record the
* path the user gave for the shell.
path
= strrchr (path
, '/');
if (path
== (char *)NULL
) {
if (newShell
.name
!= (char *)NULL
) {
shellName
= newShell
.name
;
commandShell
= JobMatchShell (shellName
);
commandShell
= (Shell
*) emalloc(sizeof(Shell
));
*commandShell
= newShell
;
if (commandShell
->echoOn
&& commandShell
->echoOff
) {
commandShell
->hasEchoCtl
= TRUE
;
if (!commandShell
->hasErrCtl
) {
if (commandShell
->errCheck
== (char *)NULL
) {
commandShell
->errCheck
= "";
if (commandShell
->ignErr
== (char *)NULL
) {
commandShell
->ignErr
= "%s\n";
* Do not free up the words themselves, since they might be in use by the
*-----------------------------------------------------------------------
* Handle the receipt of an interrupt.
* All children are killed. Another job will be started if the
* .INTERRUPT target was given.
*-----------------------------------------------------------------------
JobInterrupt (runINTERRUPT
)
int runINTERRUPT
; /* Non-zero if commands for the .INTERRUPT
* target should be executed */
LstNode ln
; /* element in job table */
Job
*job
; /* job descriptor in that element */
GNode
*interrupt
; /* the node describing the .INTERRUPT target */
aborting
= ABORT_INTERRUPT
;
while ((ln
= Lst_Next (jobs
)) != NILLNODE
) {
job
= (Job
*) Lst_Datum (ln
);
if (!Targ_Precious (job
->node
)) {
char *file
= (job
->node
->path
== (char *)NULL
?
if (!noExecute
&& lstat(file
, &st
) != -1 && !S_ISDIR(st
.st_mode
) &&
Error ("*** %s removed", file
);
if (job
->flags
& JOB_REMOTE
) {
* If job is remote, let the Rmt module do the killing.
if (!Rmt_Signal(job
, SIGINT
)) {
* If couldn't kill the thing, finish it out now with an
* error code, since no exit report will come in likely.
#endif /* RMT_WANTS_SIGNALS */
if (runINTERRUPT
&& !touchFlag
) {
interrupt
= Targ_FindNode (".INTERRUPT", TARG_NOCREATE
);
if (interrupt
!= NILGNODE
) {
JobStart (interrupt
, JOB_IGNDOTS
, (Job
*)0);
Job_CatchChildren (!usePipes
);
#endif /* RMT_WILL_WATCH */
*-----------------------------------------------------------------------
* Do final processing such as the running of the commands
* attached to the .END target.
* Number of errors reported.
* The process' temporary file (tfile) is removed if it still
*-----------------------------------------------------------------------
if (postCommands
!= NILGNODE
&& !Lst_IsEmpty (postCommands
->commands
)) {
Error ("Errors reported so .END ignored");
JobStart (postCommands
, JOB_SPECIAL
| JOB_IGNDOTS
,
Job_CatchChildren (!usePipes
);
#endif /* RMT_WILL_WATCH */
*-----------------------------------------------------------------------
* Waits for all running jobs to finish and returns. Sets 'aborting'
* to ABORT_WAIT to prevent other jobs from starting.
* Currently running jobs finish.
*-----------------------------------------------------------------------
Job_CatchChildren(!usePipes
);
#endif /* RMT_WILL_WATCH */
*-----------------------------------------------------------------------
* Abort all currently running jobs without handling output or anything.
* This function is to be called only in the event of a major
* error. Most definitely NOT to be called from JobInterrupt.
* All children are killed, not just the firstborn
*-----------------------------------------------------------------------
LstNode ln
; /* element in job table */
Job
*job
; /* the job descriptor in that element */
while ((ln
= Lst_Next (jobs
)) != NILLNODE
) {
job
= (Job
*) Lst_Datum (ln
);
* kill the child process with increasingly drastic signals to make
if (job
->flags
& JOB_REMOTE
) {
Rmt_Signal(job
, SIGKILL
);
#endif /* RMT_WANTS_SIGNALS */
* Catch as many children as want to report in at first, then give up
while (wait3(&foo
, WNOHANG
, (struct rusage
*)0) > 0)