version 1.123, 2012/08/25 08:12:56 |
version 1.124, 2012/09/21 07:55:20 |
|
|
/* $NetBSD: job.c,v 1.16 1996/11/06 17:59:08 christos Exp $ */ |
/* $NetBSD: job.c,v 1.16 1996/11/06 17:59:08 christos Exp $ */ |
|
|
/* |
/* |
|
* Copyright (c) 2012 Marc Espie. |
|
* |
|
* Extensive code modifications for the OpenBSD project. |
|
* |
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions |
|
* are met: |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice, this list of conditions and the following disclaimer in the |
|
* documentation and/or other materials provided with the distribution. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS |
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD |
|
* PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
*/ |
|
/* |
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. |
* Copyright (c) 1988, 1989, 1990 The Regents of the University of California. |
* Copyright (c) 1988, 1989 by Adam de Boor |
* Copyright (c) 1988, 1989 by Adam de Boor |
* Copyright (c) 1989 by Berkeley Softworks |
* Copyright (c) 1989 by Berkeley Softworks |
|
|
* Interface: |
* Interface: |
* Job_Make Start the creation of the given target. |
* Job_Make Start the creation of the given target. |
* |
* |
* Job_Init Called to initialize this module. in addition, |
* Job_Init Called to initialize this module. |
* any commands attached to the .BEGIN target |
|
* are executed before this function returns. |
|
* Hence, the makefile must have been parsed |
|
* before this function is called. |
|
* |
* |
* Job_End Cleanup any memory used. |
* Job_Begin execute commands attached to the .BEGIN target |
|
* if any. |
* |
* |
|
* Job_End Should cleanup any memory used. |
|
* |
* can_start_job Return true if we can start job |
* can_start_job Return true if we can start job |
* |
* |
* Job_Empty Return true if the job table is completely |
* Job_Empty Return true if the job table is completely |
|
|
* Job_Finish Perform any final processing which needs doing. |
* Job_Finish Perform any final processing which needs doing. |
* This includes the execution of any commands |
* This includes the execution of any commands |
* which have been/were attached to the .END |
* which have been/were attached to the .END |
* target. It should only be called when the |
* target. |
* job table is empty. |
|
* |
* |
* Job_AbortAll Abort all current jobs. It doesn't |
* Job_AbortAll Abort all current jobs. It doesn't |
* handle output or do anything for the jobs, |
* handle output or do anything for the jobs, |
* just kills them. It should only be called in |
* just kills them. |
* an emergency, as it were. |
|
* |
* |
* Job_Wait Wait for all running jobs to finish. |
* Job_Wait Wait for all running jobs to finish. |
*/ |
*/ |
|
|
#include "var.h" |
#include "var.h" |
#include "targ.h" |
#include "targ.h" |
#include "error.h" |
#include "error.h" |
#include "lst.h" |
|
#include "extern.h" |
#include "extern.h" |
|
#include "lst.h" |
#include "gnode.h" |
#include "gnode.h" |
#include "memory.h" |
#include "memory.h" |
#include "make.h" |
#include "make.h" |
|
#include "buf.h" |
|
|
/* |
|
* The SEL_ constants determine the maximum amount of time spent in select |
|
* before coming out to see if a child has finished. SEL_SEC is the number of |
|
* seconds and SEL_USEC is the number of micro-seconds |
|
*/ |
|
#define SEL_SEC 0 |
|
#define SEL_USEC 500000 |
|
|
|
|
|
/*- |
|
* Job Table definitions. |
|
* |
|
* Each job has several things associated with it: |
|
* 1) The process id of the child shell |
|
* 2) The graph node describing the target being made by this job |
|
* 3) An FILE* for writing out the commands. This is only |
|
* used before the job is actually started. |
|
* 4) Things used for handling the shell's output. |
|
* the output is being caught via a pipe and |
|
* the descriptors of our pipe, an array in which output is line |
|
* buffered and the current position in that buffer are all |
|
* maintained for each job. |
|
* 5) A word of flags which determine how the module handles errors, |
|
* echoing, etc. for the job |
|
* |
|
* The job "table" is kept as a linked Lst in 'jobs', with the number of |
|
* active jobs maintained in the 'nJobs' variable. At no time will this |
|
* exceed the value of 'maxJobs', initialized by the Job_Init function. |
|
* |
|
* When a job is finished, the Make_Update function is called on each of the |
|
* parents of the node which was just remade. This takes care of the upward |
|
* traversal of the dependency graph. |
|
*/ |
|
#define JOB_BUFSIZE 1024 |
|
struct job_pipe { |
|
int fd; |
|
char buffer[JOB_BUFSIZE]; |
|
size_t pos; |
|
}; |
|
|
|
typedef struct Job_ { |
|
pid_t pid; /* The child's process ID */ |
|
GNode *node; /* The target the child is making */ |
|
short flags; /* Flags to control treatment of job */ |
|
LstNode p; |
|
#define JOB_DIDOUTPUT 0x001 |
|
#define JOB_IS_SPECIAL 0x004 /* Target is a special one. */ |
|
#define JOB_IS_EXPENSIVE 0x002 |
|
struct job_pipe in[2]; |
|
} Job; |
|
|
|
struct job_pid { |
|
pid_t pid; |
|
}; |
|
|
|
static int aborting = 0; /* why is the make aborting? */ |
static int aborting = 0; /* why is the make aborting? */ |
#define ABORT_ERROR 1 /* Because of an error */ |
#define ABORT_ERROR 1 /* Because of an error */ |
#define ABORT_INTERRUPT 2 /* Because it was interrupted */ |
#define ABORT_INTERRUPT 2 /* Because it was interrupted */ |
#define ABORT_WAIT 3 /* Waiting for jobs to finish */ |
#define ABORT_WAIT 3 /* Waiting for jobs to finish */ |
|
|
static int maxJobs; /* The most children we can run at once */ |
static int maxJobs; /* The most children we can run at once */ |
static int nJobs; /* The number of current children */ |
static int nJobs; /* Number of jobs already allocated */ |
static bool expensive_job; |
static bool no_new_jobs; /* Mark recursive shit so we shouldn't start |
static LIST runningJobs; /* The structures that describe them */ |
* something else at the same time |
static GNode *lastNode; /* The node for which output was most recently |
*/ |
* produced. */ |
Job *runningJobs; /* Jobs currently running a process */ |
static LIST job_pids; /* a simple list that doesn't move that much */ |
Job *errorJobs; /* Jobs in error at end */ |
|
static Job *heldJobs; /* Jobs not running yet because of expensive */ |
|
static pid_t mypid; |
|
|
/* data structure linked to job handling through select */ |
static volatile sig_atomic_t got_fatal; |
static fd_set *output_mask = NULL; /* File descriptors to look for */ |
|
|
|
static fd_set *actual_mask = NULL; /* actual select argument */ |
static volatile sig_atomic_t got_SIGINT, got_SIGHUP, got_SIGQUIT, got_SIGTERM, |
static int largest_fd = -1; |
got_SIGINFO; |
static size_t mask_size = 0; |
|
|
|
/* wait possibilities */ |
static sigset_t sigset, emptyset; |
#define JOB_EXITED 0 |
|
#define JOB_SIGNALED 1 |
|
#define JOB_UNKNOWN 4 |
|
|
|
static LIST errorsList; |
|
static int errors; |
|
struct error_info { |
|
int reason; |
|
int code; |
|
GNode *n; |
|
}; |
|
|
|
/* for blocking/unblocking */ |
|
static sigset_t oset, set; |
|
static void block_signals(void); |
|
static void unblock_signals(void); |
|
|
|
static void handle_all_signals(void); |
|
static void handle_signal(int); |
static void handle_signal(int); |
static int JobCmpPid(void *, void *); |
static void handle_siginfo(void); |
static void process_job_status(Job *, int); |
static void postprocess_job(Job *, bool); |
static void JobExec(Job *); |
static Job *prepare_job(GNode *); |
static void JobStart(GNode *, int); |
static void determine_job_next_step(Job *); |
static void JobInterrupt(bool, int); |
static void remove_job(Job *, bool); |
static void debug_printf(const char *, ...); |
static void may_continue_job(Job *); |
static Job *prepare_job(GNode *, int); |
static void continue_job(Job *); |
static void banner(Job *, FILE *); |
static Job *reap_finished_job(pid_t); |
static bool Job_Full(void); |
static bool reap_jobs(void); |
|
|
/*** |
|
*** Input/output from jobs |
|
***/ |
|
|
|
/* prepare_pipe(jp, &fd): |
|
* set up pipe data structure (buffer and pos) corresponding to |
|
* pointed fd, and prepare to watch for it. |
|
*/ |
|
static void prepare_pipe(struct job_pipe *, int *); |
|
|
|
/* close_job_pipes(j): |
|
* handle final output from job, and close pipes properly |
|
*/ |
|
static void close_job_pipes(Job *); |
|
|
|
|
|
static void handle_all_jobs_output(void); |
|
|
|
/* handle_job_output(job, n, finish): |
|
* n = 0 or 1 (stdout/stderr), set finish to retrieve everything. |
|
*/ |
|
static void handle_job_output(Job *, int, bool); |
|
|
|
static void print_partial_buffer(struct job_pipe *, Job *, FILE *, size_t); |
|
static void print_partial_buffer_and_shift(struct job_pipe *, Job *, FILE *, |
|
size_t); |
|
static bool print_complete_lines(struct job_pipe *, Job *, FILE *, size_t); |
|
|
|
|
|
static void register_error(int, int, Job *); |
|
static void loop_handle_running_jobs(void); |
static void loop_handle_running_jobs(void); |
static void Job_CatchChildren(void); |
static bool expensive_job(Job *); |
|
static bool expensive_command(const char *); |
|
static void setup_signal(int); |
|
static void notice_signal(int); |
|
static void setup_all_signals(void); |
|
static void really_kill(int, pid_t); |
|
|
static void |
void |
register_error(int reason, int code, Job *job) |
print_errors(void) |
{ |
{ |
struct error_info *p; |
Job *j; |
|
|
errors++; |
fprintf(stderr, "\nStop in %s%c\n", Var_Value(".CURDIR"), |
p = emalloc(sizeof(struct error_info)); |
errorJobs ? ':' : '.'); |
p->reason = reason; |
const char *previous = NULL; |
p->code = code; |
|
p->n = job->node; |
|
Lst_AtEnd(&errorsList, p); |
|
} |
|
|
|
void |
for (j = errorJobs; j != NULL; j = j->next) { |
print_errors() |
const char *type; |
{ |
|
LstNode ln; |
|
struct error_info *p; |
|
const char *type; |
|
|
|
for (ln = Lst_First(&errorsList); ln != NULL; ln = Lst_Adv(ln)) { |
if (j->exit_type == JOB_EXIT_BAD) |
p = (struct error_info *)Lst_Datum(ln); |
|
switch(p->reason) { |
|
case JOB_EXITED: |
|
type = "Exit status"; |
type = "Exit status"; |
break; |
else if (j->exit_type == JOB_SIGNALED) |
case JOB_SIGNALED: |
|
type = "Received signal"; |
type = "Received signal"; |
break; |
else |
default: |
|
type = "Should not happen"; |
type = "Should not happen"; |
break; |
fprintf(stderr, " %s %d (", type, j->code); |
} |
fprintf(stderr, "line %lu", |
if (p->n->origin.lineno) |
j->location->lineno); |
Error(" %s %d (%s, line %lu of %s)", |
if (j->location->fname == previous) |
type, p->code, p->n->name, p->n->origin.lineno, p->n->origin.fname); |
fputs(",", stderr); |
else |
else |
Error(" %s %d (%s)", type, p->code, p->n->name); |
fprintf(stderr, " of %s,", j->location->fname); |
|
previous = j->location->fname; |
|
if ((j->flags & (JOB_SILENT | JOB_IS_EXPENSIVE)) == JOB_SILENT) |
|
fprintf(stderr, "\n target %s: %s", j->node->name, j->cmd); |
|
else |
|
fprintf(stderr, " target %s", j->node->name); |
|
fputs(")\n", stderr); |
|
free(j->cmd); |
} |
} |
} |
} |
|
|
static void |
static void |
banner(Job *job, FILE *out) |
setup_signal(int sig) |
{ |
{ |
if (job->node != lastNode) { |
if (signal(sig, SIG_IGN) != SIG_IGN) { |
if (DEBUG(JOBBANNER)) |
(void)signal(sig, notice_signal); |
(void)fprintf(out, "--- %s ---\n", job->node->name); |
sigaddset(&sigset, sig); |
lastNode = job->node; |
|
} |
} |
} |
} |
|
|
volatile sig_atomic_t got_SIGTSTP, got_SIGTTOU, got_SIGTTIN, got_SIGWINCH, |
|
got_SIGCONT; |
|
static void |
static void |
handle_all_signals() |
notice_signal(int sig) |
{ |
{ |
while (got_signal) { |
switch(sig) { |
got_signal = 0; |
|
|
|
if (got_SIGINT) { |
|
got_SIGINT=0; |
|
handle_signal(SIGINT); |
|
} |
|
if (got_SIGHUP) { |
|
got_SIGHUP=0; |
|
handle_signal(SIGHUP); |
|
} |
|
if (got_SIGQUIT) { |
|
got_SIGQUIT=0; |
|
handle_signal(SIGQUIT); |
|
} |
|
if (got_SIGTERM) { |
|
got_SIGTERM=0; |
|
handle_signal(SIGTERM); |
|
} |
|
if (got_SIGTSTP) { |
|
got_SIGTSTP=0; |
|
signal(SIGTSTP, parallel_handler); |
|
} |
|
if (got_SIGTTOU) { |
|
got_SIGTTOU=0; |
|
signal(SIGTTOU, parallel_handler); |
|
} |
|
if (got_SIGTTIN) { |
|
got_SIGTTIN=0; |
|
signal(SIGTTIN, parallel_handler); |
|
} |
|
if (got_SIGWINCH) { |
|
got_SIGWINCH=0; |
|
signal(SIGWINCH, parallel_handler); |
|
} |
|
if (got_SIGCONT) { |
|
got_SIGCONT = 0; |
|
signal(SIGCONT, parallel_handler); |
|
} |
|
} |
|
} |
|
|
|
/* this is safe from interrupts, actually */ |
|
void |
|
parallel_handler(int signo) |
|
{ |
|
int save_errno = errno; |
|
LstNode ln; |
|
for (ln = Lst_First(&job_pids); ln != NULL; ln = Lst_Adv(ln)) { |
|
struct job_pid *p = Lst_Datum(ln); |
|
killpg(p->pid, signo); |
|
} |
|
errno = save_errno; |
|
|
|
switch(signo) { |
|
case SIGINT: |
case SIGINT: |
got_SIGINT++; |
got_SIGINT++; |
got_signal = 1; |
got_fatal = 1; |
return; |
break; |
case SIGHUP: |
case SIGHUP: |
got_SIGHUP++; |
got_SIGHUP++; |
got_signal = 1; |
got_fatal = 1; |
return; |
break; |
case SIGQUIT: |
case SIGQUIT: |
got_SIGQUIT++; |
got_SIGQUIT++; |
got_signal = 1; |
got_fatal = 1; |
return; |
break; |
case SIGTERM: |
case SIGTERM: |
got_SIGTERM++; |
got_SIGTERM++; |
got_signal = 1; |
got_fatal = 1; |
return; |
|
case SIGTSTP: |
|
got_SIGTSTP++; |
|
got_signal = 1; |
|
break; |
break; |
case SIGTTOU: |
case SIGINFO: |
got_SIGTTOU++; |
got_SIGINFO++; |
got_signal = 1; |
|
break; |
break; |
case SIGTTIN: |
case SIGCHLD: |
got_SIGTTIN++; |
|
got_signal = 1; |
|
break; |
break; |
case SIGWINCH: |
|
got_SIGWINCH++; |
|
got_signal = 1; |
|
break; |
|
case SIGCONT: |
|
got_SIGCONT++; |
|
got_signal = 1; |
|
break; |
|
} |
} |
(void)killpg(getpid(), signo); |
} |
|
|
(void)signal(signo, SIG_DFL); |
void |
errno = save_errno; |
setup_all_signals(void) |
|
{ |
|
sigemptyset(&sigset); |
|
sigemptyset(&emptyset); |
|
/* |
|
* Catch the four signals that POSIX specifies if they aren't ignored. |
|
* handle_signal will take care of calling JobInterrupt if appropriate. |
|
*/ |
|
setup_signal(SIGINT); |
|
setup_signal(SIGHUP); |
|
setup_signal(SIGQUIT); |
|
setup_signal(SIGTERM); |
|
/* Display running jobs on SIGINFO */ |
|
setup_signal(SIGINFO); |
|
/* Have to see SIGCHLD */ |
|
setup_signal(SIGCHLD); |
|
got_fatal = 0; |
} |
} |
|
|
/*- |
static void |
*----------------------------------------------------------------------- |
handle_siginfo(void) |
* handle_signal -- |
|
* handle a signal for ourselves |
|
* |
|
*----------------------------------------------------------------------- |
|
*/ |
|
static void |
|
handle_signal(int signo) |
|
{ |
{ |
if (DEBUG(JOB)) { |
Job *job; |
(void)fprintf(stdout, "handle_signal(%d) called.\n", signo); |
BUFFER buf; |
(void)fflush(stdout); |
bool first = true; |
} |
|
|
|
/* |
got_SIGINFO = 0; |
* Deal with proper cleanup based on the signal received. We only run |
/* we have to store the info in a buffer, because status from all |
* the .INTERRUPT target if the signal was in fact an interrupt. The |
* makes running would get intermixed otherwise |
* other three termination signals are more of a "get out *now*" |
|
* command. |
|
*/ |
*/ |
if (signo == SIGINT) |
Buf_Init(&buf, 0); |
JobInterrupt(true, signo); |
|
else if (signo == SIGHUP || signo == SIGTERM || signo == SIGQUIT) |
|
JobInterrupt(false, signo); |
|
|
|
if (signo == SIGQUIT) |
Buf_printf(&buf, "%s in %s: ", Var_Value("MAKE"), Var_Value(".CURDIR")); |
Finish(0); |
|
|
for (job = runningJobs; job != NULL ; job = job->next) { |
|
if (!first) |
|
Buf_puts(&buf, ", "); |
|
first = false; |
|
Buf_puts(&buf, job->node->name); |
|
} |
|
Buf_puts(&buf, first ? "nothing running\n" : "\n"); |
|
|
|
fputs(Buf_Retrieve(&buf), stderr); |
|
Buf_Destroy(&buf); |
} |
} |
|
|
/*- |
void |
*----------------------------------------------------------------------- |
handle_all_signals(void) |
* JobCmpPid -- |
|
* Compare the pid of the job with the given pid and return 0 if they |
|
* are equal. This function is called from Job_CatchChildren via |
|
* Lst_Find to find the job descriptor of the finished job. |
|
* |
|
* Results: |
|
* 0 if the pid's match |
|
*----------------------------------------------------------------------- |
|
*/ |
|
static int |
|
JobCmpPid(void *job, /* job to examine */ |
|
void *pid) /* process id desired */ |
|
{ |
{ |
return *(pid_t *)pid - ((Job *)job)->pid; |
if (got_SIGINFO) |
|
handle_siginfo(); |
|
while (got_fatal) { |
|
got_fatal = 0; |
|
aborting = ABORT_INTERRUPT; |
|
|
|
if (got_SIGINT) { |
|
got_SIGINT=0; |
|
handle_signal(SIGINT); |
|
} |
|
if (got_SIGHUP) { |
|
got_SIGHUP=0; |
|
handle_signal(SIGHUP); |
|
} |
|
if (got_SIGQUIT) { |
|
got_SIGQUIT=0; |
|
handle_signal(SIGQUIT); |
|
} |
|
if (got_SIGTERM) { |
|
got_SIGTERM=0; |
|
handle_signal(SIGTERM); |
|
} |
|
} |
} |
} |
|
|
static void |
void |
debug_printf(const char *fmt, ...) |
debug_job_printf(const char *fmt, ...) |
{ |
{ |
if (DEBUG(JOB)) { |
if (DEBUG(JOB)) { |
va_list va; |
va_list va; |
|
(void)printf("[%ld] ", (long)mypid); |
va_start(va, fmt); |
va_start(va, fmt); |
(void)vfprintf(stdout, fmt, va); |
(void)vprintf(fmt, va); |
fflush(stdout); |
fflush(stdout); |
va_end(va); |
va_end(va); |
} |
} |
} |
} |
|
|
static void |
|
close_job_pipes(Job *job) |
|
{ |
|
int i; |
|
|
|
for (i = 1; i >= 0; i--) { |
|
FD_CLR(job->in[i].fd, output_mask); |
|
handle_job_output(job, i, true); |
|
(void)close(job->in[i].fd); |
|
} |
|
} |
|
|
|
/*- |
/*- |
*----------------------------------------------------------------------- |
*----------------------------------------------------------------------- |
* process_job_status -- |
* postprocess_job -- |
* Do processing for the given job including updating |
* Do final processing for the given job including updating |
* parents and starting new jobs as available/necessary. |
* parents and starting new jobs as available/necessary. |
* |
* |
* Side Effects: |
* Side Effects: |
* Some nodes may be put on the toBeMade queue. |
|
* Final commands for the job are placed on end_node. |
|
* |
|
* If we got an error and are aborting (aborting == ABORT_ERROR) and |
* If we got an error and are aborting (aborting == ABORT_ERROR) and |
* the job list is now empty, we are done for the day. |
* the job list is now empty, we are done for the day. |
* If we recognized an error we set the aborting flag |
* If we recognized an error we set the aborting flag |
|
|
/*ARGSUSED*/ |
/*ARGSUSED*/ |
|
|
static void |
static void |
process_job_status(Job *job, int status) |
postprocess_job(Job *job, bool okay) |
{ |
{ |
int reason, code; |
if (okay && |
bool done; |
|
|
|
debug_printf("Process %ld (%s) exited with status %d.\n", |
|
(long)job->pid, job->node->name, status); |
|
/* parse status */ |
|
if (WIFEXITED(status)) { |
|
reason = JOB_EXITED; |
|
code = WEXITSTATUS(status); |
|
} else if (WIFSIGNALED(status)) { |
|
reason = JOB_SIGNALED; |
|
code = WTERMSIG(status); |
|
} else { |
|
/* can't happen, set things to be bad. */ |
|
reason = UNKNOWN; |
|
code = status; |
|
} |
|
|
|
if ((reason == JOB_EXITED && |
|
code != 0 && !(job->node->type & OP_IGNORE)) || |
|
reason == JOB_SIGNALED) { |
|
/* |
|
* 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... |
|
*/ |
|
close_job_pipes(job); |
|
done = true; |
|
} else if (reason == JOB_EXITED) { |
|
/* |
|
* Deal with ignored errors. We need to print a message telling |
|
* of the ignored error as well as setting status.w_status to 0 |
|
* so the next command gets run. To do this, we set done to be |
|
* true and the job exited non-zero. |
|
*/ |
|
done = code != 0; |
|
close_job_pipes(job); |
|
} else { |
|
/* |
|
* No need to close things down or anything. |
|
*/ |
|
done = false; |
|
} |
|
|
|
if (done || DEBUG(JOB)) { |
|
if (reason == JOB_EXITED) { |
|
debug_printf("Process %ld (%s) exited.\n", |
|
(long)job->pid, job->node->name); |
|
if (code != 0) { |
|
banner(job, stdout); |
|
(void)fprintf(stdout, "*** Error code %d %s\n", |
|
code, |
|
(job->node->type & OP_IGNORE) ? |
|
"(ignored)" : ""); |
|
|
|
if (job->node->type & OP_IGNORE) { |
|
reason = JOB_EXITED; |
|
code = 0; |
|
} |
|
} else if (DEBUG(JOB)) { |
|
(void)fprintf(stdout, |
|
"*** %ld (%s) Completed successfully\n", |
|
(long)job->pid, job->node->name); |
|
} |
|
} else { |
|
banner(job, stdout); |
|
(void)fprintf(stdout, "*** Signal %d\n", code); |
|
} |
|
|
|
(void)fflush(stdout); |
|
} |
|
|
|
done = true; |
|
|
|
if (done && |
|
aborting != ABORT_ERROR && |
aborting != ABORT_ERROR && |
aborting != ABORT_INTERRUPT && |
aborting != ABORT_INTERRUPT) { |
reason == JOB_EXITED && code == 0) { |
|
/* As long as we aren't aborting and the job didn't return a |
/* As long as we aren't aborting and the job didn't return a |
* non-zero status that we shouldn't ignore, we call |
* non-zero status that we shouldn't ignore, we call |
* Make_Update to update the parents. */ |
* Make_Update to update the parents. */ |
job->node->built_status = MADE; |
job->node->built_status = MADE; |
Make_Update(job->node); |
Make_Update(job->node); |
} else if (!(reason == JOB_EXITED && code == 0)) { |
free(job); |
register_error(reason, code, job); |
|
} |
} |
free(job); |
|
|
|
if (errors && !keepgoing && |
if (errorJobs != NULL && !keepgoing && |
aborting != ABORT_INTERRUPT) |
aborting != ABORT_INTERRUPT) |
aborting = ABORT_ERROR; |
aborting = ABORT_ERROR; |
|
|
|
if (aborting == ABORT_ERROR && DEBUG(QUICKDEATH)) |
|
handle_signal(SIGINT); |
if (aborting == ABORT_ERROR && Job_Empty()) |
if (aborting == ABORT_ERROR && Job_Empty()) |
Finish(errors); |
Finish(); |
} |
} |
|
|
static void |
/* expensive jobs handling: in order to avoid forking an exponential number |
prepare_pipe(struct job_pipe *p, int *fd) |
* of jobs, make tries to figure out "recursive make" configurations. |
{ |
* It may err on the side of caution. |
p->pos = 0; |
* Basically, a command is "expensive" if it's likely to fork an extra |
(void)fcntl(fd[0], F_SETFD, FD_CLOEXEC); |
* level of make: either by looking at the command proper, or if it has |
p->fd = fd[0]; |
* some specific qualities ('+cmd' are likely to be recursive, as are |
close(fd[1]); |
* .MAKE: commands). It's possible to explicitly say some targets are |
|
* expensive or cheap with .EXPENSIVE or .CHEAP. |
if (output_mask == NULL || p->fd > largest_fd) { |
|
int fdn, ofdn; |
|
|
|
fdn = howmany(p->fd+1, NFDBITS); |
|
ofdn = howmany(largest_fd+1, NFDBITS); |
|
|
|
if (fdn != ofdn) { |
|
output_mask = emult_realloc(output_mask, fdn, |
|
sizeof(fd_mask)); |
|
memset(((char *)output_mask) + ofdn * sizeof(fd_mask), |
|
0, (fdn-ofdn) * sizeof(fd_mask)); |
|
actual_mask = emult_realloc(actual_mask, fdn, |
|
sizeof(fd_mask)); |
|
mask_size = fdn * sizeof(fd_mask); |
|
} |
|
largest_fd = p->fd; |
|
} |
|
fcntl(p->fd, F_SETFL, O_NONBLOCK); |
|
FD_SET(p->fd, output_mask); |
|
} |
|
|
|
/*- |
|
*----------------------------------------------------------------------- |
|
* JobExec -- |
|
* Execute the shell for the given job. Called from JobStart |
|
* |
* |
* Side Effects: |
* While an expensive command is running, no_new_jobs |
* A shell is executed, outputs is altered and the Job structure added |
* is set, so jobs that would fork new processes are accumulated in the |
* to the job table. |
* heldJobs list instead. |
*----------------------------------------------------------------------- |
* |
|
* This heuristics is also used on error exit: we display silent commands |
|
* that failed, unless those ARE expensive commands: expensive commands |
|
* are likely to not be failing by themselves, but to be the result of |
|
* a cascade of failures in descendant makes. |
*/ |
*/ |
static void |
void |
JobExec(Job *job) |
determine_expensive_job(Job *job) |
{ |
{ |
pid_t cpid; /* ID of new child */ |
if (expensive_job(job)) { |
struct job_pid *p; |
job->flags |= JOB_IS_EXPENSIVE; |
int fds[4]; |
no_new_jobs = true; |
int *fdout = fds; |
} else |
int *fderr = fds+2; |
job->flags &= ~JOB_IS_EXPENSIVE; |
int i; |
if (DEBUG(EXPENSIVE)) |
|
fprintf(stderr, "[%ld] Target %s running %.50s: %s\n", |
|
(long)mypid, job->node->name, job->cmd, |
|
job->flags & JOB_IS_EXPENSIVE ? "expensive" : "cheap"); |
|
} |
|
|
banner(job, stdout); |
static bool |
|
expensive_job(Job *job) |
setup_engine(1); |
{ |
|
if (job->node->type & OP_CHEAP) |
/* Create the pipe by which we'll get the shell's output. |
return false; |
*/ |
if (job->node->type & (OP_EXPENSIVE | OP_MAKE)) |
if (pipe(fdout) == -1) |
return true; |
Punt("Cannot create pipe: %s", strerror(errno)); |
return expensive_command(job->cmd); |
|
|
if (pipe(fderr) == -1) |
|
Punt("Cannot create pipe: %s", strerror(errno)); |
|
|
|
block_signals(); |
|
if ((cpid = fork()) == -1) { |
|
Punt("Cannot fork"); |
|
unblock_signals(); |
|
} else if (cpid == 0) { |
|
supervise_jobs = false; |
|
/* standard pipe code to route stdout and stderr */ |
|
close(fdout[0]); |
|
if (dup2(fdout[1], 1) == -1) |
|
Punt("Cannot dup2(outPipe): %s", strerror(errno)); |
|
if (fdout[1] != 1) |
|
close(fdout[1]); |
|
close(fderr[0]); |
|
if (dup2(fderr[1], 2) == -1) |
|
Punt("Cannot dup2(errPipe): %s", strerror(errno)); |
|
if (fderr[1] != 2) |
|
close(fderr[1]); |
|
|
|
/* |
|
* We want to switch the child into a different process family |
|
* so we can kill it and all its descendants in one fell swoop, |
|
* by killing its process family, but not commit suicide. |
|
*/ |
|
(void)setpgid(0, getpid()); |
|
|
|
if (random_delay) |
|
if (!(nJobs == 1 && no_jobs_left())) |
|
usleep(random() % random_delay); |
|
|
|
setup_all_signals(SigHandler, SIG_DFL); |
|
unblock_signals(); |
|
/* this exits directly */ |
|
run_gnode_parallel(job->node); |
|
/*NOTREACHED*/ |
|
} else { |
|
supervise_jobs = true; |
|
job->pid = cpid; |
|
|
|
/* we set the current position in the buffers to the beginning |
|
* and mark another stream to watch in the outputs mask |
|
*/ |
|
for (i = 0; i < 2; i++) |
|
prepare_pipe(&job->in[i], fds+2*i); |
|
} |
|
/* |
|
* Now the job is actually running, add it to the table. |
|
*/ |
|
nJobs++; |
|
Lst_AtEnd(&runningJobs, job); |
|
if (job->flags & JOB_IS_EXPENSIVE) |
|
expensive_job = true; |
|
p = emalloc(sizeof(struct job_pid)); |
|
p->pid = cpid; |
|
Lst_AtEnd(&job_pids, p); |
|
job->p = Lst_Last(&job_pids); |
|
|
|
unblock_signals(); |
|
if (DEBUG(JOB)) { |
|
LstNode ln; |
|
|
|
(void)fprintf(stdout, "Running %ld (%s)\n", (long)cpid, |
|
job->node->name); |
|
for (ln = Lst_First(&job->node->commands); ln != NULL ; |
|
ln = Lst_Adv(ln)) |
|
fprintf(stdout, "\t%s\n", (char *)Lst_Datum(ln)); |
|
(void)fflush(stdout); |
|
} |
|
|
|
} |
} |
|
|
static bool |
static bool |
|
|
/* okay, comments are cheap, always */ |
/* okay, comments are cheap, always */ |
if (*s == '#') |
if (*s == '#') |
return false; |
return false; |
|
/* and commands we always execute are expensive */ |
|
if (*s == '+') |
|
return true; |
|
|
for (p = s; *p != '\0'; p++) { |
for (p = s; *p != '\0'; p++) { |
if (*p == ' ' || *p == '\t') { |
if (*p == ' ' || *p == '\t') { |
|
|
p++; |
p++; |
expensive = true; |
expensive = true; |
while (p[1] != '\0' && p[1] != ' ' && p[1] != '\t') { |
while (p[1] != '\0' && p[1] != ' ' && p[1] != '\t') { |
if (p[1] == '.') { |
if (p[1] == '.' || p[1] == '/') { |
expensive = false; |
expensive = false; |
break; |
break; |
} |
} |
|
|
return false; |
return false; |
} |
} |
|
|
static bool |
|
expensive_commands(Lst l) |
|
{ |
|
LstNode ln; |
|
for (ln = Lst_First(l); ln != NULL; ln = Lst_Adv(ln)) |
|
if (expensive_command(Lst_Datum(ln))) |
|
return true; |
|
return false; |
|
} |
|
|
|
static Job * |
static Job * |
prepare_job(GNode *gn, int flags) |
prepare_job(GNode *gn) |
{ |
{ |
bool cmdsOK; /* true if the nodes commands were all right */ |
/* a new job is prepared unless its commands are bogus (we don't |
bool noExec; /* Set true if we decide not to run the job */ |
* have anything for it), or if we're in touch mode. |
|
* |
/* |
* Note that even in noexec mode, some commands may still run |
* Check the commands now so any attributes from .DEFAULT have a chance |
* thanks to the +cmd construct. |
* to migrate to the node |
|
*/ |
*/ |
cmdsOK = Job_CheckCommands(gn); |
if (node_find_valid_commands(gn)) { |
expand_commands(gn); |
if (touchFlag) { |
if (fatal_errors) |
Job_Touch(gn); |
Punt("can't continue"); |
return NULL; |
|
} else { |
|
Job *job; |
|
|
if ((gn->type & OP_MAKE) || (!noExecute && !touchFlag)) { |
job = emalloc(sizeof(Job)); |
/* |
if (job == NULL) |
* We're serious here, but if the commands were bogus, we're |
Punt("can't create job: out of memory"); |
* also dead... |
|
*/ |
|
if (!cmdsOK) |
|
job_failure(gn, Punt); |
|
|
|
if (Lst_IsEmpty(&gn->commands)) |
job_attach_node(job, gn); |
noExec = true; |
return job; |
else |
} |
noExec = false; |
|
|
|
} else if (noExecute) { |
|
if (!cmdsOK || Lst_IsEmpty(&gn->commands)) |
|
noExec = true; |
|
else |
|
noExec = false; |
|
} else { |
} else { |
/* |
node_failure(gn); |
* Just touch the target and note that no shell should be |
return NULL; |
* executed. Check |
|
* the commands, too, but don't die if they're no good -- it |
|
* does no harm to keep working up the graph. |
|
*/ |
|
Job_Touch(gn); |
|
noExec = true; |
|
} |
} |
|
} |
|
|
/* |
static void |
* If we're not supposed to execute a shell, don't. |
may_continue_job(Job *job) |
*/ |
{ |
if (noExec) { |
if (no_new_jobs) { |
/* |
if (DEBUG(EXPENSIVE)) |
* We only want to work our way up the graph if we aren't here |
fprintf(stderr, "[%ld] expensive -> hold %s\n", |
* because the commands for the job were no good. |
(long)mypid, job->node->name); |
*/ |
job->next = heldJobs; |
if (cmdsOK && !aborting) { |
heldJobs = job; |
gn->built_status = MADE; |
} else |
Make_Update(gn); |
continue_job(job); |
} |
} |
return NULL; |
|
} else { |
|
Job *job; /* new job descriptor */ |
|
job = emalloc(sizeof(Job)); |
|
if (job == NULL) |
|
Punt("JobStart out of memory"); |
|
|
|
job->node = gn; |
static void |
|
continue_job(Job *job) |
/* |
{ |
* Set the initial value of the flags for this job based on the |
bool finished = job_run_next(job); |
* global ones and the node's attributes... Any flags supplied |
if (finished) |
* by the caller are also added to the field. |
remove_job(job, true); |
*/ |
else |
job->flags = flags; |
determine_expensive_job(job); |
|
|
if (gn->type & OP_CHEAP) |
|
return job; |
|
if ((gn->type & OP_EXPENSIVE) || |
|
expensive_commands(&gn->expanded)) |
|
job->flags |= JOB_IS_EXPENSIVE; |
|
|
|
return job; |
|
} |
|
} |
} |
|
|
/*- |
/*- |
*----------------------------------------------------------------------- |
*----------------------------------------------------------------------- |
* JobStart -- |
* Job_Make -- |
* Start a target-creation process going for the target described |
* Start a target-creation process going for the target described |
* by the graph node gn. |
* by the graph node gn. |
* |
* |
* Side Effects: |
* Side Effects: |
* A new Job node is created and added to the list of running |
* A new Job node is created and its commands continued, which |
* jobs. Make is forked and a child shell created. |
* may fork the first command of that job. |
*----------------------------------------------------------------------- |
*----------------------------------------------------------------------- |
*/ |
*/ |
static void |
void |
JobStart(GNode *gn, /* target to create */ |
Job_Make(GNode *gn) |
int flags) /* flags for the job to override normal ones. |
|
* e.g. JOB_IS_SPECIAL */ |
|
{ |
{ |
Job *job; |
Job *job; |
job = prepare_job(gn, flags); |
bool finished; |
|
|
|
job = prepare_job(gn); |
if (!job) |
if (!job) |
return; |
return; |
JobExec(job); |
nJobs++; |
|
may_continue_job(job); |
} |
} |
|
|
/* Helper functions for JobDoOutput */ |
|
|
|
|
|
/* output debugging banner and print characters from 0 to endpos */ |
|
static void |
static void |
print_partial_buffer(struct job_pipe *p, Job *job, FILE *out, size_t endPos) |
determine_job_next_step(Job *job) |
{ |
{ |
size_t i; |
bool okay; |
|
if (job->flags & JOB_IS_EXPENSIVE) { |
|
no_new_jobs = false; |
|
if (DEBUG(EXPENSIVE)) |
|
fprintf(stderr, "[%ld] " |
|
"Returning from expensive target %s, " |
|
"allowing new jobs\n", (long)mypid, |
|
job->node->name); |
|
} |
|
|
banner(job, out); |
okay = job->exit_type == JOB_EXIT_OKAY; |
job->flags |= JOB_DIDOUTPUT; |
if (!okay || job->next_cmd == NULL) |
for (i = 0; i < endPos; i++) |
remove_job(job, okay); |
putc(p->buffer[i], out); |
else |
|
may_continue_job(job); |
} |
} |
|
|
/* print partial buffer and shift remaining contents */ |
|
static void |
static void |
print_partial_buffer_and_shift(struct job_pipe *p, Job *job, FILE *out, |
remove_job(Job *job, bool okay) |
size_t endPos) |
|
{ |
{ |
size_t i; |
nJobs--; |
|
postprocess_job(job, okay); |
print_partial_buffer(p, job, out, endPos); |
while (!no_new_jobs) { |
|
if (heldJobs != NULL) { |
for (i = endPos; i < p->pos; i++) |
job = heldJobs; |
p->buffer[i-endPos] = p->buffer[i]; |
heldJobs = heldJobs->next; |
p->pos -= endPos; |
if (DEBUG(EXPENSIVE)) |
} |
fprintf(stderr, "[%ld] cheap -> release %s\n", |
|
(long)mypid, job->node->name); |
/* print complete lines, looking back to the limit position |
continue_job(job); |
* (stuff before limit was already scanned). |
} else |
* returns true if something was printed. |
break; |
*/ |
|
static bool |
|
print_complete_lines(struct job_pipe *p, Job *job, FILE *out, size_t limit) |
|
{ |
|
size_t i; |
|
|
|
for (i = p->pos; i > limit; i--) { |
|
if (p->buffer[i-1] == '\n') { |
|
print_partial_buffer_and_shift(p, job, out, i); |
|
return true; |
|
} |
|
} |
} |
return false; |
|
} |
} |
|
|
/*- |
/* |
*----------------------------------------------------------------------- |
* job = reap_finished_job(pid): |
* handle_pipe -- |
* retrieve and remove a job from runningJobs, based on its pid |
* This functions is called whenever there is something to read on the |
|
* pipe. We collect more output from the given job and store it in the |
|
* job's outBuf. If this makes up lines, we print it tagged by the job's |
|
* identifier, as necessary. |
|
* |
* |
* Side Effects: |
* Note that we remove it right away, so that handle_signals() |
* curPos may be shifted as may the contents of outBuf. |
* is accurate. |
*----------------------------------------------------------------------- |
|
*/ |
*/ |
static void |
static Job * |
handle_pipe(struct job_pipe *p, |
reap_finished_job(pid_t pid) |
Job *job, FILE *out, bool finish) |
|
{ |
{ |
int nr; /* number of bytes read */ |
Job **j, *job; |
int oldpos; /* optimization */ |
|
|
|
/* want to get everything ? -> we block */ |
for (j = &runningJobs; *j != NULL; j = &((*j)->next)) |
if (finish) |
if ((*j)->pid == pid) { |
fcntl(p->fd, F_SETFL, 0); |
job = *j; |
|
*j = job->next; |
do { |
return job; |
nr = read(p->fd, &p->buffer[p->pos], |
|
JOB_BUFSIZE - p->pos); |
|
if (nr == -1) { |
|
if (errno == EAGAIN) |
|
break; |
|
if (DEBUG(JOB)) { |
|
perror("JobDoOutput(piperead)"); |
|
} |
|
} |
} |
oldpos = p->pos; |
|
p->pos += nr; |
|
if (!print_complete_lines(p, job, out, oldpos)) |
|
if (p->pos == JOB_BUFSIZE) { |
|
print_partial_buffer(p, job, out, p->pos); |
|
p->pos = 0; |
|
} |
|
} while (nr != 0); |
|
|
|
/* at end of file, we print whatever is left */ |
return NULL; |
if (nr == 0) { |
|
print_partial_buffer(p, job, out, p->pos); |
|
if (p->pos > 0 && p->buffer[p->pos - 1] != '\n') |
|
putchar('\n'); |
|
p->pos = 0; |
|
} |
|
} |
} |
|
|
static void |
/* |
handle_job_output(Job *job, int i, bool finish) |
* classic waitpid handler: retrieve as many dead children as possible. |
|
* returns true if succesful |
|
*/ |
|
static bool |
|
reap_jobs(void) |
{ |
{ |
handle_pipe(&job->in[i], job, i == 0 ? stdout : stderr, finish); |
pid_t pid; /* pid of dead child */ |
} |
int status; /* Exit/termination status */ |
|
bool reaped = false; |
static void |
|
remove_job(LstNode ln, int status) |
|
{ |
|
Job *job; |
Job *job; |
|
|
job = (Job *)Lst_Datum(ln); |
|
Lst_Remove(&runningJobs, ln); |
|
block_signals(); |
|
free(Lst_Datum(job->p)); |
|
Lst_Remove(&job_pids, job->p); |
|
unblock_signals(); |
|
nJobs--; |
|
if (job->flags & JOB_IS_EXPENSIVE) |
|
expensive_job = false; |
|
process_job_status(job, status); |
|
} |
|
|
|
/*- |
|
*----------------------------------------------------------------------- |
|
* Job_CatchChildren -- |
|
* Handle the exit of a child. Called by handle_running_jobs |
|
* |
|
* Side Effects: |
|
* 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 process_job_status to finish things off. |
|
*----------------------------------------------------------------------- |
|
*/ |
|
void |
|
Job_CatchChildren() |
|
{ |
|
pid_t pid; /* pid of dead child */ |
|
LstNode jnode; /* list element for finding job */ |
|
int status; /* Exit/termination status */ |
|
|
|
/* |
|
* Don't even bother if we know there's no one around. |
|
*/ |
|
if (nJobs == 0) |
|
return; |
|
|
|
while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0) { |
while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) > 0) { |
handle_all_signals(); |
reaped = true; |
|
job = reap_finished_job(pid); |
|
|
jnode = Lst_Find(&runningJobs, JobCmpPid, &pid); |
if (job == NULL) { |
|
Punt("Child (%ld) not in table?", (long)pid); |
if (jnode == NULL) { |
|
Error("Child (%ld) not in table?", (long)pid); |
|
} else { |
} else { |
remove_job(jnode, status); |
job_handle_status(job, status); |
|
determine_job_next_step(job); |
} |
} |
} |
} |
|
/* sanity check, should not happen */ |
|
if (pid == -1 && errno == ECHILD && runningJobs != NULL) |
|
Punt("Process has no children, but runningJobs is not empty ?"); |
|
return reaped; |
} |
} |
|
|
void |
void |
handle_all_jobs_output(void) |
handle_running_jobs(void) |
{ |
{ |
int nfds; |
sigset_t old; |
struct timeval timeout; |
|
LstNode ln, ln2; |
|
Job *job; |
|
int i; |
|
int status; |
|
|
|
/* no jobs */ |
/* reaping children in the presence of caught signals */ |
if (Lst_IsEmpty(&runningJobs)) |
|
return; |
|
|
|
(void)fflush(stdout); |
/* first, we make sure to hold on new signals, to synchronize |
|
* reception of new stuff on sigsuspend |
memcpy(actual_mask, output_mask, mask_size); |
*/ |
timeout.tv_sec = SEL_SEC; |
sigprocmask(SIG_BLOCK, &sigset, &old); |
timeout.tv_usec = SEL_USEC; |
while (runningJobs != NULL) { |
|
/* did we already have pending stuff that advances things ? |
nfds = select(largest_fd+1, actual_mask, NULL, NULL, &timeout); |
* then handle_all_signals() will not return |
handle_all_signals(); |
* or reap_jobs() will reap_jobs() |
for (ln = Lst_First(&runningJobs); nfds && ln != NULL; ln = ln2) { |
*/ |
ln2 = Lst_Adv(ln); |
handle_all_signals(); |
job = (Job *)Lst_Datum(ln); |
if (reap_jobs()) |
job->flags &= ~JOB_DIDOUTPUT; |
break; |
for (i = 1; i >= 0; i--) { |
/* okay, so it's safe to suspend, we have nothing to do but |
if (FD_ISSET(job->in[i].fd, actual_mask)) { |
* wait... |
nfds--; |
*/ |
handle_job_output(job, i, false); |
sigsuspend(&emptyset); |
} |
|
} |
|
if (job->flags & JOB_DIDOUTPUT) { |
|
if (waitpid(job->pid, &status, WNOHANG) == job->pid) { |
|
remove_job(ln, status); |
|
} else { |
|
Lst_Requeue(&runningJobs, ln); |
|
} |
|
} |
|
} |
} |
|
sigprocmask(SIG_SETMASK, &old, NULL); |
} |
} |
|
|
void |
void |
handle_running_jobs() |
handle_one_job(Job *job) |
{ |
{ |
handle_all_jobs_output(); |
int stat; |
Job_CatchChildren(); |
int status; |
|
sigset_t old; |
|
|
|
sigprocmask(SIG_BLOCK, &sigset, &old); |
|
while (1) { |
|
handle_all_signals(); |
|
stat = waitpid(job->pid, &status, WNOHANG); |
|
if (stat == job->pid) |
|
break; |
|
sigsuspend(&emptyset); |
|
} |
|
runningJobs = NULL; |
|
job_handle_status(job, status); |
|
sigprocmask(SIG_SETMASK, &old, NULL); |
} |
} |
|
|
static void |
static void |
loop_handle_running_jobs() |
loop_handle_running_jobs() |
{ |
{ |
while (nJobs) |
while (runningJobs != NULL) |
handle_running_jobs(); |
handle_running_jobs(); |
} |
} |
/*- |
|
*----------------------------------------------------------------------- |
|
* Job_Make -- |
|
* Start the creation of a target. Basically a front-end for |
|
* JobStart used by the Make module. |
|
* |
|
* Side Effects: |
|
* Another job is started. |
|
*----------------------------------------------------------------------- |
|
*/ |
|
void |
|
Job_Make(GNode *gn) |
|
{ |
|
(void)JobStart(gn, 0); |
|
} |
|
|
|
|
|
static void |
|
block_signals() |
|
{ |
|
sigprocmask(SIG_BLOCK, &set, &oset); |
|
} |
|
|
|
static void |
|
unblock_signals() |
|
{ |
|
sigprocmask(SIG_SETMASK, &oset, NULL); |
|
} |
|
|
|
/*- |
|
*----------------------------------------------------------------------- |
|
* Job_Init -- |
|
* Initialize the process module |
|
* |
|
* Side Effects: |
|
* lists and counters are initialized |
|
*----------------------------------------------------------------------- |
|
*/ |
|
void |
void |
Job_Init(int maxproc) |
Job_Init(int maxproc) |
{ |
{ |
Static_Lst_Init(&runningJobs); |
runningJobs = NULL; |
Static_Lst_Init(&errorsList); |
heldJobs = NULL; |
|
errorJobs = NULL; |
maxJobs = maxproc; |
maxJobs = maxproc; |
|
mypid = getpid(); |
|
|
nJobs = 0; |
nJobs = 0; |
errors = 0; |
|
sigemptyset(&set); |
|
sigaddset(&set, SIGINT); |
|
sigaddset(&set, SIGHUP); |
|
sigaddset(&set, SIGQUIT); |
|
sigaddset(&set, SIGTERM); |
|
sigaddset(&set, SIGTSTP); |
|
sigaddset(&set, SIGTTOU); |
|
sigaddset(&set, SIGTTIN); |
|
|
|
aborting = 0; |
aborting = 0; |
|
setup_all_signals(); |
lastNode = NULL; |
|
|
|
if ((begin_node->type & OP_DUMMY) == 0) { |
|
JobStart(begin_node, JOB_IS_SPECIAL); |
|
loop_handle_running_jobs(); |
|
} |
|
} |
} |
|
|
static bool |
|
Job_Full() |
|
{ |
|
return aborting || (nJobs >= maxJobs); |
|
} |
|
/*- |
|
*----------------------------------------------------------------------- |
|
* Job_Full -- |
|
* See if the job table is full. It is considered full |
|
* if we are in the process of aborting OR if we have |
|
* reached/exceeded our quota. |
|
* |
|
* Results: |
|
* true if the job table is full, false otherwise |
|
*----------------------------------------------------------------------- |
|
*/ |
|
bool |
bool |
can_start_job(void) |
can_start_job(void) |
{ |
{ |
if (Job_Full() || expensive_job) |
if (aborting || nJobs >= maxJobs) |
return false; |
return false; |
else |
else |
return true; |
return true; |
} |
} |
|
|
/*- |
|
*----------------------------------------------------------------------- |
|
* Job_Empty -- |
|
* See if the job table is empty. |
|
* |
|
* Results: |
|
* true if it is. false if it ain't. |
|
* ----------------------------------------------------------------------- |
|
*/ |
|
bool |
bool |
Job_Empty(void) |
Job_Empty(void) |
{ |
{ |
if (nJobs == 0) |
return runningJobs == NULL; |
return true; |
|
else |
|
return false; |
|
} |
} |
|
|
|
static void |
|
really_kill(pid_t pid, int sig) |
|
{ |
|
killpg(pid, sig); |
|
if (killpg(pid, sig) == - 1 && errno == ESRCH) |
|
kill(pid, sig); |
|
} |
/*- |
/*- |
*----------------------------------------------------------------------- |
*----------------------------------------------------------------------- |
* JobInterrupt -- |
* handle_signal -- |
* Handle the receipt of an interrupt. |
* Handle the receipt of an interrupt. |
* |
* |
* Side Effects: |
* Side Effects: |
* All children are killed. Another job will be started if the |
* All children are killed. Another job may be started if the |
* .INTERRUPT target was given. |
* .INTERRUPT target was given. |
*----------------------------------------------------------------------- |
*----------------------------------------------------------------------- |
*/ |
*/ |
static void |
static void |
JobInterrupt(bool runINTERRUPT, /* true if commands for the .INTERRUPT |
handle_signal(int signo) |
* target should be executed */ |
|
int signo) /* signal received */ |
|
{ |
{ |
LstNode ln; /* element in job table */ |
Job *job; |
Job *job; /* job descriptor in that element */ |
|
|
|
aborting = ABORT_INTERRUPT; |
debug_job_printf("handle_signal(%d) called.\n", signo); |
|
|
for (ln = Lst_First(&runningJobs); ln != NULL; ln = Lst_Adv(ln)) { |
|
job = (Job *)Lst_Datum(ln); |
|
|
|
|
for (job = runningJobs; job != NULL; job = job->next) { |
if (!Targ_Precious(job->node)) { |
if (!Targ_Precious(job->node)) { |
const char *file = job->node->path == NULL ? |
const char *file = Var(TARGET_INDEX, job->node); |
job->node->name : job->node->path; |
|
if (!noExecute && eunlink(file) != -1) { |
if (!noExecute && eunlink(file) != -1) |
Error("*** %s removed", file); |
Error("*** %s removed", file); |
} |
|
} |
} |
if (job->pid) { |
debug_job_printf("handle_signal passing signal to " |
debug_printf("JobInterrupt passing signal to " |
"child %ld running %s.\n", (long)job->pid, |
"child %ld.\n", (long)job->pid); |
job->node->name); |
killpg(job->pid, signo); |
really_kill(job->pid, signo); |
} |
|
} |
} |
|
|
if (runINTERRUPT && !touchFlag) { |
if (signo == SIGINT && !touchFlag) { |
if ((interrupt_node->type & OP_DUMMY) == 0) { |
if ((interrupt_node->type & OP_DUMMY) == 0) { |
ignoreErrors = false; |
ignoreErrors = false; |
|
|
JobStart(interrupt_node, 0); |
Job_Make(interrupt_node); |
loop_handle_running_jobs(); |
|
} |
} |
} |
} |
exit(signo); |
loop_handle_running_jobs(); |
|
print_errors(); |
|
|
|
/* die by that signal */ |
|
sigprocmask(SIG_BLOCK, &sigset, NULL); |
|
signal(signo, SIG_DFL); |
|
really_kill(getpid(), signo); |
|
sigprocmask(SIG_SETMASK, &emptyset, NULL); |
|
/*NOTREACHED*/ |
} |
} |
|
|
/* |
/* |
|
|
* Do final processing such as the running of the commands |
* Do final processing such as the running of the commands |
* attached to the .END target. |
* attached to the .END target. |
* |
* |
* Results: |
* return true if fatal errors have happened. |
* Number of errors reported. |
|
* |
|
*----------------------------------------------------------------------- |
*----------------------------------------------------------------------- |
*/ |
*/ |
int |
bool |
Job_Finish(void) |
Job_Finish(void) |
{ |
{ |
|
bool errors = errorJobs != NULL; |
|
|
if ((end_node->type & OP_DUMMY) == 0) { |
if ((end_node->type & OP_DUMMY) == 0) { |
if (errors) { |
if (errors) { |
Error("Errors reported so .END ignored"); |
Error("Errors reported so .END ignored"); |
} else { |
} else { |
JobStart(end_node, JOB_IS_SPECIAL); |
Job_Make(end_node); |
loop_handle_running_jobs(); |
loop_handle_running_jobs(); |
} |
} |
} |
} |
return errors; |
return errors; |
} |
} |
|
|
|
void |
|
Job_Begin(void) |
|
{ |
|
if ((begin_node->type & OP_DUMMY) == 0) { |
|
Job_Make(begin_node); |
|
loop_handle_running_jobs(); |
|
} |
|
} |
|
|
#ifdef CLEANUP |
#ifdef CLEANUP |
void |
void |
Job_End(void) |
Job_End(void) |
|
|
* Job_AbortAll -- |
* Job_AbortAll -- |
* 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 |
* This function is to be called only in the event of a major |
* error. Most definitely NOT to be called from JobInterrupt. |
* error. |
* |
* |
* Side Effects: |
* Side Effects: |
* All children are killed, not just the firstborn |
* All children are killed |
*----------------------------------------------------------------------- |
*----------------------------------------------------------------------- |
*/ |
*/ |
void |
void |
Job_AbortAll(void) |
Job_AbortAll(void) |
{ |
{ |
LstNode ln; /* element in job table */ |
|
Job *job; /* the job descriptor in that element */ |
Job *job; /* the job descriptor in that element */ |
int foo; |
int foo; |
|
|
aborting = ABORT_ERROR; |
aborting = ABORT_ERROR; |
|
|
if (nJobs) { |
for (job = runningJobs; job != NULL; job = job->next) { |
for (ln = Lst_First(&runningJobs); ln != NULL; |
really_kill(job->pid, SIGINT); |
ln = Lst_Adv(ln)) { |
really_kill(job->pid, SIGKILL); |
job = (Job *)Lst_Datum(ln); |
|
|
|
/* |
|
* kill the child process with increasingly drastic |
|
* signals to make darn sure it's dead. |
|
*/ |
|
killpg(job->pid, SIGINT); |
|
killpg(job->pid, SIGKILL); |
|
} |
|
} |
} |
|
|
/* |
/* |
|
|
while (waitpid(WAIT_ANY, &foo, WNOHANG) > 0) |
while (waitpid(WAIT_ANY, &foo, WNOHANG) > 0) |
continue; |
continue; |
} |
} |
|
|