diff options
Diffstat (limited to 'contrib/bmake/job.c')
-rw-r--r-- | contrib/bmake/job.c | 251 |
1 files changed, 86 insertions, 165 deletions
diff --git a/contrib/bmake/job.c b/contrib/bmake/job.c index bd85e6d95fb8..d06b1570ba85 100644 --- a/contrib/bmake/job.c +++ b/contrib/bmake/job.c @@ -1,4 +1,4 @@ -/* $NetBSD: job.c,v 1.485 2025/01/19 10:57:10 rillig Exp $ */ +/* $NetBSD: job.c,v 1.489 2025/03/08 20:15:03 rillig Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. @@ -102,10 +102,7 @@ * define the shell that is used for the creation * commands in jobs mode. * - * Job_Finish Perform any final processing which needs doing. - * This includes the execution of any commands - * which have been/were attached to the .END - * target. It should only be called when the + * Job_Finish Make the .END target. Should only be called when the * job table is empty. * * Job_AbortAll Abort all currently running jobs. Do not handle @@ -154,7 +151,7 @@ #include "trace.h" /* "@(#)job.c 8.2 (Berkeley) 3/19/94" */ -MAKE_RCSID("$NetBSD: job.c,v 1.485 2025/01/19 10:57:10 rillig Exp $"); +MAKE_RCSID("$NetBSD: job.c,v 1.489 2025/03/08 20:15:03 rillig Exp $"); /* * A shell defines how the commands are run. All commands for a target are @@ -689,7 +686,7 @@ JobPassSig_suspend(int signo) act.sa_flags = 0; (void)sigaction(signo, &act, NULL); - DEBUG1(JOB, "JobPassSig passing signal %d to self.\n", signo); + DEBUG1(JOB, "JobPassSig_suspend passing signal %d to self.\n", signo); (void)kill(getpid(), signo); @@ -1251,8 +1248,16 @@ JobFinish (Job *job, WAIT_T status) if (return_job_token) Job_TokenReturn(); - if (aborting == ABORT_ERROR && jobTokensRunning == 0) - Finish(job_errors); + if (aborting == ABORT_ERROR && jobTokensRunning == 0) { + if (shouldDieQuietly(NULL, -1)) { + /* + * TODO: better clean up properly, to avoid killing + * child processes by SIGPIPE. + */ + exit(2); + } + Fatal("%d error%s", job_errors, job_errors == 1 ? "" : "s"); + } } static void @@ -1962,27 +1967,14 @@ JobRun(GNode *targ) } } -/* - * Handle the exit of a child. Called from Make_Make. - * - * The job descriptor is removed from the list of children. - * - * Notes: - * We do waits, blocking or not, according to the wisdom of our - * caller, until there are no more children to report. For each - * job, call JobFinish to finish things off. - */ void Job_CatchChildren(void) { - int pid; /* pid of dead child */ - WAIT_T status; /* Exit/termination status */ + int pid; + WAIT_T status; - /* Don't even bother if we know there's no one around. */ if (jobTokensRunning == 0) return; - - /* Have we received SIGCHLD since last call? */ if (caught_sigchld == 0) return; caught_sigchld = 0; @@ -2001,21 +1993,19 @@ Job_CatchChildren(void) void JobReapChild(pid_t pid, WAIT_T status, bool isJobs) { - Job *job; /* job descriptor for dead child */ + Job *job; - /* 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 */ + if (isJobs && !lurking_children) + Error("Child (%d) status %x not in table?", + pid, status); + return; } + if (WIFSTOPPED(status)) { DEBUG2(JOB, "Process %d (%s) stopped.\n", job->pid, job->node->name); @@ -2047,12 +2037,6 @@ JobReapChild(pid_t pid, WAIT_T status, bool isJobs) JobFinish(job, status); } -/* - * 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. - */ void Job_CatchOutput(void) { @@ -2062,7 +2046,7 @@ Job_CatchOutput(void) (void)fflush(stdout); - /* The first fd in the list is the job token pipe */ + /* Skip the first fd in the list, as it is the job token pipe. */ do { nready = poll(fds + 1 - wantToken, fdsLen - 1 + wantToken, POLL_MSEC); @@ -2073,13 +2057,12 @@ Job_CatchOutput(void) if (nready > 0 && readyfd(&childExitJob)) { char token = 0; - ssize_t count; - count = read(childExitJob.inPipe, &token, 1); + ssize_t count = read(childExitJob.inPipe, &token, 1); if (count == 1) { if (token == DO_JOB_RESUME[0]) /* * Complete relay requested from our SIGCONT - * handler + * handler. */ JobRestartJobs(); } else if (count == 0) @@ -2160,10 +2143,7 @@ Shell_Init(void) } } -/* - * Return the string literal that is used in the current command shell - * to produce a newline character. - */ +/* Return the shell string literal that results in a newline character. */ const char * Shell_GetNewline(void) { @@ -2192,12 +2172,11 @@ AddSig(int sig, SignalProc 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; @@ -2214,8 +2193,8 @@ Job_Init(void) /* - * There is a non-zero chance that we already have children. - * eg after 'make -f- <<EOF' + * There is a non-zero chance that we already have children, + * e.g. 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. @@ -2237,7 +2216,6 @@ Job_Init(void) JobCreatePipe(&childExitJob, 3); { - /* Preallocate enough for the maximum number of jobs. */ size_t nfds = (npseudojobs + (size_t)opts.maxJobs) * nfds_per_job(); fds = bmake_malloc(sizeof *fds * nfds); @@ -2249,24 +2227,19 @@ Job_Init(void) watchfd(&childExitJob); sigemptyset(&caught_signals); - /* Install a SIGCHLD handler. */ (void)bmake_signal(SIGCHLD, JobChildSig); sigaddset(&caught_signals, SIGCHLD); - /* - * Catch the four signals that POSIX specifies if they aren't ignored. - * JobPassSig will take care of calling JobInterrupt if appropriate. - */ + /* Handle the signals specified by POSIX. */ AddSig(SIGINT, JobPassSig_int); AddSig(SIGHUP, JobPassSig_term); AddSig(SIGTERM, JobPassSig_term); AddSig(SIGQUIT, JobPassSig_term); /* - * 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) + * These signals need to be passed to the jobs, as each job has its + * own process group and thus the terminal driver doesn't forward the + * signals itself. */ AddSig(SIGTSTP, JobPassSig_suspend); AddSig(SIGTTOU, JobPassSig_suspend); @@ -2275,10 +2248,7 @@ Job_Init(void) 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_MakeAll. - */ + /* Create the .END node, see Targ_GetEndNode in Compat_MakeAll. */ (void)Targ_GetEndNode(); } @@ -2304,7 +2274,6 @@ JobSigReset(void) (void)bmake_signal(SIGCHLD, SIG_DFL); } -/* Find a shell in 'shells' given its name, or return NULL. */ static Shell * FindShellByName(const char *name) { @@ -2326,40 +2295,35 @@ FindShellByName(const char *name) * line The shell spec * * Results: - * false if the specification was incorrect. - * - * Side Effects: - * 'shell' points to a Shell structure (either predefined or - * created from the shell spec), shellPath is the full path of the - * shell described by 'shell', while shellName is just the - * final component of shellPath. + * Returns false if the specification was incorrect. + * If successful, 'shell' is usable, shellPath is the full path of the + * shell described by 'shell', and shellName is the final component of + * shellPath. * * Notes: - * A shell specification consists of a .SHELL target, with dependency - * operator, followed by a series of blank-separated words. Double - * quotes can be used to use blanks in words. A backslash escapes + * A shell specification has the form ".SHELL: keyword=value...". Double + * quotes can be used to enclose blanks in words. A backslash escapes * anything (most notably a double-quote and a space) and - * provides the functionality it does in C. Each word consists of - * keyword and value separated by an equal sign. There should be no - * unnecessary spaces in the word. The keywords are as follows: + * provides the usual escape sequences from C. There should be no + * unnecessary spaces in the word. The keywords are: * name Name of shell. * path Location of shell. * quiet Command to turn off echoing. * echo Command to turn echoing on - * filter Result of turning off echoing that shouldn't be - * printed. - * echoFlag Flag to turn echoing on at the start - * errFlag Flag to turn error checking on at the start - * hasErrCtl True if shell has error checking control - * newline String literal to represent a newline char - * check Command to turn on error checking if hasErrCtl - * is true or template of command to echo a command - * for which error checking is off if hasErrCtl is - * false. - * ignore Command to turn off error checking if hasErrCtl - * is true or template of command to execute a - * command so as to ignore any errors it returns if - * hasErrCtl is false. + * filter The output from the shell command that turns off + * echoing, to be filtered from the final output. + * echoFlag Flag to turn echoing on at the start. + * errFlag Flag to turn error checking on at the start. + * hasErrCtl True if the shell has error checking control. + * newline String literal to represent a newline character. + * check If hasErrCtl is true: The command to turn on error + * checking. If hasErrCtl is false: The template for a + * shell command that echoes a command for which error + * checking is off. + * ignore If hasErrCtl is true: The command to turn off error + * checking. If hasErrCtl is false: The template for a + * shell command that executes a command so as to ignore + * any errors it returns. */ bool Job_ParseShell(char *line) @@ -2380,7 +2344,6 @@ Job_ParseShell(char *line) memset(&newShell, 0, sizeof newShell); - /* Parse the specification by keyword. */ wordsList = Str_Words(line, true); words = wordsList.words; argc = wordsList.len; @@ -2444,12 +2407,6 @@ Job_ParseShell(char *line) } 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"); @@ -2465,10 +2422,6 @@ Job_ParseShell(char *line) shell = sh; shellName = newShell.name; if (shellPath != NULL) { - /* - * Shell_Init has already been called! - * Do it again. - */ free(shellPath); shellPath = NULL; Shell_Init(); @@ -2491,7 +2444,7 @@ Job_ParseShell(char *line) shell = bmake_malloc(sizeof *shell); *shell = newShell; } - /* this will take care of shellErrFlag */ + /* This will take care of shellErrFlag. */ Shell_Init(); } @@ -2506,7 +2459,7 @@ Job_ParseShell(char *line) } /* - * Do not free up the words themselves, since they might be in use + * Do not free up the words themselves, since they may be in use * by the shell specification. */ free(words); @@ -2514,36 +2467,22 @@ Job_ParseShell(char *line) } /* - * Handle the receipt of an interrupt. - * - * All children are killed. Another job will be started if the .INTERRUPT - * target is defined. - * - * Input: - * runINTERRUPT Non-zero if commands for the .INTERRUPT target - * should be executed - * signo signal received + * After receiving an interrupt signal, terminate all child processes and if + * necessary make the .INTERRUPT target. */ static void JobInterrupt(bool runINTERRUPT, int signo) { - Job *job; /* job descriptor in that element */ - GNode *interrupt; /* the node describing the .INTERRUPT target */ + Job *job; + GNode *interrupt; sigset_t mask; - GNode *gn; aborting = ABORT_INTERRUPT; JobSigLock(&mask); for (job = job_table; job < job_table_end; job++) { - if (job->status != JOB_ST_RUNNING) - continue; - - gn = job->node; - - JobDeleteTarget(gn); - if (job->pid != 0) { + if (job->status == JOB_ST_RUNNING && job->pid != 0) { DEBUG2(JOB, "JobInterrupt passing signal %d to child %d.\n", signo, job->pid); @@ -2551,6 +2490,14 @@ JobInterrupt(bool runINTERRUPT, int signo) } } + for (job = job_table; job < job_table_end; job++) { + if (job->status == JOB_ST_RUNNING && job->pid != 0) { + int status; + (void)waitpid(job->pid, &status, 0); + JobDeleteTarget(job->node); + } + } + JobSigUnlock(&mask); if (runINTERRUPT && !opts.touch) { @@ -2564,11 +2511,7 @@ JobInterrupt(bool runINTERRUPT, int signo) exit(signo); /* XXX: why signo? */ } -/* - * Do the final processing, i.e. run the commands attached to the .END target. - * - * Return the number of errors reported. - */ +/* Make the .END target, returning the number of job-related errors. */ int Job_Finish(void) { @@ -2584,7 +2527,6 @@ Job_Finish(void) } #ifdef CLEANUP -/* Clean up any memory used by the jobs module. */ void Job_End(void) { @@ -2592,14 +2534,11 @@ Job_End(void) } #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. */ void Job_Wait(void) { - aborting = ABORT_WAIT; + aborting = ABORT_WAIT; /* Prevent other jobs from starting. */ while (jobTokensRunning != 0) { Job_CatchOutput(); } @@ -2610,14 +2549,12 @@ Job_Wait(void) * 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. */ void Job_AbortAll(void) { - Job *job; /* the job descriptor in that element */ - WAIT_T foo; + Job *job; + WAIT_T status; aborting = ABORT_ERROR; @@ -2625,25 +2562,18 @@ Job_AbortAll(void) 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) + while (waitpid((pid_t)-1, &status, WNOHANG) > 0) continue; } /* * Tries to restart stopped jobs if there are slots available. - * Called in process context in response to a SIGCONT. + * Called in response to a SIGCONT. */ static void JobRestartJobs(void) @@ -2708,10 +2638,6 @@ clearfd(Job *job) fdsLen--; #if defined(USE_FILEMON) && !defined(USE_FILEMON_DEV) 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"); @@ -2756,7 +2682,7 @@ JobTokenAdd(void) tok = '+'; /* no error token */ } - /* If we are depositing an error token flush everything else */ + /* If we are depositing an error token, flush everything else. */ while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1) continue; @@ -2766,7 +2692,6 @@ JobTokenAdd(void) continue; } -/* Get a temp file */ int Job_TempFile(const char *pattern, char *tfile, size_t tfile_sz) { @@ -2782,7 +2707,7 @@ Job_TempFile(const char *pattern, char *tfile, size_t tfile_sz) return fd; } -/* Prep the job token pipe in the root make process. */ +/* Prepare the job token pipe in the root make process. */ void Job_ServerStart(int max_tokens, int jp_0, int jp_1) { @@ -2790,7 +2715,6 @@ Job_ServerStart(int max_tokens, int jp_0, int jp_1) 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); @@ -2832,7 +2756,7 @@ Job_TokenReturn(void) /* * Attempt to withdraw a token from the pool. * - * If pool is empty, set wantToken so that we wake up when a token is + * If the 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 @@ -2871,8 +2795,10 @@ Job_TokenWithdraw(void) while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) continue; - if (shouldDieQuietly(NULL, 1)) - exit(6); /* we aborted */ + if (shouldDieQuietly(NULL, 1)) { + Job_Wait(); + exit(6); + } Fatal("A failure has been detected " "in another branch of the parallel make"); } @@ -2888,12 +2814,7 @@ Job_TokenWithdraw(void) return true; } -/* - * Run the named target if found. If a filename is specified, then set that - * to the sources. - * - * Exits if the target fails. - */ +/* Make the named target if found, exit if the target fails. */ bool Job_RunTarget(const char *target, const char *fname) { |