diff options
author | Simon J. Gerraty <sjg@FreeBSD.org> | 2021-01-14 06:21:37 +0000 |
---|---|---|
committer | Simon J. Gerraty <sjg@FreeBSD.org> | 2021-01-14 06:21:37 +0000 |
commit | 06b9b3e0ad0dc3f0166b3e8f26ced68c271cf527 (patch) | |
tree | 92a02f1874a5dacc12b39edd184602d24888baad /contrib/bmake/job.c | |
parent | 0495ed398c4f64013bab2327eb13a303e1f90c13 (diff) | |
parent | 8e11a9b4250be3c3379c45fa820bff78d99d5946 (diff) | |
download | src-06b9b3e0ad0dc3f0166b3e8f26ced68c271cf527.tar.gz src-06b9b3e0ad0dc3f0166b3e8f26ced68c271cf527.zip |
Merge bmake-20210110
Quite a lot of churn on style, but lots of
good work refactoring complicated functions
and lots more unit-tests.
Thanks mostly to rillig at NetBSD
Some interesting entries from ChangeLog
o .MAKE.{UID,GID} represent uid and gid running make.
o allow env var MAKE_OBJDIR_CHECK_WRITABLE=no to skip writable
checks in InitObjdir. Explicit .OBJDIR target always allows
read-only directory.
o add more unit tests for META MODE
Merge commit '8e11a9b4250be3c3379c45fa820bff78d99d5946' into main
Change-Id: I464fd4c013067f0915671c1ccc96d2d8090b2b9c
Diffstat (limited to 'contrib/bmake/job.c')
-rw-r--r-- | contrib/bmake/job.c | 3893 |
1 files changed, 2028 insertions, 1865 deletions
diff --git a/contrib/bmake/job.c b/contrib/bmake/job.c index 052b9290f9f3..d43761ca80ff 100644 --- a/contrib/bmake/job.c +++ b/contrib/bmake/job.c @@ -1,4 +1,4 @@ -/* $NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $ */ +/* $NetBSD: job.c,v 1.397 2021/01/10 23:59:53 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -156,9 +156,10 @@ #include "trace.h" /* "@(#)job.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $"); +MAKE_RCSID("$NetBSD: job.c,v 1.397 2021/01/10 23:59:53 rillig Exp $"); -/* A shell defines how the commands are run. All commands for a target are +/* + * A shell defines how the commands are run. All commands for a target are * written into a single file, which is then given to the shell to execute * the commands from it. The commands are written to the file using a few * templates for echo control and error control. @@ -174,12 +175,12 @@ MAKE_RCSID("$NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $"); * it is filtered out using noPrint and noPrintLen. * * The error checking for individual commands is controlled using hasErrCtl, - * errOnOrEcho, errOffOrExecIgnore and errExit. + * errOn, errOff and runChkTmpl. * - * If a shell doesn't have error control, errOnOrEcho becomes a printf template - * for echoing the command, should echoing be on; errOffOrExecIgnore becomes + * If a shell doesn't have error control, echoTmpl becomes a printf template + * for echoing the command, should echoing be on; runIgnTmpl becomes * another printf template for executing the command while ignoring the return - * status. Finally errExit is a printf template for running the command and + * status. Finally runChkTmpl is a printf template for running the command and * causing the shell to exit on error. If any of these strings are empty when * hasErrCtl is FALSE, the command will be executed anyway as is, and if it * causes an error, so be it. Any templates set up to echo the command will @@ -193,37 +194,68 @@ MAKE_RCSID("$NetBSD: job.c,v 1.326 2020/11/16 18:28:27 rillig Exp $"); */ typedef struct Shell { - /* The name of the shell. For Bourne and C shells, this is used only to - * find the shell description when used as the single source of a .SHELL - * target. For user-defined shells, this is the full path of the shell. */ - const char *name; + /* + * The name of the shell. For Bourne and C shells, this is used only + * to find the shell description when used as the single source of a + * .SHELL target. For user-defined shells, this is the full path of + * the shell. + */ + const char *name; - Boolean hasEchoCtl; /* True if both echoOff and echoOn defined */ - const char *echoOff; /* command to turn off echo */ - const char *echoOn; /* command to turn it back on again */ - const char *noPrint; /* text to skip when printing output from + Boolean hasEchoCtl; /* whether both echoOff and echoOn are there */ + const char *echoOff; /* command to turn echoing off */ + const char *echoOn; /* command to turn echoing back on */ + const char *noPrint; /* text to skip when printing output from the * shell. This is usually the same as echoOff */ - size_t noPrintLen; /* length of noPrint command */ + size_t noPrintLen; /* length of noPrint command */ + + Boolean hasErrCtl; /* whether error checking can be controlled + * for individual commands */ + const char *errOn; /* command to turn on error checking */ + const char *errOff; /* command to turn off error checking */ + + const char *echoTmpl; /* template to echo a command */ + const char *runIgnTmpl; /* template to run a command + * without error checking */ + const char *runChkTmpl; /* template to run a command + * with error checking */ + + /* string literal that results in a newline character when it appears + * outside of any 'quote' or "quote" characters */ + const char *newline; + char commentChar; /* character used by shell for comment lines */ + + const char *echoFlag; /* shell flag to echo commands */ + const char *errFlag; /* shell flag to exit on error */ +} Shell; - Boolean hasErrCtl; /* set if can control error checking for - * individual commands */ - /* XXX: split into errOn and echoCmd */ - const char *errOnOrEcho; /* template to turn on error checking */ - /* XXX: split into errOff and execIgnore */ - const char *errOffOrExecIgnore; /* template to turn off error checking */ - const char *errExit; /* template to use for testing exit code */ +typedef struct CommandFlags { + /* Whether to echo the command before running it. */ + Boolean echo; - /* string literal that results in a newline character when it appears - * outside of any 'quote' or "quote" characters */ - const char *newline; - char commentChar; /* character used by shell for comment lines */ + /* Run the command even in -n or -N mode. */ + Boolean always; - /* - * command-line flags - */ - const char *echo; /* echo commands */ - const char *exit; /* exit on error */ -} Shell; + /* + * true if we turned error checking off before printing the command + * and need to turn it back on + */ + Boolean ignerr; +} CommandFlags; + +/* + * Write shell commands to a file. + * + * TODO: keep track of whether commands are echoed. + * TODO: keep track of whether error checking is active. + */ +typedef struct ShellWriter { + FILE *f; + + /* we've sent 'set -x' */ + Boolean xtraced; + +} ShellWriter; /* * FreeBSD: traditionally .MAKE is not required to @@ -244,29 +276,25 @@ static int Job_error_token = TRUE; /* * error handling variables */ -static int errors = 0; /* number of errors reported */ +static int job_errors = 0; /* number of errors reported */ typedef enum AbortReason { /* why is the make aborting? */ - ABORT_NONE, - ABORT_ERROR, /* Because of an error */ - ABORT_INTERRUPT, /* Because it was interrupted */ - ABORT_WAIT /* Waiting for jobs to finish */ + ABORT_NONE, + ABORT_ERROR, /* Because of an error */ + ABORT_INTERRUPT, /* Because it was interrupted */ + ABORT_WAIT /* Waiting for jobs to finish */ } AbortReason; static AbortReason aborting = ABORT_NONE; -#define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */ +#define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */ /* * this tracks the number of tokens currently "out" to build jobs. */ int jobTokensRunning = 0; -/* The number of commands actually printed to the shell commands file for - * the current job. Should this number be 0, no shell will be executed. */ -static int numCommands; - typedef enum JobStartResult { - JOB_RUNNING, /* Job is running */ - JOB_ERROR, /* Error in starting the job */ - JOB_FINISHED /* The job is already finished */ + JOB_RUNNING, /* Job is running */ + JOB_ERROR, /* Error in starting the job */ + JOB_FINISHED /* The job is already finished */ } JobStartResult; /* @@ -299,7 +327,7 @@ typedef enum JobStartResult { #define DEFSHELL_INDEX 0 /* DEFSHELL_INDEX_CUSTOM or DEFSHELL_INDEX_SH */ #endif /* !DEFSHELL_INDEX */ -static Shell shells[] = { +static Shell shells[] = { #ifdef DEFSHELL_CUSTOM /* * An sh-compatible shell with a non-standard name. @@ -308,93 +336,103 @@ static Shell shells[] = { * non-portable features that might not be supplied by all * sh-compatible shells. */ -{ - DEFSHELL_CUSTOM, /* .name */ - FALSE, /* .hasEchoCtl */ - "", /* .echoOff */ - "", /* .echoOn */ - "", /* .noPrint */ - 0, /* .noPrintLen */ - FALSE, /* .hasErrCtl */ - "echo \"%s\"\n", /* .errOnOrEcho */ - "%s\n", /* .errOffOrExecIgnore */ - "{ %s \n} || exit $?\n", /* .errExit */ - "'\n'", /* .newline */ - '#', /* .commentChar */ - "", /* .echo */ - "", /* .exit */ -}, + { + DEFSHELL_CUSTOM, /* .name */ + FALSE, /* .hasEchoCtl */ + "", /* .echoOff */ + "", /* .echoOn */ + "", /* .noPrint */ + 0, /* .noPrintLen */ + FALSE, /* .hasErrCtl */ + "", /* .errOn */ + "", /* .errOff */ + "echo \"%s\"\n", /* .echoTmpl */ + "%s\n", /* .runIgnTmpl */ + "{ %s \n} || exit $?\n", /* .runChkTmpl */ + "'\n'", /* .newline */ + '#', /* .commentChar */ + "", /* .echoFlag */ + "", /* .errFlag */ + }, #endif /* DEFSHELL_CUSTOM */ /* * SH description. Echo control is also possible and, under * sun UNIX anyway, one can even control error checking. */ -{ - "sh", /* .name */ - FALSE, /* .hasEchoCtl */ - "", /* .echoOff */ - "", /* .echoOn */ - "", /* .noPrint */ - 0, /* .noPrintLen */ - FALSE, /* .hasErrCtl */ - "echo \"%s\"\n", /* .errOnOrEcho */ - "%s\n", /* .errOffOrExecIgnore */ - "{ %s \n} || exit $?\n", /* .errExit */ - "'\n'", /* .newline */ - '#', /* .commentChar*/ + { + "sh", /* .name */ + FALSE, /* .hasEchoCtl */ + "", /* .echoOff */ + "", /* .echoOn */ + "", /* .noPrint */ + 0, /* .noPrintLen */ + FALSE, /* .hasErrCtl */ + "", /* .errOn */ + "", /* .errOff */ + "echo \"%s\"\n", /* .echoTmpl */ + "%s\n", /* .runIgnTmpl */ + "{ %s \n} || exit $?\n", /* .runChkTmpl */ + "'\n'", /* .newline */ + '#', /* .commentChar*/ #if defined(MAKE_NATIVE) && defined(__NetBSD__) - "q", /* .echo */ + /* XXX: -q is not really echoFlag, it's more like noEchoInSysFlag. */ + "q", /* .echoFlag */ #else - "", /* .echo */ + "", /* .echoFlag */ #endif - "", /* .exit */ -}, + "", /* .errFlag */ + }, /* * KSH description. */ -{ - "ksh", /* .name */ - TRUE, /* .hasEchoCtl */ - "set +v", /* .echoOff */ - "set -v", /* .echoOn */ - "set +v", /* .noPrint */ - 6, /* .noPrintLen */ - FALSE, /* .hasErrCtl */ - "echo \"%s\"\n", /* .errOnOrEcho */ - "%s\n", /* .errOffOrExecIgnore */ - "{ %s \n} || exit $?\n", /* .errExit */ - "'\n'", /* .newline */ - '#', /* .commentChar */ - "v", /* .echo */ - "", /* .exit */ -}, + { + "ksh", /* .name */ + TRUE, /* .hasEchoCtl */ + "set +v", /* .echoOff */ + "set -v", /* .echoOn */ + "set +v", /* .noPrint */ + 6, /* .noPrintLen */ + FALSE, /* .hasErrCtl */ + "", /* .errOn */ + "", /* .errOff */ + "echo \"%s\"\n", /* .echoTmpl */ + "%s\n", /* .runIgnTmpl */ + "{ %s \n} || exit $?\n", /* .runChkTmpl */ + "'\n'", /* .newline */ + '#', /* .commentChar */ + "v", /* .echoFlag */ + "", /* .errFlag */ + }, /* * 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. */ -{ - "csh", /* .name */ - TRUE, /* .hasEchoCtl */ - "unset verbose", /* .echoOff */ - "set verbose", /* .echoOn */ - "unset verbose", /* .noPrint */ - 13, /* .noPrintLen */ - FALSE, /* .hasErrCtl */ - "echo \"%s\"\n", /* .errOnOrEcho */ - /* XXX: Mismatch between errOn and execIgnore */ - "csh -c \"%s || exit 0\"\n", /* .errOffOrExecIgnore */ - "", /* .errExit */ - "'\\\n'", /* .newline */ - '#', /* .commentChar */ - "v", /* .echo */ - "e", /* .exit */ -} + { + "csh", /* .name */ + TRUE, /* .hasEchoCtl */ + "unset verbose", /* .echoOff */ + "set verbose", /* .echoOn */ + "unset verbose", /* .noPrint */ + 13, /* .noPrintLen */ + FALSE, /* .hasErrCtl */ + "", /* .errOn */ + "", /* .errOff */ + "echo \"%s\"\n", /* .echoTmpl */ + "csh -c \"%s || exit 0\"\n", /* .runIgnTmpl */ + "", /* .runChkTmpl */ + "'\\\n'", /* .newline */ + '#', /* .commentChar */ + "v", /* .echoFlag */ + "e", /* .errFlag */ + } }; -/* This is the shell to which we pass all commands in the Makefile. - * It is set by the Job_ParseShell function. */ -static Shell *commandShell = &shells[DEFSHELL_INDEX]; +/* + * This is the shell to which we pass all commands in the Makefile. + * It is set by the Job_ParseShell function. + */ +static Shell *shell = &shells[DEFSHELL_INDEX]; const char *shellPath = NULL; /* full pathname of executable image */ const char *shellName = NULL; /* last component of shellPath */ char *shellErrFlag = NULL; @@ -405,62 +443,82 @@ static Job *job_table; /* The structures that describe them */ static Job *job_table_end; /* job_table + maxJobs */ static unsigned int wantToken; /* we want a token */ static Boolean lurking_children = FALSE; -static Boolean make_suspended = FALSE; /* Whether we've seen a SIGTSTP (etc) */ +static Boolean make_suspended = FALSE; /* Whether we've seen a SIGTSTP (etc) */ /* * Set of descriptors of pipes connected to * the output channels of children */ static struct pollfd *fds = NULL; -static Job **jobfds = NULL; -static nfds_t nfds = 0; +static Job **allJobs = NULL; +static nfds_t nJobs = 0; static void watchfd(Job *); static void clearfd(Job *); -static int readyfd(Job *); +static Boolean readyfd(Job *); -static GNode *lastNode; /* The node for which output was most recently - * produced. */ -static char *targPrefix = NULL; /* What we print at the start of TARG_FMT */ +static char *targPrefix = NULL; /* To identify a job change in the output. */ static Job tokenWaitJob; /* token wait pseudo-job */ static Job childExitJob; /* child exit pseudo-job */ -#define CHILD_EXIT "." -#define DO_JOB_RESUME "R" - -enum { npseudojobs = 2 }; /* number of pseudo-jobs */ +#define CHILD_EXIT "." +#define DO_JOB_RESUME "R" -#define TARG_FMT "%s %s ---\n" /* Default format */ -#define MESSAGE(fp, gn) \ - if (opts.maxJobs != 1 && targPrefix && *targPrefix) \ - (void)fprintf(fp, TARG_FMT, targPrefix, gn->name) +enum { + npseudojobs = 2 /* number of pseudo-jobs */ +}; static sigset_t caught_signals; /* Set of signals we handle */ static void JobDoOutput(Job *, Boolean); -static void JobInterrupt(int, int) MAKE_ATTR_DEAD; +static void JobInterrupt(Boolean, int) MAKE_ATTR_DEAD; static void JobRestartJobs(void); static void JobSigReset(void); +static void +SwitchOutputTo(GNode *gn) +{ + /* The node for which output was most recently produced. */ + static GNode *lastNode = NULL; + + if (gn == lastNode) + return; + lastNode = gn; + + if (opts.maxJobs != 1 && targPrefix != NULL && targPrefix[0] != '\0') + (void)fprintf(stdout, "%s %s ---\n", targPrefix, gn->name); +} + static unsigned nfds_per_job(void) { #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) - if (useMeta) - return 2; + if (useMeta) + return 2; #endif - return 1; + return 1; +} + +void +Job_FlagsToString(const Job *job, char *buf, size_t bufsize) +{ + snprintf(buf, bufsize, "%c%c%c", + job->ignerr ? 'i' : '-', + !job->echo ? 's' : '-', + job->special ? 'S' : '-'); } static void job_table_dump(const char *where) { - Job *job; + Job *job; + char flags[4]; - debug_printf("job table @ %s\n", where); - for (job = job_table; job < job_table_end; job++) { - debug_printf("job %d, status %d, flags %d, pid %d\n", - (int)(job - job_table), job->status, job->flags, job->pid); - } + debug_printf("job table @ %s\n", where); + for (job = job_table; job < job_table_end; job++) { + Job_FlagsToString(job, flags, sizeof flags); + debug_printf("job %d, status %d, flags %s, pid %d\n", + (int)(job - job_table), job->status, flags, job->pid); + } } /* @@ -470,20 +528,20 @@ job_table_dump(const char *where) static void JobDeleteTarget(GNode *gn) { - const char *file; - - if (gn->type & OP_JOIN) - return; - if (gn->type & OP_PHONY) - return; - if (Targ_Precious(gn)) - return; - if (opts.noExecute) - return; - - file = GNode_Path(gn); - if (eunlink(file) != -1) - Error("*** %s removed", file); + const char *file; + + if (gn->type & OP_JOIN) + return; + if (gn->type & OP_PHONY) + return; + if (Targ_Precious(gn)) + return; + if (opts.noExecute) + return; + + file = GNode_Path(gn); + if (eunlink(file) != -1) + Error("*** %s removed", file); } /* @@ -492,7 +550,8 @@ JobDeleteTarget(GNode *gn) * Signal lock routines to get exclusive access. Currently used to * protect `jobs' and `stoppedJobs' list manipulations. */ -static void JobSigLock(sigset_t *omaskp) +static void +JobSigLock(sigset_t *omaskp) { if (sigprocmask(SIG_BLOCK, &caught_signals, omaskp) != 0) { Punt("JobSigLock: sigprocmask: %s", strerror(errno)); @@ -500,7 +559,8 @@ static void JobSigLock(sigset_t *omaskp) } } -static void JobSigUnlock(sigset_t *omaskp) +static void +JobSigUnlock(sigset_t *omaskp) { (void)sigprocmask(SIG_SETMASK, omaskp, NULL); } @@ -508,458 +568,499 @@ static void JobSigUnlock(sigset_t *omaskp) static void JobCreatePipe(Job *job, int minfd) { - int i, fd, flags; - int pipe_fds[2]; - - if (pipe(pipe_fds) == -1) - Punt("Cannot create pipe: %s", strerror(errno)); - - for (i = 0; i < 2; i++) { - /* Avoid using low numbered fds */ - fd = fcntl(pipe_fds[i], F_DUPFD, minfd); - if (fd != -1) { - close(pipe_fds[i]); - pipe_fds[i] = fd; + int i, fd, flags; + int pipe_fds[2]; + + if (pipe(pipe_fds) == -1) + Punt("Cannot create pipe: %s", strerror(errno)); + + for (i = 0; i < 2; i++) { + /* Avoid using low numbered fds */ + fd = fcntl(pipe_fds[i], F_DUPFD, minfd); + if (fd != -1) { + close(pipe_fds[i]); + pipe_fds[i] = fd; + } } - } - job->inPipe = pipe_fds[0]; - job->outPipe = pipe_fds[1]; + job->inPipe = pipe_fds[0]; + job->outPipe = pipe_fds[1]; - /* Set close-on-exec flag for both */ - if (fcntl(job->inPipe, F_SETFD, FD_CLOEXEC) == -1) - Punt("Cannot set close-on-exec: %s", strerror(errno)); - if (fcntl(job->outPipe, F_SETFD, FD_CLOEXEC) == -1) - Punt("Cannot set close-on-exec: %s", strerror(errno)); + /* Set close-on-exec flag for both */ + if (fcntl(job->inPipe, F_SETFD, FD_CLOEXEC) == -1) + Punt("Cannot set close-on-exec: %s", strerror(errno)); + if (fcntl(job->outPipe, F_SETFD, FD_CLOEXEC) == -1) + Punt("Cannot set close-on-exec: %s", strerror(errno)); - /* - * We mark the input side of the pipe non-blocking; we poll(2) the - * pipe when we're waiting for a job token, but we might lose the - * race for the token when a new one becomes available, so the read - * from the pipe should not block. - */ - flags = fcntl(job->inPipe, F_GETFL, 0); - if (flags == -1) - Punt("Cannot get flags: %s", strerror(errno)); - flags |= O_NONBLOCK; - if (fcntl(job->inPipe, F_SETFL, flags) == -1) - Punt("Cannot set flags: %s", strerror(errno)); + /* + * We mark the input side of the pipe non-blocking; we poll(2) the + * pipe when we're waiting for a job token, but we might lose the + * race for the token when a new one becomes available, so the read + * from the pipe should not block. + */ + flags = fcntl(job->inPipe, F_GETFL, 0); + if (flags == -1) + Punt("Cannot get flags: %s", strerror(errno)); + flags |= O_NONBLOCK; + if (fcntl(job->inPipe, F_SETFL, flags) == -1) + Punt("Cannot set flags: %s", strerror(errno)); } /* Pass the signal to each running job. */ static void JobCondPassSig(int signo) { - Job *job; + Job *job; - DEBUG1(JOB, "JobCondPassSig(%d) called.\n", signo); + DEBUG1(JOB, "JobCondPassSig(%d) called.\n", signo); - for (job = job_table; job < job_table_end; job++) { - if (job->status != JOB_ST_RUNNING) - continue; - DEBUG2(JOB, "JobCondPassSig passing signal %d to child %d.\n", - signo, job->pid); - KILLPG(job->pid, signo); - } + for (job = job_table; job < job_table_end; job++) { + if (job->status != JOB_ST_RUNNING) + continue; + DEBUG2(JOB, "JobCondPassSig passing signal %d to child %d.\n", + signo, job->pid); + KILLPG(job->pid, signo); + } } -/* SIGCHLD handler. +/* + * SIGCHLD handler. * - * Sends a token on the child exit pipe to wake us up from select()/poll(). */ + * Sends a token on the child exit pipe to wake us up from select()/poll(). + */ +/*ARGSUSED*/ static void JobChildSig(int signo MAKE_ATTR_UNUSED) { - while (write(childExitJob.outPipe, CHILD_EXIT, 1) == -1 && errno == EAGAIN) - continue; + while (write(childExitJob.outPipe, CHILD_EXIT, 1) == -1 && + errno == EAGAIN) + continue; } /* Resume all stopped jobs. */ +/*ARGSUSED*/ static void JobContinueSig(int signo MAKE_ATTR_UNUSED) { - /* - * Defer sending SIGCONT to our stopped children until we return - * from the signal handler. - */ - while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 && - errno == EAGAIN) - continue; + /* + * Defer sending SIGCONT to our stopped children until we return + * from the signal handler. + */ + while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 && + errno == EAGAIN) + continue; } -/* Pass a signal on to all jobs, then resend to ourselves. - * We die by the same signal. */ +/* + * Pass a signal on to all jobs, then resend to ourselves. + * We die by the same signal. + */ MAKE_ATTR_DEAD static void JobPassSig_int(int signo) { - /* Run .INTERRUPT target then exit */ - JobInterrupt(TRUE, signo); + /* Run .INTERRUPT target then exit */ + JobInterrupt(TRUE, signo); } -/* Pass a signal on to all jobs, then resend to ourselves. - * We die by the same signal. */ +/* + * Pass a signal on to all jobs, then resend to ourselves. + * We die by the same signal. + */ MAKE_ATTR_DEAD static void JobPassSig_term(int signo) { - /* Dont run .INTERRUPT target then exit */ - JobInterrupt(FALSE, signo); + /* Dont run .INTERRUPT target then exit */ + JobInterrupt(FALSE, signo); } static void JobPassSig_suspend(int signo) { - sigset_t nmask, omask; - struct sigaction act; + sigset_t nmask, omask; + struct sigaction act; - /* Suppress job started/continued messages */ - make_suspended = TRUE; + /* Suppress job started/continued messages */ + make_suspended = TRUE; - /* Pass the signal onto every job */ - JobCondPassSig(signo); + /* Pass the signal onto every job */ + JobCondPassSig(signo); - /* - * 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. - */ - sigfillset(&nmask); - sigdelset(&nmask, signo); - (void)sigprocmask(SIG_SETMASK, &nmask, &omask); + /* + * 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. + */ + sigfillset(&nmask); + sigdelset(&nmask, signo); + (void)sigprocmask(SIG_SETMASK, &nmask, &omask); - act.sa_handler = SIG_DFL; - sigemptyset(&act.sa_mask); - act.sa_flags = 0; - (void)sigaction(signo, &act, NULL); + act.sa_handler = SIG_DFL; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + (void)sigaction(signo, &act, NULL); - if (DEBUG(JOB)) - debug_printf("JobPassSig passing signal %d to self.\n", signo); + DEBUG1(JOB, "JobPassSig passing signal %d to self.\n", signo); - (void)kill(getpid(), signo); + (void)kill(getpid(), signo); - /* - * We've been continued. - * - * A whole host of signals continue to happen! - * SIGCHLD for any processes that actually suspended themselves. - * SIGCHLD for any processes that exited while we were alseep. - * The SIGCONT that actually caused us to wakeup. - * - * Since we defer passing the SIGCONT on to our children until - * the main processing loop, we can be sure that all the SIGCHLD - * events will have happened by then - and that the waitpid() will - * collect the child 'suspended' events. - * For correct sequencing we just need to ensure we process the - * waitpid() before passing on the SIGCONT. - * - * In any case nothing else is needed here. - */ + /* + * We've been continued. + * + * A whole host of signals continue to happen! + * SIGCHLD for any processes that actually suspended themselves. + * SIGCHLD for any processes that exited while we were alseep. + * The SIGCONT that actually caused us to wakeup. + * + * Since we defer passing the SIGCONT on to our children until + * the main processing loop, we can be sure that all the SIGCHLD + * events will have happened by then - and that the waitpid() will + * collect the child 'suspended' events. + * For correct sequencing we just need to ensure we process the + * waitpid() before passing on the SIGCONT. + * + * In any case nothing else is needed here. + */ - /* Restore handler and signal mask */ - act.sa_handler = JobPassSig_suspend; - (void)sigaction(signo, &act, NULL); - (void)sigprocmask(SIG_SETMASK, &omask, NULL); + /* Restore handler and signal mask */ + act.sa_handler = JobPassSig_suspend; + (void)sigaction(signo, &act, NULL); + (void)sigprocmask(SIG_SETMASK, &omask, NULL); } static Job * JobFindPid(int pid, JobStatus status, Boolean isJobs) { - Job *job; + Job *job; - for (job = job_table; job < job_table_end; job++) { - if (job->status == status && job->pid == pid) - return job; - } - if (DEBUG(JOB) && isJobs) - job_table_dump("no pid"); - return NULL; + for (job = job_table; job < job_table_end; job++) { + if (job->status == status && job->pid == pid) + return job; + } + if (DEBUG(JOB) && isJobs) + job_table_dump("no pid"); + return NULL; } /* Parse leading '@', '-' and '+', which control the exact execution mode. */ static void -ParseRunOptions( - char **pp, - Boolean *out_shutUp, Boolean *out_errOff, Boolean *out_runAlways) -{ - char *p = *pp; - *out_shutUp = FALSE; - *out_errOff = FALSE; - *out_runAlways = FALSE; - - for (;;) { - if (*p == '@') - *out_shutUp = !DEBUG(LOUD); - else if (*p == '-') - *out_errOff = TRUE; - else if (*p == '+') - *out_runAlways = TRUE; - else - break; - p++; - } +ParseCommandFlags(char **pp, CommandFlags *out_cmdFlags) +{ + char *p = *pp; + out_cmdFlags->echo = TRUE; + out_cmdFlags->ignerr = FALSE; + out_cmdFlags->always = FALSE; + + for (;;) { + if (*p == '@') + out_cmdFlags->echo = DEBUG(LOUD); + else if (*p == '-') + out_cmdFlags->ignerr = TRUE; + else if (*p == '+') + out_cmdFlags->always = TRUE; + else + break; + p++; + } - pp_skip_whitespace(&p); + pp_skip_whitespace(&p); - *pp = p; + *pp = p; } /* Escape a string for a double-quoted string literal in sh, csh and ksh. */ static char * EscapeShellDblQuot(const char *cmd) { - size_t i, j; + size_t i, j; + + /* Worst that could happen is every char needs escaping. */ + char *esc = bmake_malloc(strlen(cmd) * 2 + 1); + for (i = 0, j = 0; cmd[i] != '\0'; i++, j++) { + if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' || + cmd[i] == '"') + esc[j++] = '\\'; + esc[j] = cmd[i]; + } + esc[j] = '\0'; - /* Worst that could happen is every char needs escaping. */ - char *esc = bmake_malloc(strlen(cmd) * 2 + 1); - for (i = 0, j = 0; cmd[i] != '\0'; i++, j++) { - if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' || cmd[i] == '"') - esc[j++] = '\\'; - esc[j] = cmd[i]; - } - esc[j] = '\0'; + return esc; +} + +static void +ShellWriter_PrintFmt(ShellWriter *wr, const char *fmt, const char *arg) +{ + DEBUG1(JOB, fmt, arg); + + (void)fprintf(wr->f, fmt, arg); + /* XXX: Is flushing needed in any case, or only if f == stdout? */ + (void)fflush(wr->f); +} - return esc; +static void +ShellWriter_Println(ShellWriter *wr, const char *line) +{ + ShellWriter_PrintFmt(wr, "%s\n", line); } static void -JobPrintf(Job *job, const char *fmt, const char *arg) +ShellWriter_EchoOff(ShellWriter *wr) { - if (DEBUG(JOB)) - debug_printf(fmt, arg); + if (shell->hasEchoCtl) + ShellWriter_Println(wr, shell->echoOff); +} - (void)fprintf(job->cmdFILE, fmt, arg); - (void)fflush(job->cmdFILE); +static void +ShellWriter_EchoCmd(ShellWriter *wr, const char *escCmd) +{ + ShellWriter_PrintFmt(wr, shell->echoTmpl, escCmd); } static void -JobPrintln(Job *job, const char *line) +ShellWriter_EchoOn(ShellWriter *wr) { - JobPrintf(job, "%s\n", line); + if (shell->hasEchoCtl) + ShellWriter_Println(wr, shell->echoOn); } -/*- - *----------------------------------------------------------------------- - * JobPrintCommand -- - * 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 - * control. - * 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 .END - * node and executed by Job_End when all things are done. - * - * Side Effects: - * If the command begins with a '-' and the shell has no error control, - * the JOB_IGNERR flag is set in the job descriptor. - * numCommands is incremented if the command is actually printed. - *----------------------------------------------------------------------- - */ static void -JobPrintCommand(Job *job, char *cmd) +ShellWriter_TraceOn(ShellWriter *wr) { - const char *const cmdp = cmd; - Boolean noSpecials; /* true if we shouldn't worry about - * inserting special commands into - * the input stream. */ - Boolean shutUp; /* true if we put a no echo command - * into the command file */ - Boolean errOff; /* true if we turned error checking - * off before printing the command - * and need to turn it back on */ - Boolean runAlways; - const char *cmdTemplate; /* Template to use when printing the - * command */ - char *cmdStart; /* Start of expanded command */ - char *escCmd = NULL; /* Command with quotes/backticks escaped */ + if (!wr->xtraced) { + ShellWriter_Println(wr, "set -x"); + wr->xtraced = TRUE; + } +} - noSpecials = !GNode_ShouldExecute(job->node); +static void +ShellWriter_ErrOff(ShellWriter *wr, Boolean echo) +{ + if (echo) + ShellWriter_EchoOff(wr); + ShellWriter_Println(wr, shell->errOff); + if (echo) + ShellWriter_EchoOn(wr); +} - numCommands++; +static void +ShellWriter_ErrOn(ShellWriter *wr, Boolean echo) +{ + if (echo) + ShellWriter_EchoOff(wr); + ShellWriter_Println(wr, shell->errOn); + if (echo) + ShellWriter_EchoOn(wr); +} - Var_Subst(cmd, job->node, VARE_WANTRES, &cmd); - /* TODO: handle errors */ - cmdStart = cmd; +/* + * The shell has no built-in error control, so emulate error control by + * enclosing each shell command in a template like "{ %s \n } || exit $?" + * (configurable per shell). + */ +static void +JobPrintSpecialsEchoCtl(Job *job, ShellWriter *wr, CommandFlags *inout_cmdFlags, + const char *escCmd, const char **inout_cmdTemplate) +{ + /* XXX: Why is the job modified at this point? */ + job->ignerr = TRUE; - cmdTemplate = "%s\n"; + if (job->echo && inout_cmdFlags->echo) { + ShellWriter_EchoOff(wr); + ShellWriter_EchoCmd(wr, escCmd); - ParseRunOptions(&cmd, &shutUp, &errOff, &runAlways); + /* + * Leave echoing off so the user doesn't see the commands + * for toggling the error checking. + */ + inout_cmdFlags->echo = FALSE; + } else { + if (inout_cmdFlags->echo) + ShellWriter_EchoCmd(wr, escCmd); + } + *inout_cmdTemplate = shell->runIgnTmpl; - if (runAlways && noSpecials) { /* - * We're not actually executing anything... - * but this one needs to be - use compat mode just for it. + * The template runIgnTmpl already takes care of ignoring errors, + * so pretend error checking is still on. + * XXX: What effects does this have, and why is it necessary? */ - Compat_RunCommand(cmdp, job->node); - free(cmdStart); - return; - } + inout_cmdFlags->ignerr = FALSE; +} - /* - * If the shell doesn't have error control the alternate echo'ing will - * be done (to avoid showing additional error checking code) - * and this will need the characters '$ ` \ "' escaped - */ +static void +JobPrintSpecials(Job *job, ShellWriter *wr, const char *escCmd, Boolean run, + CommandFlags *inout_cmdFlags, const char **inout_cmdTemplate) +{ + if (!run) { + /* + * If there is no command to run, there is no need to switch + * error checking off and on again for nothing. + */ + inout_cmdFlags->ignerr = FALSE; + } else if (shell->hasErrCtl) + ShellWriter_ErrOff(wr, job->echo && inout_cmdFlags->echo); + else if (shell->runIgnTmpl != NULL && shell->runIgnTmpl[0] != '\0') { + JobPrintSpecialsEchoCtl(job, wr, inout_cmdFlags, escCmd, + inout_cmdTemplate); + } else + inout_cmdFlags->ignerr = FALSE; +} - if (!commandShell->hasErrCtl) - escCmd = EscapeShellDblQuot(cmd); +/* + * Put out another command for the given job. + * + * If the command starts with '@' and neither the -s nor the -n flag was + * given to make, we stick a shell-specific echoOff command in the script. + * + * If the command starts with '-' and the shell has no error control (none + * of the predefined shells has that), we ignore errors for the entire job. + * XXX: Why ignore errors for the entire job? + * XXX: Even ignore errors for the commands before this command? + * + * If the command is just "...", all further commands of this job are skipped + * for now. They are attached to the .END node and will be run by Job_Finish + * after all other targets have been made. + */ +static void +JobPrintCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) +{ + Boolean run; - if (shutUp) { - if (!(job->flags & JOB_SILENT) && !noSpecials && - (commandShell->hasEchoCtl)) { - JobPrintln(job, commandShell->echoOff); - } else { - if (commandShell->hasErrCtl) - shutUp = FALSE; - } - } + CommandFlags cmdFlags; + /* Template for printing a command to the shell file */ + const char *cmdTemplate; + char *xcmd; /* The expanded command */ + char *xcmdStart; + char *escCmd; /* xcmd escaped to be used in double quotes */ + + run = GNode_ShouldExecute(job->node); + + Var_Subst(ucmd, job->node, VARE_WANTRES, &xcmd); + /* TODO: handle errors */ + xcmdStart = xcmd; + + cmdTemplate = "%s\n"; + + ParseCommandFlags(&xcmd, &cmdFlags); - if (errOff) { - if (!noSpecials) { - if (commandShell->hasErrCtl) { + /* The '+' command flag overrides the -n or -N options. */ + if (cmdFlags.always && !run) { /* - * 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 - * it already is? + * We're not actually executing anything... + * but this one needs to be - use compat mode just for it. */ - if (!(job->flags & JOB_SILENT) && !shutUp && - (commandShell->hasEchoCtl)) { - JobPrintln(job, commandShell->echoOff); - JobPrintln(job, commandShell->errOffOrExecIgnore); - JobPrintln(job, commandShell->echoOn); + Compat_RunCommand(ucmd, job->node, ln); + free(xcmdStart); + return; + } + + /* + * If the shell doesn't have error control, the alternate echoing + * will be done (to avoid showing additional error checking code) + * and this needs some characters escaped. + */ + escCmd = shell->hasErrCtl ? NULL : EscapeShellDblQuot(xcmd); + + if (!cmdFlags.echo) { + if (job->echo && run && shell->hasEchoCtl) { + ShellWriter_EchoOff(wr); } else { - JobPrintln(job, commandShell->errOffOrExecIgnore); + if (shell->hasErrCtl) + cmdFlags.echo = TRUE; } - } else if (commandShell->errOffOrExecIgnore && - commandShell->errOffOrExecIgnore[0] != '\0') { + } + + if (cmdFlags.ignerr) { + JobPrintSpecials(job, wr, escCmd, run, &cmdFlags, &cmdTemplate); + } else { + /* - * 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 - * errOnOrEcho 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 errors are being checked and the shell doesn't have + * error control but does supply an runChkTmpl template, then + * set up commands to run through it. */ - job->flags |= JOB_IGNERR; - if (!(job->flags & JOB_SILENT) && !shutUp) { - if (commandShell->hasEchoCtl) { - JobPrintln(job, commandShell->echoOff); - } - JobPrintf(job, commandShell->errOnOrEcho, escCmd); - shutUp = TRUE; - } else { - if (!shutUp) - JobPrintf(job, commandShell->errOnOrEcho, escCmd); + + if (!shell->hasErrCtl && shell->runChkTmpl != NULL && + shell->runChkTmpl[0] != '\0') { + if (job->echo && cmdFlags.echo) { + ShellWriter_EchoOff(wr); + ShellWriter_EchoCmd(wr, escCmd); + cmdFlags.echo = FALSE; + } + /* + * If it's a comment line or blank, avoid the possible + * syntax error generated by "{\n} || exit $?". + */ + cmdTemplate = escCmd[0] == shell->commentChar || + escCmd[0] == '\0' + ? shell->runIgnTmpl + : shell->runChkTmpl; + cmdFlags.ignerr = FALSE; } - cmdTemplate = commandShell->errOffOrExecIgnore; - /* - * The error ignoration (hee hee) is already taken care - * of by the errOffOrExecIgnore template, so pretend error - * checking is still on. - */ - errOff = FALSE; - } else { - errOff = FALSE; - } - } else { - errOff = FALSE; } - } else { - /* - * If errors are being checked and the shell doesn't have error control - * but does supply an errExit template, then set up commands to run - * through it. - */ + if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0) + ShellWriter_TraceOn(wr); - if (!commandShell->hasErrCtl && commandShell->errExit && - commandShell->errExit[0] != '\0') { - if (!(job->flags & JOB_SILENT) && !shutUp) { - if (commandShell->hasEchoCtl) - JobPrintln(job, commandShell->echoOff); - JobPrintf(job, commandShell->errOnOrEcho, escCmd); - shutUp = TRUE; - } - /* If it's a comment line or blank, treat as an ignored error */ - if (escCmd[0] == commandShell->commentChar || - (escCmd[0] == '\0')) - cmdTemplate = commandShell->errOffOrExecIgnore; - else - cmdTemplate = commandShell->errExit; - errOff = FALSE; - } - } + ShellWriter_PrintFmt(wr, cmdTemplate, xcmd); + free(xcmdStart); + free(escCmd); - if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 && - !(job->flags & JOB_TRACED)) { - JobPrintln(job, "set -x"); - job->flags |= JOB_TRACED; - } + if (cmdFlags.ignerr) + ShellWriter_ErrOn(wr, cmdFlags.echo && job->echo); - JobPrintf(job, cmdTemplate, cmd); - free(cmdStart); - free(escCmd); - if (errOff) { - /* - * 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) { - JobPrintln(job, commandShell->echoOff); - shutUp = TRUE; - } - JobPrintln(job, commandShell->errOnOrEcho); - } - if (shutUp && commandShell->hasEchoCtl) - JobPrintln(job, commandShell->echoOn); + if (!cmdFlags.echo) + ShellWriter_EchoOn(wr); } -/* Print all commands to the shell file that is later executed. +/* + * Print all commands to the shell file that is later executed. * * The special command "..." stops printing and saves the remaining commands - * to be executed later. */ -static void + * to be executed later. + * + * Return whether at least one command was written to the shell file. + */ +static Boolean JobPrintCommands(Job *job) { - StringListNode *ln; + StringListNode *ln; + Boolean seen = FALSE; + ShellWriter wr = { job->cmdFILE, FALSE }; + + for (ln = job->node->commands.first; ln != NULL; ln = ln->next) { + const char *cmd = ln->datum; - for (ln = job->node->commands->first; ln != NULL; ln = ln->next) { - const char *cmd = ln->datum; + if (strcmp(cmd, "...") == 0) { + job->node->type |= OP_SAVE_CMDS; + job->tailCmds = ln->next; + break; + } - if (strcmp(cmd, "...") == 0) { - job->node->type |= OP_SAVE_CMDS; - job->tailCmds = ln->next; - break; + JobPrintCommand(job, &wr, ln, ln->datum); + seen = TRUE; } - JobPrintCommand(job, ln->datum); - } + return seen; } /* Save the delayed commands, to be executed when everything else is done. */ static void JobSaveCommands(Job *job) { - StringListNode *node; - - for (node = job->tailCmds; node != NULL; node = node->next) { - const char *cmd = node->datum; - char *expanded_cmd; - /* XXX: This Var_Subst is only intended to expand the dynamic - * variables such as .TARGET, .IMPSRC. It is not intended to - * expand the other variables as well; see deptgt-end.mk. */ - (void)Var_Subst(cmd, job->node, VARE_WANTRES, &expanded_cmd); - /* TODO: handle errors */ - Lst_Append(Targ_GetEndNode()->commands, expanded_cmd); - } + StringListNode *ln; + + for (ln = job->tailCmds; ln != NULL; ln = ln->next) { + const char *cmd = ln->datum; + char *expanded_cmd; + /* XXX: This Var_Subst is only intended to expand the dynamic + * variables such as .TARGET, .IMPSRC. It is not intended to + * expand the other variables as well; see deptgt-end.mk. */ + (void)Var_Subst(cmd, job->node, VARE_WANTRES, &expanded_cmd); + /* TODO: handle errors */ + Lst_Append(&Targ_GetEndNode()->commands, expanded_cmd); + } } @@ -967,21 +1068,81 @@ JobSaveCommands(Job *job) static void JobClosePipes(Job *job) { - clearfd(job); - (void)close(job->outPipe); - job->outPipe = -1; + clearfd(job); + (void)close(job->outPipe); + job->outPipe = -1; + + JobDoOutput(job, TRUE); + (void)close(job->inPipe); + job->inPipe = -1; +} + +static void +JobFinishDoneExitedError(Job *job, WAIT_T *inout_status) +{ + SwitchOutputTo(job->node); +#ifdef USE_META + if (useMeta) { + meta_job_error(job, job->node, + job->ignerr, WEXITSTATUS(*inout_status)); + } +#endif + if (!shouldDieQuietly(job->node, -1)) { + (void)printf("*** [%s] Error code %d%s\n", + job->node->name, WEXITSTATUS(*inout_status), + job->ignerr ? " (ignored)" : ""); + } - JobDoOutput(job, TRUE); - (void)close(job->inPipe); - job->inPipe = -1; + if (job->ignerr) + WAIT_STATUS(*inout_status) = 0; + else { + if (deleteOnError) + JobDeleteTarget(job->node); + PrintOnError(job->node, NULL); + } } -/* Do final processing for the given job including updating parent nodes and +static void +JobFinishDoneExited(Job *job, WAIT_T *inout_status) +{ + DEBUG2(JOB, "Process %d [%s] exited.\n", job->pid, job->node->name); + + if (WEXITSTATUS(*inout_status) != 0) + JobFinishDoneExitedError(job, inout_status); + else if (DEBUG(JOB)) { + SwitchOutputTo(job->node); + (void)printf("*** [%s] Completed successfully\n", + job->node->name); + } +} + +static void +JobFinishDoneSignaled(Job *job, WAIT_T status) +{ + SwitchOutputTo(job->node); + (void)printf("*** [%s] Signal %d\n", job->node->name, WTERMSIG(status)); + if (deleteOnError) + JobDeleteTarget(job->node); +} + +static void +JobFinishDone(Job *job, WAIT_T *inout_status) +{ + if (WIFEXITED(*inout_status)) + JobFinishDoneExited(job, inout_status); + else + JobFinishDoneSignaled(job, *inout_status); + + (void)fflush(stdout); +} + +/* + * Do final processing for the given job including updating parent nodes and * starting new jobs as available/necessary. * * Deferred commands for the job are placed on the .END node. * - * If there was a serious error (errors != 0; not an ignored one), no more + * If there was a serious error (job_errors != 0; not an ignored one), no more * jobs will be started. * * Input: @@ -991,203 +1152,155 @@ JobClosePipes(Job *job) static void JobFinish (Job *job, WAIT_T status) { - Boolean done, return_job_token; + Boolean done, return_job_token; - DEBUG3(JOB, "JobFinish: %d [%s], status %d\n", - job->pid, job->node->name, status); + DEBUG3(JOB, "JobFinish: %d [%s], status %d\n", + job->pid, job->node->name, status); - if ((WIFEXITED(status) && - ((WEXITSTATUS(status) != 0 && !(job->flags & JOB_IGNERR)))) || - WIFSIGNALED(status)) - { - /* - * 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 - * status... - */ - JobClosePipes(job); - if (job->cmdFILE != NULL && job->cmdFILE != stdout) { - (void)fclose(job->cmdFILE); - job->cmdFILE = NULL; - } - done = TRUE; - } else if (WIFEXITED(status)) { - /* - * Deal with ignored errors in -B mode. We need to print a message - * telling of the ignored error as well as to run the next command. - * - */ - done = WEXITSTATUS(status) != 0; - JobClosePipes(job); - } else { - /* - * No need to close things down or anything. - */ - done = FALSE; - } + if ((WIFEXITED(status) && + ((WEXITSTATUS(status) != 0 && !job->ignerr))) || + WIFSIGNALED(status)) { + /* Finished because of an error. */ - if (done) { - if (WIFEXITED(status)) { - DEBUG2(JOB, "Process %d [%s] exited.\n", - job->pid, job->node->name); - if (WEXITSTATUS(status) != 0) { - if (job->node != lastNode) { - MESSAGE(stdout, job->node); - lastNode = job->node; + JobClosePipes(job); + if (job->cmdFILE != NULL && job->cmdFILE != stdout) { + (void)fclose(job->cmdFILE); + job->cmdFILE = NULL; } -#ifdef USE_META - if (useMeta) { - meta_job_error(job, job->node, job->flags, WEXITSTATUS(status)); - } -#endif - if (!shouldDieQuietly(job->node, -1)) - (void)printf("*** [%s] Error code %d%s\n", - job->node->name, - WEXITSTATUS(status), - (job->flags & JOB_IGNERR) ? " (ignored)" : ""); - if (job->flags & JOB_IGNERR) { - WAIT_STATUS(status) = 0; - } else { - if (deleteOnError) { - JobDeleteTarget(job->node); - } - PrintOnError(job->node, NULL); - } - } else if (DEBUG(JOB)) { - if (job->node != lastNode) { - MESSAGE(stdout, job->node); - lastNode = job->node; - } - (void)printf("*** [%s] Completed successfully\n", - job->node->name); - } + done = TRUE; + + } else if (WIFEXITED(status)) { + /* + * Deal with ignored errors in -B mode. We need to print a + * message telling of the ignored error as well as to run + * the next command. + */ + done = WEXITSTATUS(status) != 0; + + JobClosePipes(job); + } else { - if (job->node != lastNode) { - MESSAGE(stdout, job->node); - lastNode = job->node; - } - (void)printf("*** [%s] Signal %d\n", - job->node->name, WTERMSIG(status)); - if (deleteOnError) { - JobDeleteTarget(job->node); - } + /* No need to close things down or anything. */ + done = FALSE; } - (void)fflush(stdout); - } + + if (done) + JobFinishDone(job, &status); #ifdef USE_META - if (useMeta) { - int meta_status = meta_job_finish(job); - if (meta_status != 0 && status == 0) - status = meta_status; - } + if (useMeta) { + int meta_status = meta_job_finish(job); + if (meta_status != 0 && status == 0) + status = meta_status; + } #endif - return_job_token = FALSE; + return_job_token = FALSE; - Trace_Log(JOBEND, job); - if (!(job->flags & JOB_SPECIAL)) { - if (WAIT_STATUS(status) != 0 || - (aborting == ABORT_ERROR) || aborting == ABORT_INTERRUPT) - return_job_token = TRUE; - } + Trace_Log(JOBEND, job); + if (!job->special) { + if (WAIT_STATUS(status) != 0 || + (aborting == ABORT_ERROR) || aborting == ABORT_INTERRUPT) + return_job_token = TRUE; + } - if (aborting != ABORT_ERROR && aborting != ABORT_INTERRUPT && - (WAIT_STATUS(status) == 0)) { - /* - * 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. - */ - JobSaveCommands(job); - job->node->made = MADE; - if (!(job->flags & JOB_SPECIAL)) - return_job_token = TRUE; - Make_Update(job->node); - job->status = JOB_ST_FREE; - } else if (WAIT_STATUS(status)) { - errors++; - job->status = JOB_ST_FREE; - } + if (aborting != ABORT_ERROR && aborting != ABORT_INTERRUPT && + (WAIT_STATUS(status) == 0)) { + /* + * 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. + */ + JobSaveCommands(job); + job->node->made = MADE; + if (!job->special) + return_job_token = TRUE; + Make_Update(job->node); + job->status = JOB_ST_FREE; + } else if (status != 0) { + job_errors++; + job->status = JOB_ST_FREE; + } - if (errors > 0 && !opts.keepgoing && aborting != ABORT_INTERRUPT) - aborting = ABORT_ERROR; /* Prevent more jobs from getting started. */ + if (job_errors > 0 && !opts.keepgoing && aborting != ABORT_INTERRUPT) { + /* Prevent more jobs from getting started. */ + aborting = ABORT_ERROR; + } - if (return_job_token) - Job_TokenReturn(); + if (return_job_token) + Job_TokenReturn(); - if (aborting == ABORT_ERROR && jobTokensRunning == 0) - Finish(errors); + if (aborting == ABORT_ERROR && jobTokensRunning == 0) + Finish(job_errors); } static void TouchRegular(GNode *gn) { - const char *file = GNode_Path(gn); - struct utimbuf times = { now, now }; - int fd; - char c; - - if (utime(file, ×) >= 0) - return; - - fd = open(file, O_RDWR | O_CREAT, 0666); - if (fd < 0) { - (void)fprintf(stderr, "*** couldn't touch %s: %s\n", - file, strerror(errno)); - (void)fflush(stderr); - return; /* XXX: What about propagating the error? */ - } + const char *file = GNode_Path(gn); + struct utimbuf times = { now, now }; + int fd; + char c; + + if (utime(file, ×) >= 0) + return; + + fd = open(file, O_RDWR | O_CREAT, 0666); + if (fd < 0) { + (void)fprintf(stderr, "*** couldn't touch %s: %s\n", + file, strerror(errno)); + (void)fflush(stderr); + return; /* XXX: What about propagating the error? */ + } - /* Last resort: update the file's time stamps in the traditional way. - * XXX: This doesn't work for empty files, which are sometimes used - * as marker files. */ - if (read(fd, &c, 1) == 1) { - (void)lseek(fd, 0, SEEK_SET); - while (write(fd, &c, 1) == -1 && errno == EAGAIN) - continue; - } - (void)close(fd); /* XXX: What about propagating the error? */ + /* Last resort: update the file's time stamps in the traditional way. + * XXX: This doesn't work for empty files, which are sometimes used + * as marker files. */ + if (read(fd, &c, 1) == 1) { + (void)lseek(fd, 0, SEEK_SET); + while (write(fd, &c, 1) == -1 && errno == EAGAIN) + continue; + } + (void)close(fd); /* XXX: What about propagating the error? */ } -/* Touch the given target. Called by JobStart when the -t flag was given. +/* + * Touch the given target. Called by JobStart when the -t flag was given. * * The modification date of the file is changed. - * If the file did not exist, it is created. */ + * If the file did not exist, it is created. + */ void -Job_Touch(GNode *gn, Boolean silent) +Job_Touch(GNode *gn, Boolean echo) { - if (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC|OP_OPTIONAL| - OP_SPECIAL|OP_PHONY)) { - /* These are "virtual" targets and should not really be created. */ - return; - } - - if (!silent || !GNode_ShouldExecute(gn)) { - (void)fprintf(stdout, "touch %s\n", gn->name); - (void)fflush(stdout); - } - - if (!GNode_ShouldExecute(gn)) - return; + if (gn->type & + (OP_JOIN | OP_USE | OP_USEBEFORE | OP_EXEC | OP_OPTIONAL | + OP_SPECIAL | OP_PHONY)) { + /* + * These are "virtual" targets and should not really be + * created. + */ + return; + } - if (gn->type & OP_ARCHV) { - Arch_Touch(gn); - return; - } + if (echo || !GNode_ShouldExecute(gn)) { + (void)fprintf(stdout, "touch %s\n", gn->name); + (void)fflush(stdout); + } - if (gn->type & OP_LIB) { - Arch_TouchLib(gn); - return; - } + if (!GNode_ShouldExecute(gn)) + return; - TouchRegular(gn); + if (gn->type & OP_ARCHV) + Arch_Touch(gn); + else if (gn->type & OP_LIB) + Arch_TouchLib(gn); + else + TouchRegular(gn); } -/* Make sure the given node has all the commands it needs. +/* + * Make sure the given node has all the commands it needs. * * The node will have commands from the .DEFAULT rule added to it if it * needs them. @@ -1202,505 +1315,499 @@ Job_Touch(GNode *gn, Boolean silent) Boolean Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) { - if (GNode_IsTarget(gn)) - return TRUE; - if (!Lst_IsEmpty(gn->commands)) - return TRUE; - if ((gn->type & OP_LIB) && !Lst_IsEmpty(gn->children)) - return TRUE; + if (GNode_IsTarget(gn)) + return TRUE; + if (!Lst_IsEmpty(&gn->commands)) + return TRUE; + if ((gn->type & OP_LIB) && !Lst_IsEmpty(&gn->children)) + return TRUE; - /* - * No commands. Look for .DEFAULT rule from which we might infer - * commands. - */ - if (defaultNode != NULL && !Lst_IsEmpty(defaultNode->commands) && - !(gn->type & OP_SPECIAL)) { /* - * The traditional Make only looks for a .DEFAULT if the node was - * never the target of an operator, so that's what we do too. - * - * The .DEFAULT node acts like a transformation rule, in that - * gn also inherits any attributes or sources attached to - * .DEFAULT itself. + * No commands. Look for .DEFAULT rule from which we might infer + * commands. */ - Make_HandleUse(defaultNode, gn); - Var_Set(IMPSRC, GNode_VarTarget(gn), gn); - return TRUE; - } + if (defaultNode != NULL && !Lst_IsEmpty(&defaultNode->commands) && + !(gn->type & OP_SPECIAL)) { + /* + * The traditional Make only looks for a .DEFAULT if the node + * was never the target of an operator, so that's what we do + * too. + * + * The .DEFAULT node acts like a transformation rule, in that + * gn also inherits any attributes or sources attached to + * .DEFAULT itself. + */ + Make_HandleUse(defaultNode, gn); + Var_Set(IMPSRC, GNode_VarTarget(gn), gn); + return TRUE; + } - Dir_UpdateMTime(gn, FALSE); - if (gn->mtime != 0 || (gn->type & OP_SPECIAL)) - return TRUE; + Dir_UpdateMTime(gn, FALSE); + if (gn->mtime != 0 || (gn->type & OP_SPECIAL)) + return TRUE; - /* - * 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. - */ + /* + * 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->flags & FROM_DEPEND) { - if (!Job_RunTarget(".STALE", gn->fname)) - fprintf(stdout, "%s: %s, %d: ignoring stale %s for %s\n", - progname, gn->fname, gn->lineno, makeDependfile, - gn->name); - return TRUE; - } + if (gn->flags & FROM_DEPEND) { + if (!Job_RunTarget(".STALE", gn->fname)) + fprintf(stdout, + "%s: %s, %d: ignoring stale %s for %s\n", + progname, gn->fname, gn->lineno, makeDependfile, + gn->name); + return TRUE; + } - if (gn->type & OP_OPTIONAL) { - (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n", - progname, gn->name, "ignored"); - (void)fflush(stdout); - return TRUE; - } + if (gn->type & OP_OPTIONAL) { + (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n", + progname, gn->name, "ignored"); + (void)fflush(stdout); + return TRUE; + } - if (opts.keepgoing) { - (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n", - progname, gn->name, "continuing"); - (void)fflush(stdout); - return FALSE; - } + if (opts.keepgoing) { + (void)fprintf(stdout, "%s: don't know how to make %s (%s)\n", + progname, gn->name, "continuing"); + (void)fflush(stdout); + return FALSE; + } - abortProc("%s: don't know how to make %s. Stop", progname, gn->name); - return FALSE; + abortProc("%s: don't know how to make %s. Stop", progname, gn->name); + return FALSE; } -/* Execute the shell for the given job. +/* + * Execute the shell for the given job. * - * See Job_CatchOutput for handling the output of the shell. */ + * See Job_CatchOutput for handling the output of the shell. + */ static void JobExec(Job *job, char **argv) { - int cpid; /* ID of new child */ - sigset_t mask; - - job->flags &= ~JOB_TRACED; + int cpid; /* ID of new child */ + sigset_t mask; - if (DEBUG(JOB)) { - int i; + if (DEBUG(JOB)) { + int i; - debug_printf("Running %s\n", job->node->name); - debug_printf("\tCommand: "); - for (i = 0; argv[i] != NULL; i++) { - debug_printf("%s ", argv[i]); + debug_printf("Running %s\n", job->node->name); + debug_printf("\tCommand: "); + for (i = 0; argv[i] != NULL; i++) { + debug_printf("%s ", argv[i]); + } + debug_printf("\n"); } - debug_printf("\n"); - } - /* - * 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_SILENT)) { - MESSAGE(stdout, job->node); - lastNode = job->node; - } + /* + * 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 (job->echo) + SwitchOutputTo(job->node); - /* No interruptions until this job is on the `jobs' list */ - JobSigLock(&mask); + /* No interruptions until this job is on the `jobs' list */ + JobSigLock(&mask); + + /* Pre-emptively mark job running, pid still zero though */ + job->status = JOB_ST_RUNNING; - /* Pre-emptively mark job running, pid still zero though */ - job->status = JOB_ST_RUNNING; + Var_ReexportVars(); - cpid = vFork(); - if (cpid == -1) - Punt("Cannot vfork: %s", strerror(errno)); + cpid = vFork(); + if (cpid == -1) + Punt("Cannot vfork: %s", strerror(errno)); - if (cpid == 0) { - /* Child */ - sigset_t tmask; + if (cpid == 0) { + /* Child */ + sigset_t tmask; #ifdef USE_META - if (useMeta) { - meta_job_child(job); - } + if (useMeta) { + meta_job_child(job); + } #endif - /* - * Reset all signal handlers; this is necessary because we also - * need to unblock signals before we exec(2). - */ - JobSigReset(); + /* + * Reset all signal handlers; this is necessary because we + * also need to unblock signals before we exec(2). + */ + JobSigReset(); - /* Now unblock signals */ - sigemptyset(&tmask); - JobSigUnlock(&tmask); + /* Now unblock signals */ + sigemptyset(&tmask); + JobSigUnlock(&tmask); - /* - * 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. - */ - if (dup2(fileno(job->cmdFILE), 0) == -1) - execDie("dup2", "job->cmdFILE"); - if (fcntl(0, F_SETFD, 0) == -1) - execDie("fcntl clear close-on-exec", "stdin"); - if (lseek(0, 0, SEEK_SET) == -1) - execDie("lseek to 0", "stdin"); - - if (Always_pass_job_queue || - (job->node->type & (OP_MAKE | OP_SUBMAKE))) { - /* - * Pass job token pipe to submakes. - */ - if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1) - execDie("clear close-on-exec", "tokenWaitJob.inPipe"); - if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1) - execDie("clear close-on-exec", "tokenWaitJob.outPipe"); - } + /* + * 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. + */ + if (dup2(fileno(job->cmdFILE), 0) == -1) + execDie("dup2", "job->cmdFILE"); + if (fcntl(0, F_SETFD, 0) == -1) + execDie("fcntl clear close-on-exec", "stdin"); + if (lseek(0, 0, SEEK_SET) == -1) + execDie("lseek to 0", "stdin"); + + if (Always_pass_job_queue || + (job->node->type & (OP_MAKE | OP_SUBMAKE))) { + /* + * Pass job token pipe to submakes. + */ + if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1) + execDie("clear close-on-exec", + "tokenWaitJob.inPipe"); + if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1) + execDie("clear close-on-exec", + "tokenWaitJob.outPipe"); + } - /* - * Set up the child's output to be routed through the pipe - * we've created for it. - */ - if (dup2(job->outPipe, 1) == -1) - execDie("dup2", "job->outPipe"); + /* + * Set up the child's output to be routed through the pipe + * we've created for it. + */ + if (dup2(job->outPipe, 1) == -1) + execDie("dup2", "job->outPipe"); - /* - * 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 - * its standard output. - */ - if (fcntl(1, F_SETFD, 0) == -1) - execDie("clear close-on-exec", "stdout"); - if (dup2(1, 2) == -1) - execDie("dup2", "1, 2"); + /* + * 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 its standard output. + */ + if (fcntl(1, F_SETFD, 0) == -1) + execDie("clear close-on-exec", "stdout"); + if (dup2(1, 2) == -1) + execDie("dup2", "1, 2"); - /* - * 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. - */ + /* + * 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. + */ #if defined(HAVE_SETPGID) - (void)setpgid(0, getpid()); + (void)setpgid(0, getpid()); #else #if defined(HAVE_SETSID) - /* XXX: dsl - I'm sure this should be setpgrp()... */ - (void)setsid(); + /* XXX: dsl - I'm sure this should be setpgrp()... */ + (void)setsid(); #else - (void)setpgrp(0, getpid()); + (void)setpgrp(0, getpid()); #endif #endif - Var_ExportVars(); - - (void)execv(shellPath, argv); - execDie("exec", shellPath); - } + (void)execv(shellPath, argv); + execDie("exec", shellPath); + } - /* Parent, continuing after the child exec */ - job->pid = cpid; + /* Parent, continuing after the child exec */ + job->pid = cpid; - Trace_Log(JOBSTART, job); + Trace_Log(JOBSTART, job); #ifdef USE_META - if (useMeta) { - meta_job_parent(job, cpid); - } + if (useMeta) { + meta_job_parent(job, cpid); + } #endif - /* - * Set the current position in the buffer to the beginning - * and mark another stream to watch in the outputs mask - */ - job->curPos = 0; + /* + * Set the current position in the buffer to the beginning + * and mark another stream to watch in the outputs mask + */ + job->curPos = 0; - watchfd(job); + watchfd(job); - if (job->cmdFILE != NULL && job->cmdFILE != stdout) { - (void)fclose(job->cmdFILE); - job->cmdFILE = NULL; - } + if (job->cmdFILE != NULL && job->cmdFILE != stdout) { + (void)fclose(job->cmdFILE); + job->cmdFILE = NULL; + } - /* - * Now the job is actually running, add it to the table. - */ - if (DEBUG(JOB)) { - debug_printf("JobExec(%s): pid %d added to jobs table\n", - job->node->name, job->pid); - job_table_dump("job started"); - } - JobSigUnlock(&mask); + /* + * Now the job is actually running, add it to the table. + */ + if (DEBUG(JOB)) { + debug_printf("JobExec(%s): pid %d added to jobs table\n", + job->node->name, job->pid); + job_table_dump("job started"); + } + JobSigUnlock(&mask); } /* Create the argv needed to execute the shell for a given job. */ static void JobMakeArgv(Job *job, char **argv) { - int argc; - static char args[10]; /* For merged arguments */ + int argc; + static char args[10]; /* For merged arguments */ - argv[0] = UNCONST(shellName); - argc = 1; + argv[0] = UNCONST(shellName); + argc = 1; - if ((commandShell->exit && commandShell->exit[0] != '-') || - (commandShell->echo && commandShell->echo[0] != '-')) - { - /* - * 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)snprintf(args, sizeof args, "-%s%s", - ((job->flags & JOB_IGNERR) ? "" : - (commandShell->exit ? commandShell->exit : "")), - ((job->flags & JOB_SILENT) ? "" : - (commandShell->echo ? commandShell->echo : ""))); - - if (args[1]) { - argv[argc] = args; - argc++; - } - } else { - if (!(job->flags & JOB_IGNERR) && commandShell->exit) { - argv[argc] = UNCONST(commandShell->exit); - argc++; - } - if (!(job->flags & JOB_SILENT) && commandShell->echo) { - argv[argc] = UNCONST(commandShell->echo); - argc++; + if ((shell->errFlag != NULL && shell->errFlag[0] != '-') || + (shell->echoFlag != NULL && shell->echoFlag[0] != '-')) { + /* + * 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. + * + * TODO: Research until when the above comments were + * practically relevant. + */ + (void)snprintf(args, sizeof args, "-%s%s", + (job->ignerr ? "" : + (shell->errFlag != NULL ? shell->errFlag : "")), + (!job->echo ? "" : + (shell->echoFlag != NULL ? shell->echoFlag : ""))); + + if (args[1] != '\0') { + argv[argc] = args; + argc++; + } + } else { + if (!job->ignerr && shell->errFlag != NULL) { + argv[argc] = UNCONST(shell->errFlag); + argc++; + } + if (job->echo && shell->echoFlag != NULL) { + argv[argc] = UNCONST(shell->echoFlag); + argc++; + } } - } - argv[argc] = NULL; + argv[argc] = NULL; } -/*- - *----------------------------------------------------------------------- - * JobStart -- - * Start a target-creation process going for the target described - * by the graph node gn. - * - * Input: - * gn target to create - * flags flags for the job to override normal ones. - * previous The previous Job structure for this node, if any. - * - * Results: - * 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. - * - * Side Effects: - * A new Job node is created and added to the list of running - * jobs. PMake is forked and a child shell created. - * - * NB: The return value is ignored by everyone. - *----------------------------------------------------------------------- - */ -static JobStartResult -JobStart(GNode *gn, JobFlags flags) +static void +JobOpenTmpFile(Job *job, GNode *gn, Boolean cmdsOK, Boolean *out_run) { - Job *job; /* new job descriptor */ - char *argv[10]; /* Argument vector to shell */ - Boolean cmdsOK; /* true if the nodes commands were all right */ - Boolean noExec; /* Set true if we decide not to run the job */ - int tfd; /* File descriptor to the temp file */ - - for (job = job_table; job < job_table_end; job++) { - if (job->status == JOB_ST_FREE) - break; - } - if (job >= job_table_end) - Punt("JobStart no job slots vacant"); - - memset(job, 0, sizeof *job); - job->node = gn; - job->tailCmds = NULL; - job->status = JOB_ST_SET_UP; - - if (gn->type & OP_SPECIAL) - flags |= JOB_SPECIAL; - if (Targ_Ignore(gn)) - flags |= JOB_IGNERR; - if (Targ_Silent(gn)) - flags |= JOB_SILENT; - job->flags = flags; - - /* - * Check the commands now so any attributes from .DEFAULT have a chance - * to migrate to the node - */ - cmdsOK = Job_CheckCommands(gn, Error); - - job->inPollfd = NULL; - /* - * 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) && !opts.noRecursiveExecute) || - (!opts.noExecute && !opts.touchFlag)) { /* - * tfile is the name of a file into which all shell commands are - * put. It is removed before the child shell is executed, unless - * DEBUG(SCRIPT) is set. + * tfile is the name of a file into which all shell commands + * are put. It is removed before the child shell is executed, + * unless DEBUG(SCRIPT) is set. */ char *tfile; sigset_t mask; + int tfd; /* File descriptor to the temp file */ + /* * We're serious here, but if the commands were bogus, we're * also dead... */ if (!cmdsOK) { - PrintOnError(gn, NULL); /* provide some clue */ - DieHorribly(); + PrintOnError(gn, NULL); /* provide some clue */ + DieHorribly(); } JobSigLock(&mask); tfd = mkTempFile(TMPPAT, &tfile); if (!DEBUG(SCRIPT)) - (void)eunlink(tfile); + (void)eunlink(tfile); JobSigUnlock(&mask); job->cmdFILE = fdopen(tfd, "w+"); if (job->cmdFILE == NULL) - Punt("Could not fdopen %s", tfile); + Punt("Could not fdopen %s", tfile); (void)fcntl(fileno(job->cmdFILE), F_SETFD, FD_CLOEXEC); /* - * Send the commands to the command file, flush all its buffers then - * rewind and remove the thing. + * Send the commands to the command file, flush all its + * buffers then rewind and remove the thing. */ - noExec = FALSE; + *out_run = TRUE; #ifdef USE_META if (useMeta) { - meta_job_start(job, gn); - if (Targ_Silent(gn)) /* might have changed */ - job->flags |= JOB_SILENT; + meta_job_start(job, gn); + if (gn->type & OP_SILENT) /* might have changed */ + job->echo = FALSE; } #endif - /* - * We can do all the commands at once. hooray for sanity - */ - numCommands = 0; - JobPrintCommands(job); - /* - * If we didn't print out any commands to the shell script, - * there's not much point in executing the shell, is there? - */ - if (numCommands == 0) { - noExec = TRUE; - } + /* We can do all the commands at once. hooray for sanity */ + if (!JobPrintCommands(job)) + *out_run = FALSE; free(tfile); - } else if (!GNode_ShouldExecute(gn)) { - /* - * Not executing anything -- just print all the commands to stdout - * in one fell swoop. This will still set up job->tailCmds correctly. - */ - if (lastNode != gn) { - MESSAGE(stdout, gn); - lastNode = gn; +} + +/* + * Start a target-creation process going for the target described by the + * graph node gn. + * + * Input: + * gn target to create + * flags flags for the job to override normal ones. + * previous The previous Job structure for this node, if any. + * + * Results: + * 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. + * + * Side Effects: + * A new Job node is created and added to the list of running + * jobs. PMake is forked and a child shell created. + * + * NB: The return value is ignored by everyone. + */ +static JobStartResult +JobStart(GNode *gn, Boolean special) +{ + Job *job; /* new job descriptor */ + char *argv[10]; /* Argument vector to shell */ + Boolean cmdsOK; /* true if the nodes commands were all right */ + Boolean run; + + for (job = job_table; job < job_table_end; job++) { + if (job->status == JOB_ST_FREE) + break; } - job->cmdFILE = stdout; - /* - * 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. - */ - if (cmdsOK) - JobPrintCommands(job); - /* - * Don't execute the shell, thank you. - */ - noExec = TRUE; - } else { + if (job >= job_table_end) + Punt("JobStart no job slots vacant"); + + memset(job, 0, sizeof *job); + job->node = gn; + job->tailCmds = NULL; + job->status = JOB_ST_SET_UP; + + job->special = special || gn->type & OP_SPECIAL; + job->ignerr = opts.ignoreErrors || gn->type & OP_IGNORE; + job->echo = !(opts.beSilent || gn->type & OP_SILENT); + /* - * 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 - * up the graph. + * Check the commands now so any attributes from .DEFAULT have a + * chance to migrate to the node. */ - job->cmdFILE = stdout; - Job_Touch(gn, job->flags & JOB_SILENT); - noExec = TRUE; - } - /* Just in case it isn't already... */ - (void)fflush(job->cmdFILE); + cmdsOK = Job_CheckCommands(gn, Error); - /* - * If we're not supposed to execute a shell, don't. - */ - if (noExec) { - if (!(job->flags & JOB_SPECIAL)) - Job_TokenReturn(); + job->inPollfd = NULL; /* - * Unlink and close the command file if we opened one + * 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 (job->cmdFILE != NULL && job->cmdFILE != stdout) { - (void)fclose(job->cmdFILE); - job->cmdFILE = NULL; + if (((gn->type & OP_MAKE) && !opts.noRecursiveExecute) || + (!opts.noExecute && !opts.touchFlag)) { + JobOpenTmpFile(job, gn, cmdsOK, &run); + } else if (!GNode_ShouldExecute(gn)) { + /* + * Not executing anything -- just print all the commands to + * stdout in one fell swoop. This will still set up + * job->tailCmds correctly. + */ + SwitchOutputTo(gn); + job->cmdFILE = stdout; + /* + * 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. + */ + if (cmdsOK) + JobPrintCommands(job); + /* Don't execute the shell, thank you. */ + run = FALSE; + } else { + /* + * 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 up the graph. + */ + job->cmdFILE = stdout; + Job_Touch(gn, job->echo); + run = FALSE; } + /* Just in case it isn't already... */ + (void)fflush(job->cmdFILE); + + /* If we're not supposed to execute a shell, don't. */ + if (!run) { + if (!job->special) + Job_TokenReturn(); + /* Unlink and close the command file if we opened one */ + if (job->cmdFILE != NULL && job->cmdFILE != stdout) { + (void)fclose(job->cmdFILE); + job->cmdFILE = NULL; + } - /* - * 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 (cmdsOK && aborting == ABORT_NONE) { - JobSaveCommands(job); - job->node->made = MADE; - Make_Update(job->node); + /* + * 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 (cmdsOK && aborting == ABORT_NONE) { + JobSaveCommands(job); + job->node->made = MADE; + Make_Update(job->node); + } + job->status = JOB_ST_FREE; + return cmdsOK ? JOB_FINISHED : JOB_ERROR; } - job->status = JOB_ST_FREE; - return cmdsOK ? JOB_FINISHED : JOB_ERROR; - } - /* - * Set up the control arguments to the shell. This is based on the flags - * set earlier for this job. - */ - JobMakeArgv(job, argv); + /* + * Set up the control arguments to the shell. This is based on the + * flags set earlier for this job. + */ + JobMakeArgv(job, argv); - /* Create the pipe by which we'll get the shell's output. */ - JobCreatePipe(job, 3); + /* Create the pipe by which we'll get the shell's output. */ + JobCreatePipe(job, 3); - JobExec(job, argv); - return JOB_RUNNING; + JobExec(job, argv); + return JOB_RUNNING; } -/* Print the output of the shell command, skipping the noPrint command of - * the shell, if any. */ +/* + * Print the output of the shell command, skipping the noPrint text of the + * shell, if any. The default shell does not have noPrint though, which means + * that in all practical cases, handling the output is left to the caller. + */ static char * -JobOutput(Job *job, char *cp, char *endp) +JobOutput(char *cp, char *endp) /* XXX: should all be const */ { - char *ecp; + char *ecp; /* XXX: should be const */ - if (commandShell->noPrint == NULL || commandShell->noPrint[0] == '\0') - return cp; + if (shell->noPrint == NULL || shell->noPrint[0] == '\0') + return cp; - while ((ecp = strstr(cp, commandShell->noPrint)) != NULL) { - if (ecp != cp) { - *ecp = '\0'; - /* - * 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. - */ - (void)fprintf(stdout, "%s", cp); - (void)fflush(stdout); - } - cp = ecp + commandShell->noPrintLen; - if (cp != endp) { - /* - * Still more to print, look again after skipping - * the whitespace following the non-printable - * command.... - */ - cp++; - pp_skip_whitespace(&cp); - } else { - return cp; + /* + * XXX: What happens if shell->noPrint occurs on the boundary of + * the buffer? To work correctly in all cases, this should rather + * be a proper stream filter instead of doing string matching on + * selected chunks of the output. + */ + while ((ecp = strstr(cp, shell->noPrint)) != NULL) { + if (ecp != cp) { + *ecp = '\0'; /* XXX: avoid writing to the buffer */ + /* + * The only way there wouldn't be a newline after + * this line is if it were the last in the buffer. + * however, since the noPrint output comes after it, + * there must be a newline, so we don't print one. + */ + /* XXX: What about null bytes in the output? */ + (void)fprintf(stdout, "%s", cp); + (void)fflush(stdout); + } + cp = ecp + shell->noPrintLen; + if (cp == endp) + break; + cp++; /* skip over the (XXX: assumed) newline */ + pp_skip_whitespace(&cp); } - } - return cp; + return cp; } /* @@ -1721,168 +1828,170 @@ JobOutput(Job *job, char *cp, char *endp) static void JobDoOutput(Job *job, Boolean finish) { - Boolean gotNL = FALSE; /* true if got a newline */ - Boolean fbuf; /* true if our buffer filled up */ - size_t nr; /* number of bytes read */ - size_t i; /* auxiliary index into outBuf */ - size_t max; /* limit for i (end of current data) */ - ssize_t nRead; /* (Temporary) number of bytes read */ - - /* - * Read as many bytes as will fit in the buffer. - */ + Boolean gotNL; /* true if got a newline */ + Boolean fbuf; /* true if our buffer filled up */ + size_t nr; /* number of bytes read */ + size_t i; /* auxiliary index into outBuf */ + size_t max; /* limit for i (end of current data) */ + ssize_t nRead; /* (Temporary) number of bytes read */ + + /* Read as many bytes as will fit in the buffer. */ again: - gotNL = FALSE; - fbuf = FALSE; - - nRead = read(job->inPipe, &job->outBuf[job->curPos], - JOB_BUFSIZE - job->curPos); - if (nRead < 0) { - if (errno == EAGAIN) - return; - if (DEBUG(JOB)) { - perror("JobDoOutput(piperead)"); + gotNL = FALSE; + fbuf = FALSE; + + nRead = read(job->inPipe, &job->outBuf[job->curPos], + JOB_BUFSIZE - job->curPos); + if (nRead < 0) { + if (errno == EAGAIN) + return; + if (DEBUG(JOB)) { + perror("JobDoOutput(piperead)"); + } + nr = 0; + } else { + nr = (size_t)nRead; } - nr = 0; - } else { - nr = (size_t)nRead; - } - - /* - * 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'; - nr = 1; - finish = FALSE; - } else if (nr == 0) { - finish = FALSE; - } - /* - * 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 - * TRUE. - */ - max = job->curPos + nr; - for (i = job->curPos + nr - 1; i >= job->curPos && i != (size_t)-1; i--) { - if (job->outBuf[i] == '\n') { - gotNL = TRUE; - break; - } else if (job->outBuf[i] == '\0') { - /* - * Why? - */ - job->outBuf[i] = ' '; + /* + * 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'; + nr = 1; + finish = FALSE; + } else if (nr == 0) { + finish = FALSE; } - } - if (!gotNL) { - job->curPos += nr; - if (job->curPos == JOB_BUFSIZE) { - /* - * If we've run out of buffer space, we have no choice - * but to print the stuff. sigh. - */ - fbuf = TRUE; - i = job->curPos; - } - } - if (gotNL || fbuf) { /* - * 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, preceded - * 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. + * 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 + * TRUE. */ - job->outBuf[i] = '\0'; - if (i >= job->curPos) { - char *cp; - - cp = JobOutput(job, job->outBuf, &job->outBuf[i]); - - /* - * 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 - * our own free will. - */ - if (*cp != '\0') { - if (!opts.beSilent && job->node != lastNode) { - MESSAGE(stdout, job->node); - lastNode = job->node; + max = job->curPos + nr; + for (i = job->curPos + nr - 1; + i >= job->curPos && i != (size_t)-1; i--) { + if (job->outBuf[i] == '\n') { + gotNL = TRUE; + break; + } else if (job->outBuf[i] == '\0') { + /* + * Why? + */ + job->outBuf[i] = ' '; } -#ifdef USE_META - if (useMeta) { - meta_job_output(job, cp, gotNL ? "\n" : ""); + } + + if (!gotNL) { + job->curPos += nr; + if (job->curPos == JOB_BUFSIZE) { + /* + * If we've run out of buffer space, we have no choice + * but to print the stuff. sigh. + */ + fbuf = TRUE; + i = job->curPos; } + } + if (gotNL || fbuf) { + /* + * 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, preceded + * 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. + */ + job->outBuf[i] = '\0'; + if (i >= job->curPos) { + char *cp; + + cp = JobOutput(job->outBuf, &job->outBuf[i]); + + /* + * 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 our own free will. + */ + if (*cp != '\0') { + if (!opts.beSilent) + SwitchOutputTo(job->node); +#ifdef USE_META + if (useMeta) { + meta_job_output(job, cp, + gotNL ? "\n" : ""); + } #endif - (void)fprintf(stdout, "%s%s", cp, gotNL ? "\n" : ""); - (void)fflush(stdout); - } + (void)fprintf(stdout, "%s%s", cp, + gotNL ? "\n" : ""); + (void)fflush(stdout); + } + } + /* + * max is the last offset still in the buffer. Move any + * remaining characters to the start of the buffer and + * update the end marker curPos. + */ + if (i < max) { + (void)memmove(job->outBuf, &job->outBuf[i + 1], + max - (i + 1)); + job->curPos = max - (i + 1); + } else { + assert(i == max); + job->curPos = 0; + } } - /* - * max is the last offset still in the buffer. Move any remaining - * characters to the start of the buffer and update the end marker - * curPos. - */ - if (i < max) { - (void)memmove(job->outBuf, &job->outBuf[i + 1], max - (i + 1)); - job->curPos = max - (i + 1); - } else { - assert(i == max); - job->curPos = 0; + if (finish) { + /* + * 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. + */ + goto again; } - } - if (finish) { - /* - * 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. - */ - goto again; - } } static void JobRun(GNode *targ) { #if 0 - /* - * Unfortunately it is too complicated to run .BEGIN, .END, and - * .INTERRUPT job in the parallel job module. As of 2020-09-25, - * unit-tests/deptgt-end-jobs.mk hangs in an endless loop. - * - * Running these jobs in compat mode also guarantees that these - * jobs do not overlap with other unrelated jobs. - */ - List *lst = Lst_New(); - Lst_Append(lst, targ); - (void)Make_Run(lst); - Lst_Destroy(lst, NULL); - JobStart(targ, JOB_SPECIAL); - while (jobTokensRunning) { - Job_CatchOutput(); - } + /* + * Unfortunately it is too complicated to run .BEGIN, .END, and + * .INTERRUPT job in the parallel job module. As of 2020-09-25, + * unit-tests/deptgt-end-jobs.mk hangs in an endless loop. + * + * Running these jobs in compat mode also guarantees that these + * jobs do not overlap with other unrelated jobs. + */ + List *lst = Lst_New(); + Lst_Append(lst, targ); + (void)Make_Run(lst); + Lst_Destroy(lst, NULL); + JobStart(targ, JOB_SPECIAL); + while (jobTokensRunning != 0) { + Job_CatchOutput(); + } #else - Compat_Make(targ, targ); - if (targ->made == ERROR) { - PrintOnError(targ, "\n\nStop."); - exit(1); - } + Compat_Make(targ, targ); + /* XXX: Replace with GNode_IsError(gn) */ + if (targ->made == ERROR) { + PrintOnError(targ, "\n\nStop."); + exit(1); + } #endif } -/* Handle the exit of a child. Called from Make_Make. +/* + * Handle the exit of a child. Called from Make_Make. * * The job descriptor is removed from the list of children. * @@ -1894,20 +2003,18 @@ JobRun(GNode *targ) void Job_CatchChildren(void) { - int pid; /* pid of dead child */ - WAIT_T status; /* Exit/termination status */ + int pid; /* pid of dead child */ + WAIT_T status; /* Exit/termination status */ - /* - * Don't even bother if we know there's no one around. - */ - if (jobTokensRunning == 0) - return; + /* Don't even bother if we know there's no one around. */ + if (jobTokensRunning == 0) + return; - while ((pid = waitpid((pid_t) -1, &status, WNOHANG | WUNTRACED)) > 0) { - DEBUG2(JOB, "Process %d exited/stopped status %x.\n", pid, - WAIT_STATUS(status)); - JobReapChild(pid, status, TRUE); - } + while ((pid = waitpid((pid_t)-1, &status, WNOHANG | WUNTRACED)) > 0) { + DEBUG2(JOB, "Process %d exited/stopped status %x.\n", + pid, WAIT_STATUS(status)); + JobReapChild(pid, status, TRUE); + } } /* @@ -1917,324 +2024,343 @@ Job_CatchChildren(void) void JobReapChild(pid_t pid, WAIT_T status, Boolean isJobs) { - Job *job; /* job descriptor for dead child */ - - /* - * Don't even bother if we know there's no one around. - */ - if (jobTokensRunning == 0) - return; + Job *job; /* job descriptor for dead child */ - job = JobFindPid(pid, JOB_ST_RUNNING, isJobs); - if (job == NULL) { - if (isJobs) { - if (!lurking_children) - Error("Child (%d) status %x not in table?", pid, status); + /* Don't even bother if we know there's no one around. */ + if (jobTokensRunning == 0) + return; + + job = JobFindPid(pid, JOB_ST_RUNNING, isJobs); + if (job == NULL) { + if (isJobs) { + if (!lurking_children) + Error("Child (%d) status %x not in table?", + pid, status); + } + return; /* not ours */ } - return; /* not ours */ - } - if (WIFSTOPPED(status)) { - DEBUG2(JOB, "Process %d (%s) stopped.\n", job->pid, job->node->name); - if (!make_suspended) { - switch (WSTOPSIG(status)) { - case SIGTSTP: - (void)printf("*** [%s] Suspended\n", job->node->name); - break; - case SIGSTOP: - (void)printf("*** [%s] Stopped\n", job->node->name); - break; - default: - (void)printf("*** [%s] Stopped -- signal %d\n", - job->node->name, WSTOPSIG(status)); - } - job->suspended = TRUE; + if (WIFSTOPPED(status)) { + DEBUG2(JOB, "Process %d (%s) stopped.\n", + job->pid, job->node->name); + if (!make_suspended) { + switch (WSTOPSIG(status)) { + case SIGTSTP: + (void)printf("*** [%s] Suspended\n", + job->node->name); + break; + case SIGSTOP: + (void)printf("*** [%s] Stopped\n", + job->node->name); + break; + default: + (void)printf("*** [%s] Stopped -- signal %d\n", + job->node->name, WSTOPSIG(status)); + } + job->suspended = TRUE; + } + (void)fflush(stdout); + return; } - (void)fflush(stdout); - return; - } - job->status = JOB_ST_FINISHED; - job->exit_status = WAIT_STATUS(status); + job->status = JOB_ST_FINISHED; + job->exit_status = WAIT_STATUS(status); - JobFinish(job, status); + JobFinish(job, status); } -/* Catch the output from our children, if we're using pipes do so. Otherwise +/* + * 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. */ + * child can wait for a while. + */ void Job_CatchOutput(void) { - int nready; - Job *job; - unsigned int i; + int nready; + Job *job; + unsigned int i; - (void)fflush(stdout); - - /* The first fd in the list is the job token pipe */ - do { - nready = poll(fds + 1 - wantToken, nfds - 1 + wantToken, POLL_MSEC); - } while (nready < 0 && errno == EINTR); - - if (nready < 0) - Punt("poll: %s", strerror(errno)); + (void)fflush(stdout); - if (nready > 0 && readyfd(&childExitJob)) { - char token = 0; - ssize_t count; - count = read(childExitJob.inPipe, &token, 1); - switch (count) { - case 0: - Punt("unexpected eof on token pipe"); - case -1: - Punt("token pipe read: %s", strerror(errno)); - case 1: - if (token == DO_JOB_RESUME[0]) - /* Complete relay requested from our SIGCONT handler */ - JobRestartJobs(); - break; - default: - abort(); - } - nready--; - } + /* The first fd in the list is the job token pipe */ + do { + nready = poll(fds + 1 - wantToken, nJobs - 1 + wantToken, + POLL_MSEC); + } while (nready < 0 && errno == EINTR); + + if (nready < 0) + Punt("poll: %s", strerror(errno)); + + if (nready > 0 && readyfd(&childExitJob)) { + char token = 0; + ssize_t count; + count = read(childExitJob.inPipe, &token, 1); + switch (count) { + case 0: + Punt("unexpected eof on token pipe"); + /*NOTREACHED*/ + case -1: + Punt("token pipe read: %s", strerror(errno)); + /*NOTREACHED*/ + case 1: + if (token == DO_JOB_RESUME[0]) + /* + * Complete relay requested from our SIGCONT + * handler + */ + JobRestartJobs(); + break; + default: + abort(); + } + nready--; + } - Job_CatchChildren(); - if (nready == 0) - return; + Job_CatchChildren(); + if (nready == 0) + return; - for (i = npseudojobs * nfds_per_job(); i < nfds; i++) { - if (!fds[i].revents) - continue; - job = jobfds[i]; - if (job->status == JOB_ST_RUNNING) - JobDoOutput(job, FALSE); + for (i = npseudojobs * nfds_per_job(); i < nJobs; i++) { + if (fds[i].revents == 0) + continue; + job = allJobs[i]; + if (job->status == JOB_ST_RUNNING) + JobDoOutput(job, FALSE); #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) - /* - * With meta mode, we may have activity on the job's filemon - * descriptor too, which at the moment is any pollfd other than - * job->inPollfd. - */ - if (useMeta && job->inPollfd != &fds[i]) { - if (meta_job_event(job) <= 0) { - fds[i].events = 0; /* never mind */ - } - } + /* + * With meta mode, we may have activity on the job's filemon + * descriptor too, which at the moment is any pollfd other + * than job->inPollfd. + */ + if (useMeta && job->inPollfd != &fds[i]) { + if (meta_job_event(job) <= 0) { + fds[i].events = 0; /* never mind */ + } + } #endif - if (--nready == 0) - return; - } + if (--nready == 0) + return; + } } -/* Start the creation of a target. Basically a front-end for JobStart used by - * the Make module. */ +/* + * Start the creation of a target. Basically a front-end for JobStart used by + * the Make module. + */ void Job_Make(GNode *gn) { - (void)JobStart(gn, JOB_NONE); + (void)JobStart(gn, FALSE); } -void -Shell_Init(void) +static void +InitShellNameAndPath(void) { - if (shellPath == NULL) { - /* - * We are using the default shell, which may be an absolute - * path if DEFSHELL_CUSTOM is defined. - */ - shellName = commandShell->name; + shellName = shell->name; + #ifdef DEFSHELL_CUSTOM - if (*shellName == '/') { - shellPath = shellName; - shellName = strrchr(shellPath, '/'); - shellName++; - } else + if (shellName[0] == '/') { + shellPath = shellName; + shellName = str_basename(shellPath); + return; + } #endif + shellPath = str_concat3(_PATH_DEFSHELLDIR, "/", shellName); - } - Var_SetWithFlags(".SHELL", shellPath, VAR_CMDLINE, VAR_SET_READONLY); - if (commandShell->exit == NULL) { - commandShell->exit = ""; - } - if (commandShell->echo == NULL) { - commandShell->echo = ""; - } - if (commandShell->hasErrCtl && commandShell->exit[0] != '\0') { - if (shellErrFlag && - strcmp(commandShell->exit, &shellErrFlag[1]) != 0) { - free(shellErrFlag); - shellErrFlag = NULL; - } - if (!shellErrFlag) { - size_t n = strlen(commandShell->exit) + 2; - - shellErrFlag = bmake_malloc(n); - if (shellErrFlag) { - snprintf(shellErrFlag, n, "-%s", commandShell->exit); - } - } - } else if (shellErrFlag) { - free(shellErrFlag); - shellErrFlag = NULL; - } } -/* Return the string literal that is used in the current command shell - * to produce a newline character. */ +void +Shell_Init(void) +{ + if (shellPath == NULL) + InitShellNameAndPath(); + + Var_SetWithFlags(".SHELL", shellPath, VAR_CMDLINE, VAR_SET_READONLY); + if (shell->errFlag == NULL) + shell->errFlag = ""; + if (shell->echoFlag == NULL) + shell->echoFlag = ""; + if (shell->hasErrCtl && shell->errFlag[0] != '\0') { + if (shellErrFlag != NULL && + strcmp(shell->errFlag, &shellErrFlag[1]) != 0) { + free(shellErrFlag); + shellErrFlag = NULL; + } + if (shellErrFlag == NULL) { + size_t n = strlen(shell->errFlag) + 2; + + shellErrFlag = bmake_malloc(n); + if (shellErrFlag != NULL) + snprintf(shellErrFlag, n, "-%s", + shell->errFlag); + } + } else if (shellErrFlag != NULL) { + free(shellErrFlag); + shellErrFlag = NULL; + } +} + +/* + * Return the string literal that is used in the current command shell + * to produce a newline character. + */ const char * Shell_GetNewline(void) { - return commandShell->newline; + return shell->newline; } void Job_SetPrefix(void) { - if (targPrefix) { - free(targPrefix); - } else if (!Var_Exists(MAKE_JOB_PREFIX, VAR_GLOBAL)) { - Var_Set(MAKE_JOB_PREFIX, "---", VAR_GLOBAL); - } + if (targPrefix != NULL) { + free(targPrefix); + } else if (!Var_Exists(MAKE_JOB_PREFIX, VAR_GLOBAL)) { + Var_Set(MAKE_JOB_PREFIX, "---", VAR_GLOBAL); + } - (void)Var_Subst("${" MAKE_JOB_PREFIX "}", - VAR_GLOBAL, VARE_WANTRES, &targPrefix); - /* TODO: handle errors */ + (void)Var_Subst("${" MAKE_JOB_PREFIX "}", + VAR_GLOBAL, VARE_WANTRES, &targPrefix); + /* TODO: handle errors */ +} + +static void +AddSig(int sig, SignalProc handler) +{ + if (bmake_signal(sig, SIG_IGN) != SIG_IGN) { + sigaddset(&caught_signals, sig); + (void)bmake_signal(sig, handler); + } } /* Initialize the process module. */ void Job_Init(void) { - Job_SetPrefix(); - /* Allocate space for all the job info */ - job_table = bmake_malloc((size_t)opts.maxJobs * sizeof *job_table); - memset(job_table, 0, (size_t)opts.maxJobs * sizeof *job_table); - job_table_end = job_table + opts.maxJobs; - wantToken = 0; + Job_SetPrefix(); + /* Allocate space for all the job info */ + job_table = bmake_malloc((size_t)opts.maxJobs * sizeof *job_table); + memset(job_table, 0, (size_t)opts.maxJobs * sizeof *job_table); + job_table_end = job_table + opts.maxJobs; + wantToken = 0; - aborting = ABORT_NONE; - errors = 0; + aborting = ABORT_NONE; + job_errors = 0; - lastNode = NULL; + Always_pass_job_queue = GetBooleanVar(MAKE_ALWAYS_PASS_JOB_QUEUE, + Always_pass_job_queue); - Always_pass_job_queue = GetBooleanVar(MAKE_ALWAYS_PASS_JOB_QUEUE, - Always_pass_job_queue); + Job_error_token = GetBooleanVar(MAKE_JOB_ERROR_TOKEN, Job_error_token); - Job_error_token = GetBooleanVar(MAKE_JOB_ERROR_TOKEN, Job_error_token); - - - /* - * There is a non-zero chance that we already have children. - * eg after 'make -f- <<EOF' - * Since their termination causes a 'Child (pid) not in table' message, - * Collect the status of any that are already dead, and suppress the - * error message if there are any undead ones. - */ - for (;;) { - int rval, status; - rval = waitpid((pid_t) -1, &status, WNOHANG); - if (rval > 0) - continue; - if (rval == 0) - lurking_children = TRUE; - break; - } - - Shell_Init(); - - JobCreatePipe(&childExitJob, 3); + /* + * There is a non-zero chance that we already have children. + * eg after 'make -f- <<EOF' + * Since their termination causes a 'Child (pid) not in table' + * message, Collect the status of any that are already dead, and + * suppress the error message if there are any undead ones. + */ + for (;;) { + int rval; + WAIT_T status; + + rval = waitpid((pid_t)-1, &status, WNOHANG); + if (rval > 0) + continue; + if (rval == 0) + lurking_children = TRUE; + break; + } - /* Preallocate enough for the maximum number of jobs. */ - fds = bmake_malloc(sizeof *fds * - (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job()); - jobfds = bmake_malloc(sizeof *jobfds * - (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job()); + Shell_Init(); - /* These are permanent entries and take slots 0 and 1 */ - watchfd(&tokenWaitJob); - watchfd(&childExitJob); + JobCreatePipe(&childExitJob, 3); - sigemptyset(&caught_signals); - /* - * Install a SIGCHLD handler. - */ - (void)bmake_signal(SIGCHLD, JobChildSig); - sigaddset(&caught_signals, SIGCHLD); + /* Preallocate enough for the maximum number of jobs. */ + fds = bmake_malloc(sizeof *fds * + (npseudojobs + (size_t)opts.maxJobs) * + nfds_per_job()); + allJobs = bmake_malloc(sizeof *allJobs * + (npseudojobs + (size_t)opts.maxJobs) * + nfds_per_job()); -#define ADDSIG(s,h) \ - if (bmake_signal(s, SIG_IGN) != SIG_IGN) { \ - sigaddset(&caught_signals, s); \ - (void)bmake_signal(s, h); \ - } + /* These are permanent entries and take slots 0 and 1 */ + watchfd(&tokenWaitJob); + watchfd(&childExitJob); - /* - * Catch the four signals that POSIX specifies if they aren't ignored. - * JobPassSig will take care of calling JobInterrupt if appropriate. - */ - ADDSIG(SIGINT, JobPassSig_int) - ADDSIG(SIGHUP, JobPassSig_term) - ADDSIG(SIGTERM, JobPassSig_term) - ADDSIG(SIGQUIT, JobPassSig_term) + sigemptyset(&caught_signals); + /* + * Install a SIGCHLD handler. + */ + (void)bmake_signal(SIGCHLD, JobChildSig); + sigaddset(&caught_signals, SIGCHLD); - /* - * 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) - */ - ADDSIG(SIGTSTP, JobPassSig_suspend) - ADDSIG(SIGTTOU, JobPassSig_suspend) - ADDSIG(SIGTTIN, JobPassSig_suspend) - ADDSIG(SIGWINCH, JobCondPassSig) - ADDSIG(SIGCONT, JobContinueSig) -#undef ADDSIG + /* + * Catch the four signals that POSIX specifies if they aren't ignored. + * JobPassSig will take care of calling JobInterrupt if appropriate. + */ + AddSig(SIGINT, JobPassSig_int); + AddSig(SIGHUP, JobPassSig_term); + AddSig(SIGTERM, JobPassSig_term); + AddSig(SIGQUIT, JobPassSig_term); - (void)Job_RunTarget(".BEGIN", NULL); - /* Create the .END node now, even though no code in the unit tests - * depends on it. See also Targ_GetEndNode in Compat_Run. */ - (void)Targ_GetEndNode(); + /* + * 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) + */ + AddSig(SIGTSTP, JobPassSig_suspend); + AddSig(SIGTTOU, JobPassSig_suspend); + AddSig(SIGTTIN, JobPassSig_suspend); + AddSig(SIGWINCH, JobCondPassSig); + AddSig(SIGCONT, JobContinueSig); + + (void)Job_RunTarget(".BEGIN", NULL); + /* Create the .END node now, even though no code in the unit tests + * depends on it. See also Targ_GetEndNode in Compat_Run. */ + (void)Targ_GetEndNode(); } -static void JobSigReset(void) +static void +DelSig(int sig) { -#define DELSIG(s) \ - if (sigismember(&caught_signals, s)) { \ - (void)bmake_signal(s, SIG_DFL); \ - } + if (sigismember(&caught_signals, sig) != 0) + (void)bmake_signal(sig, SIG_DFL); +} - DELSIG(SIGINT) - DELSIG(SIGHUP) - DELSIG(SIGQUIT) - DELSIG(SIGTERM) - DELSIG(SIGTSTP) - DELSIG(SIGTTOU) - DELSIG(SIGTTIN) - DELSIG(SIGWINCH) - DELSIG(SIGCONT) -#undef DELSIG - (void)bmake_signal(SIGCHLD, SIG_DFL); +static void +JobSigReset(void) +{ + DelSig(SIGINT); + DelSig(SIGHUP); + DelSig(SIGQUIT); + DelSig(SIGTERM); + DelSig(SIGTSTP); + DelSig(SIGTTOU); + DelSig(SIGTTIN); + DelSig(SIGWINCH); + DelSig(SIGCONT); + (void)bmake_signal(SIGCHLD, SIG_DFL); } /* Find a shell in 'shells' given its name, or return NULL. */ static Shell * FindShellByName(const char *name) { - Shell *sh = shells; - const Shell *shellsEnd = sh + sizeof shells / sizeof shells[0]; + Shell *sh = shells; + const Shell *shellsEnd = sh + sizeof shells / sizeof shells[0]; - for (sh = shells; sh < shellsEnd; sh++) { - if (strcmp(name, sh->name) == 0) - return sh; - } - return NULL; + for (sh = shells; sh < shellsEnd; sh++) { + if (strcmp(name, sh->name) == 0) + return sh; + } + return NULL; } -/*- - *----------------------------------------------------------------------- - * Job_ParseShell -- - * Parse a shell specification and set up commandShell, shellPath - * and shellName appropriately. +/* + * Parse a shell specification and set up 'shell', shellPath and + * shellName appropriately. * * Input: * line The shell spec @@ -2243,9 +2369,9 @@ FindShellByName(const char *name) * FALSE if the specification was incorrect. * * Side Effects: - * commandShell points to a Shell structure (either predefined or + * 'shell' 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 + * shell described by 'shell', while shellName is just the * final component of shellPath. * * Notes: @@ -2274,166 +2400,174 @@ FindShellByName(const char *name) * is TRUE or template of command to execute a * command so as to ignore any errors it returns if * hasErrCtl is FALSE. - * - *----------------------------------------------------------------------- */ Boolean Job_ParseShell(char *line) { - Words wordsList; - char **words; - char **argv; - size_t argc; - char *path; - Shell newShell; - Boolean fullSpec = FALSE; - Shell *sh; + Words wordsList; + char **words; + char **argv; + size_t argc; + char *path; + Shell newShell; + Boolean fullSpec = FALSE; + Shell *sh; - pp_skip_whitespace(&line); + /* XXX: don't use line as an iterator variable */ + pp_skip_whitespace(&line); - free(shellArgv); + free(shellArgv); - memset(&newShell, 0, sizeof newShell); + memset(&newShell, 0, sizeof newShell); - /* - * Parse the specification by keyword - */ - wordsList = Str_Words(line, TRUE); - words = wordsList.words; - argc = wordsList.len; - path = wordsList.freeIt; - if (words == NULL) { - Error("Unterminated quoted string [%s]", line); - return FALSE; - } - shellArgv = path; - - for (path = NULL, argv = words; argc != 0; argc--, argv++) { - char *arg = *argv; - if (strncmp(arg, "path=", 5) == 0) { - path = arg + 5; - } else if (strncmp(arg, "name=", 5) == 0) { - newShell.name = arg + 5; - } else { - if (strncmp(arg, "quiet=", 6) == 0) { - newShell.echoOff = arg + 6; - } else if (strncmp(arg, "echo=", 5) == 0) { - newShell.echoOn = arg + 5; - } else if (strncmp(arg, "filter=", 7) == 0) { - newShell.noPrint = arg + 7; - newShell.noPrintLen = strlen(newShell.noPrint); - } else if (strncmp(arg, "echoFlag=", 9) == 0) { - newShell.echo = arg + 9; - } else if (strncmp(arg, "errFlag=", 8) == 0) { - newShell.exit = arg + 8; - } else if (strncmp(arg, "hasErrCtl=", 10) == 0) { - char c = arg[10]; - newShell.hasErrCtl = c == 'Y' || c == 'y' || - c == 'T' || c == 't'; - } else if (strncmp(arg, "newline=", 8) == 0) { - newShell.newline = arg + 8; - } else if (strncmp(arg, "check=", 6) == 0) { - newShell.errOnOrEcho = arg + 6; - } else if (strncmp(arg, "ignore=", 7) == 0) { - newShell.errOffOrExecIgnore = arg + 7; - } else if (strncmp(arg, "errout=", 7) == 0) { - newShell.errExit = arg + 7; - } else if (strncmp(arg, "comment=", 8) == 0) { - newShell.commentChar = arg[8]; - } else { - Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"", arg); - free(words); - return FALSE; - } - fullSpec = TRUE; - } - } - - if (path == 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 FindShellByName - * and set things up the right way. shellPath will be set up by - * Shell_Init. + * Parse the specification by keyword */ - if (newShell.name == NULL) { - Parse_Error(PARSE_FATAL, "Neither path nor name specified"); - free(words); - return FALSE; - } else { - if ((sh = FindShellByName(newShell.name)) == NULL) { - Parse_Error(PARSE_WARNING, "%s: No matching shell", - newShell.name); - free(words); - return FALSE; - } - commandShell = sh; - shellName = newShell.name; - if (shellPath) { - /* Shell_Init has already been called! Do it again. */ - free(UNCONST(shellPath)); - shellPath = NULL; - Shell_Init(); - } + wordsList = Str_Words(line, TRUE); + words = wordsList.words; + argc = wordsList.len; + path = wordsList.freeIt; + if (words == NULL) { + Error("Unterminated quoted string [%s]", line); + return FALSE; } - } else { - /* - * 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. - */ - shellPath = path; - path = strrchr(path, '/'); - if (path == NULL) { - path = UNCONST(shellPath); - } else { - path++; + shellArgv = path; + + for (path = NULL, argv = words; argc != 0; argc--, argv++) { + char *arg = *argv; + if (strncmp(arg, "path=", 5) == 0) { + path = arg + 5; + } else if (strncmp(arg, "name=", 5) == 0) { + newShell.name = arg + 5; + } else { + if (strncmp(arg, "quiet=", 6) == 0) { + newShell.echoOff = arg + 6; + } else if (strncmp(arg, "echo=", 5) == 0) { + newShell.echoOn = arg + 5; + } else if (strncmp(arg, "filter=", 7) == 0) { + newShell.noPrint = arg + 7; + newShell.noPrintLen = strlen(newShell.noPrint); + } else if (strncmp(arg, "echoFlag=", 9) == 0) { + newShell.echoFlag = arg + 9; + } else if (strncmp(arg, "errFlag=", 8) == 0) { + newShell.errFlag = arg + 8; + } else if (strncmp(arg, "hasErrCtl=", 10) == 0) { + char c = arg[10]; + newShell.hasErrCtl = c == 'Y' || c == 'y' || + c == 'T' || c == 't'; + } else if (strncmp(arg, "newline=", 8) == 0) { + newShell.newline = arg + 8; + } else if (strncmp(arg, "check=", 6) == 0) { + /* Before 2020-12-10, these two variables + * had been a single variable. */ + newShell.errOn = arg + 6; + newShell.echoTmpl = arg + 6; + } else if (strncmp(arg, "ignore=", 7) == 0) { + /* Before 2020-12-10, these two variables + * had been a single variable. */ + newShell.errOff = arg + 7; + newShell.runIgnTmpl = arg + 7; + } else if (strncmp(arg, "errout=", 7) == 0) { + newShell.runChkTmpl = arg + 7; + } else if (strncmp(arg, "comment=", 8) == 0) { + newShell.commentChar = arg[8]; + } else { + Parse_Error(PARSE_FATAL, + "Unknown keyword \"%s\"", arg); + free(words); + return FALSE; + } + fullSpec = TRUE; + } } - if (newShell.name != NULL) { - shellName = newShell.name; - } else { - shellName = path; - } - if (!fullSpec) { - if ((sh = FindShellByName(shellName)) == NULL) { - Parse_Error(PARSE_WARNING, "%s: No matching shell", - shellName); - free(words); - return FALSE; - } - commandShell = sh; + + if (path == 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 FindShellByName and set things up the + * right way. shellPath will be set up by Shell_Init. + */ + if (newShell.name == NULL) { + Parse_Error(PARSE_FATAL, + "Neither path nor name specified"); + free(words); + return FALSE; + } else { + if ((sh = FindShellByName(newShell.name)) == NULL) { + Parse_Error(PARSE_WARNING, + "%s: No matching shell", newShell.name); + free(words); + return FALSE; + } + shell = sh; + shellName = newShell.name; + if (shellPath != NULL) { + /* + * Shell_Init has already been called! + * Do it again. + */ + free(UNCONST(shellPath)); + shellPath = NULL; + Shell_Init(); + } + } } else { - commandShell = bmake_malloc(sizeof *commandShell); - *commandShell = newShell; + /* + * 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. + */ + shellPath = path; + path = strrchr(path, '/'); + if (path == NULL) { + path = UNCONST(shellPath); + } else { + path++; + } + if (newShell.name != NULL) { + shellName = newShell.name; + } else { + shellName = path; + } + if (!fullSpec) { + if ((sh = FindShellByName(shellName)) == NULL) { + Parse_Error(PARSE_WARNING, + "%s: No matching shell", shellName); + free(words); + return FALSE; + } + shell = sh; + } else { + shell = bmake_malloc(sizeof *shell); + *shell = newShell; + } + /* this will take care of shellErrFlag */ + Shell_Init(); } - /* this will take care of shellErrFlag */ - Shell_Init(); - } - if (commandShell->echoOn && commandShell->echoOff) { - commandShell->hasEchoCtl = TRUE; - } + if (shell->echoOn != NULL && shell->echoOff != NULL) + shell->hasEchoCtl = TRUE; - if (!commandShell->hasErrCtl) { - if (commandShell->errOnOrEcho == NULL) { - commandShell->errOnOrEcho = ""; - } - if (commandShell->errOffOrExecIgnore == NULL) { - commandShell->errOffOrExecIgnore = "%s\n"; + if (!shell->hasErrCtl) { + if (shell->echoTmpl == NULL) + shell->echoTmpl = ""; + if (shell->runIgnTmpl == NULL) + shell->runIgnTmpl = "%s\n"; } - } - /* - * Do not free up the words themselves, since they might be in use by the - * shell specification. - */ - free(words); - return TRUE; + /* + * Do not free up the words themselves, since they might be in use + * by the shell specification. + */ + free(words); + return TRUE; } -/* Handle the receipt of an interrupt. +/* + * Handle the receipt of an interrupt. * * All children are killed. Another job will be started if the .INTERRUPT * target is defined. @@ -2444,59 +2578,63 @@ Job_ParseShell(char *line) * signo signal received */ static void -JobInterrupt(int runINTERRUPT, int signo) +JobInterrupt(Boolean runINTERRUPT, int signo) { - Job *job; /* job descriptor in that element */ - GNode *interrupt; /* the node describing the .INTERRUPT target */ - sigset_t mask; - GNode *gn; + Job *job; /* job descriptor in that element */ + GNode *interrupt; /* the node describing the .INTERRUPT target */ + sigset_t mask; + GNode *gn; - aborting = ABORT_INTERRUPT; + aborting = ABORT_INTERRUPT; - JobSigLock(&mask); + JobSigLock(&mask); - for (job = job_table; job < job_table_end; job++) { - if (job->status != JOB_ST_RUNNING) - continue; + for (job = job_table; job < job_table_end; job++) { + if (job->status != JOB_ST_RUNNING) + continue; - gn = job->node; + gn = job->node; - JobDeleteTarget(gn); - if (job->pid) { - DEBUG2(JOB, "JobInterrupt passing signal %d to child %d.\n", - signo, job->pid); - KILLPG(job->pid, signo); + JobDeleteTarget(gn); + if (job->pid != 0) { + DEBUG2(JOB, + "JobInterrupt passing signal %d to child %d.\n", + signo, job->pid); + KILLPG(job->pid, signo); + } } - } - JobSigUnlock(&mask); + JobSigUnlock(&mask); - if (runINTERRUPT && !opts.touchFlag) { - interrupt = Targ_FindNode(".INTERRUPT"); - if (interrupt != NULL) { - opts.ignoreErrors = FALSE; - JobRun(interrupt); + if (runINTERRUPT && !opts.touchFlag) { + interrupt = Targ_FindNode(".INTERRUPT"); + if (interrupt != NULL) { + opts.ignoreErrors = FALSE; + JobRun(interrupt); + } } - } - Trace_Log(MAKEINTR, NULL); - exit(signo); + Trace_Log(MAKEINTR, NULL); + exit(signo); /* XXX: why signo? */ } -/* Do the final processing, i.e. run the commands attached to the .END target. +/* + * Do the final processing, i.e. run the commands attached to the .END target. * - * Return the number of errors reported. */ + * Return the number of errors reported. + */ int Job_Finish(void) { - GNode *endNode = Targ_GetEndNode(); - if (!Lst_IsEmpty(endNode->commands) || !Lst_IsEmpty(endNode->children)) { - if (errors) { - Error("Errors reported so .END ignored"); - } else { - JobRun(endNode); + GNode *endNode = Targ_GetEndNode(); + if (!Lst_IsEmpty(&endNode->commands) || + !Lst_IsEmpty(&endNode->children)) { + if (job_errors != 0) { + Error("Errors reported so .END ignored"); + } else { + JobRun(endNode); + } } - } - return errors; + return job_errors; } /* Clean up any memory used by the jobs module. */ @@ -2504,350 +2642,375 @@ void Job_End(void) { #ifdef CLEANUP - free(shellArgv); + free(shellArgv); #endif } -/* Waits for all running jobs to finish and returns. - * Sets 'aborting' to ABORT_WAIT to prevent other jobs from starting. */ +/* + * Waits for all running jobs to finish and returns. + * Sets 'aborting' to ABORT_WAIT to prevent other jobs from starting. + */ void Job_Wait(void) { - aborting = ABORT_WAIT; - while (jobTokensRunning != 0) { - Job_CatchOutput(); - } - aborting = ABORT_NONE; + aborting = ABORT_WAIT; + while (jobTokensRunning != 0) { + Job_CatchOutput(); + } + aborting = ABORT_NONE; } -/* Abort all currently running jobs without handling output or anything. +/* + * 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. */ + * All children are killed, not just the firstborn. + */ void Job_AbortAll(void) { - Job *job; /* the job descriptor in that element */ - WAIT_T foo; - - aborting = ABORT_ERROR; - - if (jobTokensRunning) { - for (job = job_table; job < job_table_end; job++) { - if (job->status != JOB_ST_RUNNING) - continue; - /* - * kill the child process with increasingly drastic signals to make - * darn sure it's dead. - */ - KILLPG(job->pid, SIGINT); - KILLPG(job->pid, SIGKILL); + Job *job; /* the job descriptor in that element */ + WAIT_T foo; + + aborting = ABORT_ERROR; + + if (jobTokensRunning != 0) { + for (job = job_table; job < job_table_end; job++) { + if (job->status != JOB_ST_RUNNING) + continue; + /* + * kill the child process with increasingly drastic + * signals to make darn sure it's dead. + */ + KILLPG(job->pid, SIGINT); + KILLPG(job->pid, SIGKILL); + } } - } - /* - * Catch as many children as want to report in at first, then give up - */ - while (waitpid((pid_t) -1, &foo, WNOHANG) > 0) - continue; + /* + * Catch as many children as want to report in at first, then give up + */ + while (waitpid((pid_t)-1, &foo, WNOHANG) > 0) + continue; } -/* Tries to restart stopped jobs if there are slots available. - * Called in process context in response to a SIGCONT. */ +/* + * Tries to restart stopped jobs if there are slots available. + * Called in process context in response to a SIGCONT. + */ static void JobRestartJobs(void) { - Job *job; - - for (job = job_table; job < job_table_end; job++) { - if (job->status == JOB_ST_RUNNING && - (make_suspended || job->suspended)) { - DEBUG1(JOB, "Restarting stopped job pid %d.\n", job->pid); - if (job->suspended) { - (void)printf("*** [%s] Continued\n", job->node->name); - (void)fflush(stdout); - } - job->suspended = FALSE; - if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) { - debug_printf("Failed to send SIGCONT to %d\n", job->pid); - } - } - if (job->status == JOB_ST_FINISHED) - /* Job exit deferred after calling waitpid() in a signal handler */ - JobFinish(job, job->exit_status); - } - make_suspended = FALSE; + Job *job; + + for (job = job_table; job < job_table_end; job++) { + if (job->status == JOB_ST_RUNNING && + (make_suspended || job->suspended)) { + DEBUG1(JOB, "Restarting stopped job pid %d.\n", + job->pid); + if (job->suspended) { + (void)printf("*** [%s] Continued\n", + job->node->name); + (void)fflush(stdout); + } + job->suspended = FALSE; + if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) { + debug_printf("Failed to send SIGCONT to %d\n", + job->pid); + } + } + if (job->status == JOB_ST_FINISHED) { + /* + * Job exit deferred after calling waitpid() in a + * signal handler + */ + JobFinish(job, job->exit_status); + } + } + make_suspended = FALSE; } static void watchfd(Job *job) { - if (job->inPollfd != NULL) - Punt("Watching watched job"); - - fds[nfds].fd = job->inPipe; - fds[nfds].events = POLLIN; - jobfds[nfds] = job; - job->inPollfd = &fds[nfds]; - nfds++; + if (job->inPollfd != NULL) + Punt("Watching watched job"); + + fds[nJobs].fd = job->inPipe; + fds[nJobs].events = POLLIN; + allJobs[nJobs] = job; + job->inPollfd = &fds[nJobs]; + nJobs++; #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) - if (useMeta) { - fds[nfds].fd = meta_job_fd(job); - fds[nfds].events = fds[nfds].fd == -1 ? 0 : POLLIN; - jobfds[nfds] = job; - nfds++; - } + if (useMeta) { + fds[nJobs].fd = meta_job_fd(job); + fds[nJobs].events = fds[nJobs].fd == -1 ? 0 : POLLIN; + allJobs[nJobs] = job; + nJobs++; + } #endif } static void clearfd(Job *job) { - size_t i; - if (job->inPollfd == NULL) - Punt("Unwatching unwatched job"); - i = (size_t)(job->inPollfd - fds); - nfds--; + size_t i; + if (job->inPollfd == NULL) + Punt("Unwatching unwatched job"); + i = (size_t)(job->inPollfd - fds); + nJobs--; #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) - if (useMeta) { + if (useMeta) { + /* + * Sanity check: there should be two fds per job, so the job's + * pollfd number should be even. + */ + assert(nfds_per_job() == 2); + if (i % 2 != 0) + Punt("odd-numbered fd with meta"); + nJobs--; + } +#endif /* - * Sanity check: there should be two fds per job, so the job's - * pollfd number should be even. + * Move last job in table into hole made by dead job. */ - assert(nfds_per_job() == 2); - if (i % 2) - Punt("odd-numbered fd with meta"); - nfds--; - } -#endif - /* - * Move last job in table into hole made by dead job. - */ - if (nfds != i) { - fds[i] = fds[nfds]; - jobfds[i] = jobfds[nfds]; - jobfds[i]->inPollfd = &fds[i]; + if (nJobs != i) { + fds[i] = fds[nJobs]; + allJobs[i] = allJobs[nJobs]; + allJobs[i]->inPollfd = &fds[i]; #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) - if (useMeta) { - fds[i + 1] = fds[nfds + 1]; - jobfds[i + 1] = jobfds[nfds + 1]; - } + if (useMeta) { + fds[i + 1] = fds[nJobs + 1]; + allJobs[i + 1] = allJobs[nJobs + 1]; + } #endif - } - job->inPollfd = NULL; + } + job->inPollfd = NULL; } -static int +static Boolean readyfd(Job *job) { - if (job->inPollfd == NULL) - Punt("Polling unwatched job"); - return (job->inPollfd->revents & POLLIN) != 0; + if (job->inPollfd == NULL) + Punt("Polling unwatched job"); + return (job->inPollfd->revents & POLLIN) != 0; } -/* Put a token (back) into the job pipe. - * This allows a make process to start a build job. */ +/* + * Put a token (back) into the job pipe. + * This allows a make process to start a build job. + */ static void JobTokenAdd(void) { - char tok = JOB_TOKENS[aborting], tok1; + char tok = JOB_TOKENS[aborting], tok1; - if (!Job_error_token && aborting == ABORT_ERROR) { - if (jobTokensRunning == 0) - return; - tok = '+'; /* no error token */ - } + if (!Job_error_token && aborting == ABORT_ERROR) { + if (jobTokensRunning == 0) + return; + tok = '+'; /* no error token */ + } - /* If we are depositing an error token flush everything else */ - while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1) - continue; + /* If we are depositing an error token flush everything else */ + while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1) + continue; - DEBUG3(JOB, "(%d) aborting %d, deposit token %c\n", - getpid(), aborting, tok); - while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) - continue; + DEBUG3(JOB, "(%d) aborting %d, deposit token %c\n", + getpid(), aborting, tok); + while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) + continue; } /* Prep the job token pipe in the root make process. */ void Job_ServerStart(int max_tokens, int jp_0, int jp_1) { - int i; - char jobarg[64]; - - if (jp_0 >= 0 && jp_1 >= 0) { - /* Pipe passed in from parent */ - tokenWaitJob.inPipe = jp_0; - tokenWaitJob.outPipe = jp_1; - (void)fcntl(jp_0, F_SETFD, FD_CLOEXEC); - (void)fcntl(jp_1, F_SETFD, FD_CLOEXEC); - return; - } + int i; + char jobarg[64]; + + if (jp_0 >= 0 && jp_1 >= 0) { + /* Pipe passed in from parent */ + tokenWaitJob.inPipe = jp_0; + tokenWaitJob.outPipe = jp_1; + (void)fcntl(jp_0, F_SETFD, FD_CLOEXEC); + (void)fcntl(jp_1, F_SETFD, FD_CLOEXEC); + return; + } - JobCreatePipe(&tokenWaitJob, 15); + JobCreatePipe(&tokenWaitJob, 15); - snprintf(jobarg, sizeof jobarg, "%d,%d", + snprintf(jobarg, sizeof jobarg, "%d,%d", tokenWaitJob.inPipe, tokenWaitJob.outPipe); - Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); - Var_Append(MAKEFLAGS, jobarg, VAR_GLOBAL); + Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); + Var_Append(MAKEFLAGS, jobarg, VAR_GLOBAL); - /* - * Preload the job pipe with one token per job, save the one - * "extra" token for the primary job. - * - * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is - * larger than the write buffer size of the pipe, we will - * deadlock here. - */ - for (i = 1; i < max_tokens; i++) - JobTokenAdd(); + /* + * Preload the job pipe with one token per job, save the one + * "extra" token for the primary job. + * + * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is + * larger than the write buffer size of the pipe, we will + * deadlock here. + */ + for (i = 1; i < max_tokens; i++) + JobTokenAdd(); } /* Return a withdrawn token to the pool. */ void Job_TokenReturn(void) { - jobTokensRunning--; - if (jobTokensRunning < 0) - Punt("token botch"); - if (jobTokensRunning || JOB_TOKENS[aborting] != '+') - JobTokenAdd(); + jobTokensRunning--; + if (jobTokensRunning < 0) + Punt("token botch"); + if (jobTokensRunning != 0 || JOB_TOKENS[aborting] != '+') + JobTokenAdd(); } -/* Attempt to withdraw a token from the pool. +/* + * Attempt to withdraw a token from the pool. * * If pool is empty, set wantToken so that we wake up when a token is * released. * * Returns TRUE if a token was withdrawn, and FALSE if the pool is currently - * empty. */ + * empty. + */ Boolean Job_TokenWithdraw(void) { - char tok, tok1; - ssize_t count; + char tok, tok1; + ssize_t count; - wantToken = 0; - DEBUG3(JOB, "Job_TokenWithdraw(%d): aborting %d, running %d\n", - getpid(), aborting, jobTokensRunning); + wantToken = 0; + DEBUG3(JOB, "Job_TokenWithdraw(%d): aborting %d, running %d\n", + getpid(), aborting, jobTokensRunning); - if (aborting != ABORT_NONE || (jobTokensRunning >= opts.maxJobs)) - return FALSE; + if (aborting != ABORT_NONE || (jobTokensRunning >= opts.maxJobs)) + return FALSE; - count = read(tokenWaitJob.inPipe, &tok, 1); - if (count == 0) - Fatal("eof on job pipe!"); - if (count < 0 && jobTokensRunning != 0) { - if (errno != EAGAIN) { - Fatal("job pipe read: %s", strerror(errno)); + count = read(tokenWaitJob.inPipe, &tok, 1); + if (count == 0) + Fatal("eof on job pipe!"); + if (count < 0 && jobTokensRunning != 0) { + if (errno != EAGAIN) { + Fatal("job pipe read: %s", strerror(errno)); + } + DEBUG1(JOB, "(%d) blocked for token\n", getpid()); + wantToken = 1; + return FALSE; } - DEBUG1(JOB, "(%d) blocked for token\n", getpid()); - return FALSE; - } - if (count == 1 && tok != '+') { - /* make being abvorted - remove any other job tokens */ - DEBUG2(JOB, "(%d) aborted by token %c\n", getpid(), tok); - while (read(tokenWaitJob.inPipe, &tok1, 1) == 1) - continue; - /* And put the stopper back */ - while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) - continue; - if (shouldDieQuietly(NULL, 1)) - exit(2); - Fatal("A failure has been detected in another branch of the parallel make"); - } + if (count == 1 && tok != '+') { + /* make being aborted - remove any other job tokens */ + DEBUG2(JOB, "(%d) aborted by token %c\n", getpid(), tok); + while (read(tokenWaitJob.inPipe, &tok1, 1) == 1) + continue; + /* And put the stopper back */ + while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && + errno == EAGAIN) + continue; + if (shouldDieQuietly(NULL, 1)) + exit(6); /* we aborted */ + Fatal("A failure has been detected " + "in another branch of the parallel make"); + } - if (count == 1 && jobTokensRunning == 0) - /* We didn't want the token really */ - while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) - continue; + if (count == 1 && jobTokensRunning == 0) + /* We didn't want the token really */ + while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && + errno == EAGAIN) + continue; - jobTokensRunning++; - DEBUG1(JOB, "(%d) withdrew token\n", getpid()); - return TRUE; + jobTokensRunning++; + DEBUG1(JOB, "(%d) withdrew token\n", getpid()); + return TRUE; } -/* Run the named target if found. If a filename is specified, then set that +/* + * Run the named target if found. If a filename is specified, then set that * to the sources. * - * Exits if the target fails. */ + * Exits if the target fails. + */ Boolean -Job_RunTarget(const char *target, const char *fname) { - GNode *gn = Targ_FindNode(target); - if (gn == NULL) - return FALSE; +Job_RunTarget(const char *target, const char *fname) +{ + GNode *gn = Targ_FindNode(target); + if (gn == NULL) + return FALSE; - if (fname) - Var_Set(ALLSRC, fname, gn); + if (fname != NULL) + Var_Set(ALLSRC, fname, gn); - JobRun(gn); - if (gn->made == ERROR) { - PrintOnError(gn, "\n\nStop."); - exit(1); - } - return TRUE; + JobRun(gn); + /* XXX: Replace with GNode_IsError(gn) */ + if (gn->made == ERROR) { + PrintOnError(gn, "\n\nStop."); + exit(1); + } + return TRUE; } #ifdef USE_SELECT int emul_poll(struct pollfd *fd, int nfd, int timeout) { - fd_set rfds, wfds; - int i, maxfd, nselect, npoll; - struct timeval tv, *tvp; - long usecs; + fd_set rfds, wfds; + int i, maxfd, nselect, npoll; + struct timeval tv, *tvp; + long usecs; - FD_ZERO(&rfds); - FD_ZERO(&wfds); + FD_ZERO(&rfds); + FD_ZERO(&wfds); - maxfd = -1; - for (i = 0; i < nfd; i++) { - fd[i].revents = 0; + maxfd = -1; + for (i = 0; i < nfd; i++) { + fd[i].revents = 0; - if (fd[i].events & POLLIN) - FD_SET(fd[i].fd, &rfds); + if (fd[i].events & POLLIN) + FD_SET(fd[i].fd, &rfds); - if (fd[i].events & POLLOUT) - FD_SET(fd[i].fd, &wfds); + if (fd[i].events & POLLOUT) + FD_SET(fd[i].fd, &wfds); - if (fd[i].fd > maxfd) - maxfd = fd[i].fd; - } + if (fd[i].fd > maxfd) + maxfd = fd[i].fd; + } - if (maxfd >= FD_SETSIZE) { - Punt("Ran out of fd_set slots; " - "recompile with a larger FD_SETSIZE."); - } + if (maxfd >= FD_SETSIZE) { + Punt("Ran out of fd_set slots; " + "recompile with a larger FD_SETSIZE."); + } - if (timeout < 0) { - tvp = NULL; - } else { - usecs = timeout * 1000; - tv.tv_sec = usecs / 1000000; - tv.tv_usec = usecs % 1000000; - tvp = &tv; - } + if (timeout < 0) { + tvp = NULL; + } else { + usecs = timeout * 1000; + tv.tv_sec = usecs / 1000000; + tv.tv_usec = usecs % 1000000; + tvp = &tv; + } - nselect = select(maxfd + 1, &rfds, &wfds, NULL, tvp); + nselect = select(maxfd + 1, &rfds, &wfds, NULL, tvp); - if (nselect <= 0) - return nselect; + if (nselect <= 0) + return nselect; - npoll = 0; - for (i = 0; i < nfd; i++) { - if (FD_ISSET(fd[i].fd, &rfds)) - fd[i].revents |= POLLIN; + npoll = 0; + for (i = 0; i < nfd; i++) { + if (FD_ISSET(fd[i].fd, &rfds)) + fd[i].revents |= POLLIN; - if (FD_ISSET(fd[i].fd, &wfds)) - fd[i].revents |= POLLOUT; + if (FD_ISSET(fd[i].fd, &wfds)) + fd[i].revents |= POLLOUT; - if (fd[i].revents) - npoll++; - } + if (fd[i].revents) + npoll++; + } - return npoll; + return npoll; } #endif /* USE_SELECT */ |