Lines Matching full:job

1 /*	$NetBSD: job.c,v 1.519 2025/08/04 15:40:39 sjg Exp $	*/
93 * job table is empty.
132 #include "job.h"
139 /* "@(#)job.c 8.2 (Berkeley) 3/19/94" */
140 MAKE_RCSID("$NetBSD: job.c,v 1.519 2025/08/04 15:40:39 sjg Exp $");
168 JOB_ST_FREE, /* Job is available */
169 JOB_ST_SET_UP, /* Job is allocated but otherwise invalid */
170 JOB_ST_RUNNING, /* Job is running, pid valid */
171 JOB_ST_FINISHED /* Job is done (i.e. after SIGCHLD) */
182 * A Job manages the shell commands that are run to create a single target.
183 * Each job is run in a separate subprocess by a shell. Several jobs can run
190 * When a job is finished, Make_Update updates all parents of the node
194 struct Job { struct
224 int inPipe; /* Pipe for reading output from job */
229 /* Buffer for storing the output of the job, line by line. */
513 static Job *job_table; /* The structures that describe them */
514 static Job *job_table_end; /* job_table + maxJobs */
524 static Job **jobByFdIndex = NULL;
526 static void watchfd(Job *);
527 static void clearfd(Job *);
529 static char *targPrefix = NULL; /* To identify a job change in the output. */
531 static Job tokenPoolJob; /* token wait pseudo-job */
533 static Job childExitJob; /* child exit pseudo-job */
544 static void CollectOutput(Job *, bool);
573 Job_FlagsToString(const Job *job, char *buf, size_t bufsize) in Job_FlagsToString() argument
576 job->ignerr ? 'i' : '-', in Job_FlagsToString()
577 !job->echo ? 's' : '-', in Job_FlagsToString()
578 job->special ? 'S' : '-'); in Job_FlagsToString()
583 Job_BuildMon(Job *job) in Job_BuildMon() argument
585 return &job->bm; in Job_BuildMon()
590 Job_Node(Job *job) in Job_Node() argument
592 return job->node; in Job_Node()
596 Job_Pid(Job *job) in Job_Pid() argument
598 return job->pid; in Job_Pid()
604 const Job *job; in JobTable_Dump() local
607 debug_printf("%s, job table:\n", where); in JobTable_Dump()
608 for (job = job_table; job < job_table_end; job++) { in JobTable_Dump()
609 Job_FlagsToString(job, flags, sizeof flags); in JobTable_Dump()
610 debug_printf("job %d, status %s, flags %s, pid %d\n", in JobTable_Dump()
611 (int)(job - job_table), JobStatus_Name[job->status], in JobTable_Dump()
612 flags, job->pid); in JobTable_Dump()
618 * unsuccessful job unless inhibited by .PRECIOUS.
673 JobCreatePipe(Job *job, int minfd) in JobCreatePipe() argument
690 job->inPipe = pipe_fds[0]; in JobCreatePipe()
691 job->outPipe = pipe_fds[1]; in JobCreatePipe()
693 SetCloseOnExec(job->inPipe); in JobCreatePipe()
694 SetCloseOnExec(job->outPipe); in JobCreatePipe()
698 * pipe when we're waiting for a job token, but we might lose the in JobCreatePipe()
702 SetNonblocking(job->inPipe); in JobCreatePipe()
705 /* Pass the signal to each running job. */
709 Job *job; in JobCondPassSig() local
711 DEBUG1(JOB, "JobCondPassSig: signal %d\n", signo); in JobCondPassSig()
713 for (job = job_table; job < job_table_end; job++) { in JobCondPassSig()
714 if (job->status != JOB_ST_RUNNING) in JobCondPassSig()
716 DEBUG2(JOB, "JobCondPassSig passing signal %d to pid %d\n", in JobCondPassSig()
717 signo, job->pid); in JobCondPassSig()
718 KILLPG(job->pid, signo); in JobCondPassSig()
771 /* Suppress job started/continued messages */ in JobPassSig_suspend()
774 /* Pass the signal onto every job */ in JobPassSig_suspend()
792 DEBUG1(JOB, "JobPassSig_suspend passing signal %d to self\n", signo); in JobPassSig_suspend()
820 static Job *
823 Job *job; in JobFindPid() local
825 for (job = job_table; job < job_table_end; job++) { in JobFindPid()
826 if (job->status == status && job->pid == pid) in JobFindPid()
827 return job; in JobFindPid()
829 if (DEBUG(JOB) && isJobs) in JobFindPid()
881 DEBUG1(JOB, fmt, arg); in ShellWriter_WriteFmt()
949 JobWriteSpecialsEchoCtl(Job *job, ShellWriter *wr, CommandFlags *inout_cmdFlags, in JobWriteSpecialsEchoCtl() argument
952 /* XXX: Why is the whole job modified at this point? */ in JobWriteSpecialsEchoCtl()
953 job->ignerr = true; in JobWriteSpecialsEchoCtl()
955 if (job->echo && inout_cmdFlags->echo) { in JobWriteSpecialsEchoCtl()
976 JobWriteSpecials(Job *job, ShellWriter *wr, const char *escCmd, bool run, in JobWriteSpecials() argument
982 ShellWriter_ErrOff(wr, job->echo && inout_cmdFlags->echo); in JobWriteSpecials()
984 JobWriteSpecialsEchoCtl(job, wr, inout_cmdFlags, escCmd, in JobWriteSpecials()
991 * Write a shell command to the job's commands file, to be run later.
997 * of the predefined shells has that), ignore errors for the rest of the job.
999 * XXX: Why ignore errors for the entire job? This is documented in the
1002 * If the command is just "...", attach all further commands of this job to
1006 JobWriteCommand(Job *job, ShellWriter *wr, StringListNode *ln, const char *ucmd) in JobWriteCommand() argument
1017 run = GNode_ShouldExecute(job->node); in JobWriteCommand()
1019 xcmd = Var_SubstInTarget(ucmd, job->node); in JobWriteCommand()
1033 (void)Compat_RunCommand(ucmd, job->node, ln); in JobWriteCommand()
1046 if (job->echo && run && shell->hasEchoCtl) in JobWriteCommand()
1053 JobWriteSpecials(job, wr, escCmd, run, &cmdFlags, &cmdTemplate); in JobWriteCommand()
1064 if (job->echo && cmdFlags.echo) { in JobWriteCommand()
1089 ShellWriter_ErrOn(wr, cmdFlags.echo && job->echo); in JobWriteCommand()
1104 JobWriteCommands(Job *job) in JobWriteCommands() argument
1110 wr.f = job->cmdFILE; in JobWriteCommands()
1113 for (ln = job->node->commands.first; ln != NULL; ln = ln->next) { in JobWriteCommands()
1117 job->node->type |= OP_SAVE_CMDS; in JobWriteCommands()
1118 job->tailCmds = ln->next; in JobWriteCommands()
1122 JobWriteCommand(job, &wr, ln, ln->datum); in JobWriteCommands()
1134 JobSaveCommands(Job *job) in JobSaveCommands() argument
1138 for (ln = job->tailCmds; ln != NULL; ln = ln->next) { in JobSaveCommands()
1146 expanded_cmd = Var_SubstInTarget(cmd, job->node); in JobSaveCommands()
1154 /* Close both input and output pipes when a job is finished. */
1156 JobClosePipes(Job *job) in JobClosePipes() argument
1158 clearfd(job); in JobClosePipes()
1159 (void)close(job->outPipe); in JobClosePipes()
1160 job->outPipe = -1; in JobClosePipes()
1162 CollectOutput(job, true); in JobClosePipes()
1163 (void)close(job->inPipe); in JobClosePipes()
1164 job->inPipe = -1; in JobClosePipes()
1168 DebugFailedJob(const Job *job) in DebugFailedJob() argument
1176 debug_printf("*** Failed target: %s\n", job->node->name); in DebugFailedJob()
1179 for (ln = job->node->commands.first; ln != NULL; ln = ln->next) { in DebugFailedJob()
1184 char *xcmd = Var_Subst(cmd, job->node, VARE_EVAL); in DebugFailedJob()
1192 JobFinishDoneExitedError(Job *job, WAIT_T *inout_status) in JobFinishDoneExitedError() argument
1194 SwitchOutputTo(job->node); in JobFinishDoneExitedError()
1197 meta_job_error(job, job->node, in JobFinishDoneExitedError()
1198 job->ignerr, WEXITSTATUS(*inout_status)); in JobFinishDoneExitedError()
1201 if (!shouldDieQuietly(job->node, -1)) { in JobFinishDoneExitedError()
1202 DebugFailedJob(job); in JobFinishDoneExitedError()
1204 job->node->name, WEXITSTATUS(*inout_status), in JobFinishDoneExitedError()
1205 job->ignerr ? " (ignored)" : ""); in JobFinishDoneExitedError()
1208 if (job->ignerr) in JobFinishDoneExitedError()
1212 JobDeleteTarget(job->node); in JobFinishDoneExitedError()
1213 PrintOnError(job->node, "\n"); in JobFinishDoneExitedError()
1218 JobFinishDoneExited(Job *job, WAIT_T *inout_status) in JobFinishDoneExited() argument
1220 DEBUG2(JOB, "Target %s, pid %d exited\n", in JobFinishDoneExited()
1221 job->node->name, job->pid); in JobFinishDoneExited()
1224 JobFinishDoneExitedError(job, inout_status); in JobFinishDoneExited()
1225 else if (DEBUG(JOB)) { in JobFinishDoneExited()
1226 SwitchOutputTo(job->node); in JobFinishDoneExited()
1228 job->node->name, job->pid); in JobFinishDoneExited()
1233 JobFinishDoneSignaled(Job *job, WAIT_T status) in JobFinishDoneSignaled() argument
1235 SwitchOutputTo(job->node); in JobFinishDoneSignaled()
1236 DebugFailedJob(job); in JobFinishDoneSignaled()
1237 (void)printf("*** [%s] Signal %d\n", job->node->name, WTERMSIG(status)); in JobFinishDoneSignaled()
1239 JobDeleteTarget(job->node); in JobFinishDoneSignaled()
1243 JobFinishDone(Job *job, WAIT_T *inout_status) in JobFinishDone() argument
1246 JobFinishDoneExited(job, inout_status); in JobFinishDone()
1248 JobFinishDoneSignaled(job, *inout_status); in JobFinishDone()
1254 * Finish the job, add deferred commands to the .END node, mark the job as
1258 JobFinish (Job *job, WAIT_T status) in JobFinish() argument
1262 DEBUG3(JOB, "JobFinish: target %s, pid %d, status %#x\n", in JobFinish()
1263 job->node->name, job->pid, status); in JobFinish()
1266 ((WEXITSTATUS(status) != 0 && !job->ignerr))) || in JobFinish()
1270 JobClosePipes(job); in JobFinish()
1271 if (job->cmdFILE != NULL && job->cmdFILE != stdout) { in JobFinish()
1272 if (fclose(job->cmdFILE) != 0) in JobFinish()
1274 job->node->name, strerror(errno)); in JobFinish()
1275 job->cmdFILE = NULL; in JobFinish()
1287 JobClosePipes(job); in JobFinish()
1295 JobFinishDone(job, &status); in JobFinish()
1299 int meta_status = meta_job_finish(job); in JobFinish()
1307 Trace_Log(JOBEND, job); in JobFinish()
1308 if (!job->special) { in JobFinish()
1316 JobSaveCommands(job); in JobFinish()
1317 job->node->made = MADE; in JobFinish()
1318 if (!job->special) in JobFinish()
1320 Make_Update(job->node); in JobFinish()
1321 job->status = JOB_ST_FREE; in JobFinish()
1324 job->status = JOB_ST_FREE; in JobFinish()
1494 * Execute the shell for the given job.
1499 JobExec(Job *job, char **argv) in JobExec() argument
1504 if (DEBUG(JOB)) { in JobExec()
1507 debug_printf("Running %s\n", job->node->name); in JobExec()
1521 if (job->echo) in JobExec()
1522 SwitchOutputTo(job->node); in JobExec()
1524 /* No interruptions until this job is in the jobs table. */ in JobExec()
1527 /* Pre-emptively mark job running, pid still zero though */ in JobExec()
1528 job->status = JOB_ST_RUNNING; in JobExec()
1530 Var_ReexportVars(job->node); in JobExec()
1531 Var_ExportStackTrace(job->node->name, NULL); in JobExec()
1543 meta_job_child(job); in JobExec()
1554 if (dup2(fileno(job->cmdFILE), STDIN_FILENO) == -1) in JobExec()
1555 execDie("dup2", "job->cmdFILE"); in JobExec()
1562 (job->node->type & (OP_MAKE | OP_SUBMAKE))) { in JobExec()
1563 /* Pass job token pipe to submakes. */ in JobExec()
1572 if (dup2(job->outPipe, STDOUT_FILENO) == -1) in JobExec()
1573 execDie("dup2", "job->outPipe"); in JobExec()
1608 job->pid = cpid; in JobExec()
1610 Trace_Log(JOBSTART, job); in JobExec()
1614 meta_job_parent(job, cpid); in JobExec()
1617 job->outBufLen = 0; in JobExec()
1619 watchfd(job); in JobExec()
1621 if (job->cmdFILE != NULL && job->cmdFILE != stdout) { in JobExec()
1622 if (fclose(job->cmdFILE) != 0) in JobExec()
1624 job->node->name, strerror(errno)); in JobExec()
1625 job->cmdFILE = NULL; in JobExec()
1628 if (DEBUG(JOB)) { in JobExec()
1631 job->node->name, job->pid); in JobExec()
1632 JobTable_Dump("job started"); in JobExec()
1638 BuildArgv(Job *job, char **argv) in BuildArgv() argument
1659 !job->ignerr && shell->errFlag != NULL in BuildArgv()
1661 job->echo && shell->echoFlag != NULL in BuildArgv()
1668 if (!job->ignerr && shell->errFlag != NULL) { in BuildArgv()
1672 if (job->echo && shell->echoFlag != NULL) { in BuildArgv()
1681 JobWriteShellCommands(Job *job, GNode *gn, bool *out_run) in JobWriteShellCommands() argument
1688 job->cmdFILE = fdopen(fd, "w+"); in JobWriteShellCommands()
1689 if (job->cmdFILE == NULL) in JobWriteShellCommands()
1696 meta_job_start(job, gn); in JobWriteShellCommands()
1698 job->echo = false; in JobWriteShellCommands()
1702 *out_run = JobWriteCommands(job); in JobWriteShellCommands()
1708 Job *job; in Job_Make() local
1713 for (job = job_table; job < job_table_end; job++) { in Job_Make()
1714 if (job->status == JOB_ST_FREE) in Job_Make()
1717 if (job >= job_table_end) in Job_Make()
1718 Punt("Job_Make no job slots vacant"); in Job_Make()
1720 memset(job, 0, sizeof *job); in Job_Make()
1721 job->node = gn; in Job_Make()
1722 job->tailCmds = NULL; in Job_Make()
1723 job->status = JOB_ST_SET_UP; in Job_Make()
1725 job->special = (gn->type & OP_SPECIAL) != OP_NONE; in Job_Make()
1726 job->ignerr = opts.ignoreErrors || gn->type & OP_IGNORE; in Job_Make()
1727 job->echo = !(opts.silent || gn->type & OP_SILENT); in Job_Make()
1735 job->inPollfd = NULL; in Job_Make()
1738 job->cmdFILE = stdout; in Job_Make()
1755 JobWriteShellCommands(job, gn, &run); in Job_Make()
1758 (void)fflush(job->cmdFILE); in Job_Make()
1761 job->cmdFILE = stdout; in Job_Make()
1763 JobWriteCommands(job); in Job_Make()
1765 (void)fflush(job->cmdFILE); in Job_Make()
1767 Job_Touch(gn, job->echo); in Job_Make()
1772 if (!job->special) in Job_Make()
1775 if (job->cmdFILE != NULL && job->cmdFILE != stdout) { in Job_Make()
1776 (void)fclose(job->cmdFILE); in Job_Make()
1777 job->cmdFILE = NULL; in Job_Make()
1781 JobSaveCommands(job); in Job_Make()
1782 job->node->made = MADE; in Job_Make()
1783 Make_Update(job->node); in Job_Make()
1785 job->status = JOB_ST_FREE; in Job_Make()
1789 BuildArgv(job, argv); in Job_Make()
1790 JobCreatePipe(job, 3); in Job_Make()
1791 JobExec(job, argv); in Job_Make()
1803 PrintFilteredOutput(Job *job, size_t len) in PrintFilteredOutput() argument
1805 const char *p = job->outBuf, *ep, *endp; in PrintFilteredOutput()
1814 SwitchOutputTo(job->node); in PrintFilteredOutput()
1828 * Collect output from the job. Print any complete lines.
1834 * If finish is true, collect all remaining output for the job.
1837 CollectOutput(Job *job, bool finish) in CollectOutput() argument
1845 nr = (size_t)read(job->inPipe, job->outBuf + job->outBufLen, in CollectOutput()
1846 JOB_BUFSIZE - job->outBufLen); in CollectOutput()
1850 if (DEBUG(JOB)) in CollectOutput()
1858 if (nr == 0 && job->outBufLen > 0) { in CollectOutput()
1859 job->outBuf[job->outBufLen] = '\n'; in CollectOutput()
1863 max = job->outBufLen + nr; in CollectOutput()
1864 job->outBuf[max] = '\0'; in CollectOutput()
1866 for (i = job->outBufLen; i < max; i++) in CollectOutput()
1867 if (job->outBuf[i] == '\0') in CollectOutput()
1868 job->outBuf[i] = ' '; in CollectOutput()
1870 for (i = max; i > job->outBufLen; i--) in CollectOutput()
1871 if (job->outBuf[i - 1] == '\n') in CollectOutput()
1874 if (i == job->outBufLen) { in CollectOutput()
1875 job->outBufLen = max; in CollectOutput()
1881 p = PrintFilteredOutput(job, i); in CollectOutput()
1884 SwitchOutputTo(job->node); in CollectOutput()
1887 meta_job_output(job, p, (i < max) ? i : max); in CollectOutput()
1889 (void)fwrite(p, 1, (size_t)(job->outBuf + i - p), stdout); in CollectOutput()
1892 memmove(job->outBuf, job->outBuf + i, max - i); in CollectOutput()
1893 job->outBufLen = max - i; in CollectOutput()
1924 DEBUG2(JOB, in Job_CatchChildren()
1938 Job *job; in JobReapChild() local
1943 job = JobFindPid(pid, JOB_ST_RUNNING, isJobs); in JobReapChild()
1944 if (job == NULL) { in JobReapChild()
1952 DEBUG2(JOB, "Process for target %s, pid %d stopped\n", in JobReapChild()
1953 job->node->name, job->pid); in JobReapChild()
1958 job->node->name); in JobReapChild()
1962 job->node->name); in JobReapChild()
1966 job->node->name, WSTOPSIG(status)); in JobReapChild()
1968 job->suspended = true; in JobReapChild()
1974 job->status = JOB_ST_FINISHED; in JobReapChild()
1975 job->exit_status = WAIT_STATUS(status); in JobReapChild()
1977 job->node->exit_status = WEXITSTATUS(status); in JobReapChild()
1979 JobFinish(job, status); in JobReapChild()
1983 Job_Continue(Job *job) in Job_Continue() argument
1985 DEBUG1(JOB, "Continuing pid %d\n", job->pid); in Job_Continue()
1986 if (job->suspended) { in Job_Continue()
1987 (void)printf("*** [%s] Continued\n", job->node->name); in Job_Continue()
1989 job->suspended = false; in Job_Continue()
1991 if (KILLPG(job->pid, SIGCONT) != 0) in Job_Continue()
1992 DEBUG1(JOB, "Failed to send SIGCONT to pid %d\n", job->pid); in Job_Continue()
1998 Job *job; in ContinueJobs() local
2000 for (job = job_table; job < job_table_end; job++) { in ContinueJobs()
2001 if (job->status == JOB_ST_RUNNING && in ContinueJobs()
2002 (make_suspended || job->suspended)) in ContinueJobs()
2003 Job_Continue(job); in ContinueJobs()
2004 else if (job->status == JOB_ST_FINISHED) in ContinueJobs()
2005 JobFinish(job, job->exit_status); in ContinueJobs()
2014 Job *job; in Job_CatchOutput() local
2020 /* Maybe skip the job token pipe. */ in Job_CatchOutput()
2046 job = jobByFdIndex[i]; in Job_CatchOutput()
2047 if (job->status == JOB_ST_RUNNING) in Job_CatchOutput()
2048 CollectOutput(job, false); in Job_CatchOutput()
2051 * With meta mode, we may have activity on the job's filemon in Job_CatchOutput()
2053 * than job->inPollfd. in Job_CatchOutput()
2055 if (useMeta && job->inPollfd != &fds[i]) { in Job_CatchOutput()
2056 if (meta_job_event(job) <= 0) in Job_CatchOutput()
2122 else if (!Var_Exists(SCOPE_GLOBAL, ".MAKE.JOB.PREFIX")) in Job_SetPrefix()
2123 Global_Set(".MAKE.JOB.PREFIX", "---"); in Job_SetPrefix()
2125 targPrefix = Var_Subst("${.MAKE.JOB.PREFIX}", in Job_SetPrefix()
2204 * These signals need to be passed to the jobs, as each job has its in Job_Init()
2440 Job *job; in JobInterrupt() local
2447 for (job = job_table; job < job_table_end; job++) { in JobInterrupt()
2448 if (job->status == JOB_ST_RUNNING && job->pid != 0) { in JobInterrupt()
2449 DEBUG2(JOB, in JobInterrupt()
2451 signo, job->pid); in JobInterrupt()
2452 KILLPG(job->pid, signo); in JobInterrupt()
2456 for (job = job_table; job < job_table_end; job++) { in JobInterrupt()
2457 if (job->status == JOB_ST_RUNNING && job->pid != 0) { in JobInterrupt()
2459 (void)waitpid(job->pid, &status, 0); in JobInterrupt()
2460 JobDeleteTarget(job->node); in JobInterrupt()
2477 /* Make the .END target, returning the number of job-related errors. */
2518 Job *job; in Job_AbortAll() local
2524 for (job = job_table; job < job_table_end; job++) { in Job_AbortAll()
2525 if (job->status != JOB_ST_RUNNING) in Job_AbortAll()
2527 KILLPG(job->pid, SIGINT); in Job_AbortAll()
2528 KILLPG(job->pid, SIGKILL); in Job_AbortAll()
2537 watchfd(Job *job) in watchfd() argument
2539 if (job->inPollfd != NULL) in watchfd()
2540 Punt("Watching watched job"); in watchfd()
2542 fds[fdsLen].fd = job->inPipe; in watchfd()
2544 jobByFdIndex[fdsLen] = job; in watchfd()
2545 job->inPollfd = &fds[fdsLen]; in watchfd()
2549 fds[fdsLen].fd = meta_job_fd(job); in watchfd()
2551 jobByFdIndex[fdsLen] = job; in watchfd()
2558 clearfd(Job *job) in clearfd() argument
2561 if (job->inPollfd == NULL) in clearfd()
2562 Punt("Unwatching unwatched job"); in clearfd()
2563 i = (size_t)(job->inPollfd - fds); in clearfd()
2573 /* Move last job in table into hole made by dead job. */ in clearfd()
2585 job->inPollfd = NULL; in clearfd()
2612 * Put a token (back) into the job token pool.
2613 * This allows a make process to start a build job.
2634 DEBUG3(JOB, "TokenPool_Add: pid %d, aborting %s, token %c\n", in TokenPool_Add()
2648 /* Prepare the job token pipe in the root make process. */
2664 * Preload the job pipe with one token per job, save the one in TokenPool_InitServer()
2665 * "extra" token for the primary job. in TokenPool_InitServer()
2708 DEBUG3(JOB, "TokenPool_Take: pid %d, aborting %s, running %d\n", in TokenPool_Take()
2716 Fatal("eof on job pipe"); in TokenPool_Take()
2719 Fatal("job pipe read: %s", strerror(errno)); in TokenPool_Take()
2720 DEBUG1(JOB, "TokenPool_Take: pid %d blocked for token\n", in TokenPool_Take()
2727 /* make being aborted - remove any other job tokens */ in TokenPool_Take()
2728 DEBUG2(JOB, "TokenPool_Take: pid %d aborted by token %c\n", in TokenPool_Take()
2747 DEBUG1(JOB, "TokenPool_Take: pid %d took a token\n", getpid()); in TokenPool_Take()