diff options
Diffstat (limited to 'usr.bin/make/engine.c')
-rw-r--r-- | usr.bin/make/engine.c | 363 |
1 files changed, 362 insertions, 1 deletions
diff --git a/usr.bin/make/engine.c b/usr.bin/make/engine.c index 4dba6633604..f49d9845937 100644 --- a/usr.bin/make/engine.c +++ b/usr.bin/make/engine.c @@ -1,4 +1,4 @@ -/* $OpenBSD: engine.c,v 1.9 2007/09/17 12:42:09 espie Exp $ */ +/* $OpenBSD: engine.c,v 1.10 2007/11/02 17:27:24 espie Exp $ */ /* * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. * Copyright (c) 1988, 1989 by Adam de Boor @@ -33,12 +33,17 @@ * SUCH DAMAGE. */ +#include <sys/types.h> +#include <sys/wait.h> #include <limits.h> #include <stdio.h> +#include <stdlib.h> +#include <ctype.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <string.h> +#include <signal.h> #include "config.h" #include "defines.h" #include "dir.h" @@ -51,10 +56,22 @@ #include "lst.h" #include "timestamp.h" #include "make.h" +#include "pathnames.h" +#include "error.h" +#include "str.h" +#include "memory.h" static void MakeTimeStamp(void *, void *); static void MakeAddAllSrc(void *, void *); static int rewrite_time(const char *); +static void setup_signal(int); +static void setup_all_signals(void); +static void setup_meta(void); +static char **recheck_command_for_shell(char **); + +static int setup_and_run_command(char *, GNode *, int); +static void run_command(const char *, bool); +static void handle_compat_interrupts(GNode *); bool Job_CheckCommands(GNode *gn, void (*abortProc)(char *, ...)) @@ -381,3 +398,347 @@ Make_OODate(GNode *gn) return oodate; } +volatile sig_atomic_t got_signal; + +volatile sig_atomic_t got_SIGINT, got_SIGHUP, got_SIGQUIT, + got_SIGTERM, got_SIGTSTP, got_SIGTTOU, got_SIGTTIN, got_SIGWINCH; + +static void +setup_signal(int sig) +{ + if (signal(sig, SIG_IGN) != SIG_IGN) { + (void)signal(sig, SigHandler); + } +} + +void +setup_all_signals() +{ + /* + * 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); + /* + * There are additional signals that need to be caught and passed if + * either the export system wants to be told directly of signals or if + * we're giving each job its own process group (since then it won't get + * signals from the terminal driver as we own the terminal) + */ +#if defined(USE_PGRP) + setup_signal(SIGTSTP); + setup_signal(SIGTTOU); + setup_signal(SIGTTIN); + setup_signal(SIGWINCH); +#endif +} + +void +SigHandler(int sig) +{ + switch(sig) { + case SIGINT: + got_SIGINT++; + got_signal = 1; + break; + case SIGHUP: + got_SIGHUP++; + got_signal = 1; + break; + case SIGQUIT: + got_SIGQUIT++; + got_signal = 1; + break; + case SIGTERM: + got_SIGTERM++; + got_signal = 1; + break; +#ifdef USE_PGRP + case SIGTSTP: + got_SIGTSTP++; + got_signal = 1; + break; + case SIGTTOU: + got_SIGTTOU++; + got_signal = 1; + break; + case SIGTTIN: + got_SIGTTIN++; + got_signal = 1; + break; + case SIGWINCH: + got_SIGWINCH++; + got_signal = 1; + break; +#endif + } +} + +/* The following array is used to make a fast determination of which + * characters are interpreted specially by the shell. If a command + * contains any of these characters, it is executed by the shell, not + * directly by us. */ +static char meta[256]; + +void +setup_meta(void) +{ + char *p; + + for (p = "#=|^(){};&<>*?[]:$`\\\n"; *p != '\0'; p++) + meta[(unsigned char) *p] = 1; + /* The null character serves as a sentinel in the string. */ + meta[0] = 1; +} + +static char ** +recheck_command_for_shell(char **av) +{ + char *runsh[] = { + "alias", "cd", "eval", "exit", "read", "set", "ulimit", + "unalias", "unset", "wait", "umask", NULL + }; + + char **p; + + /* optimization: if exec cmd, we avoid the intermediate shell */ + if (strcmp(av[0], "exec") == 0) + av++; + + for (p = runsh; *p; p++) + if (strcmp(av[0], *p) == 0) + return NULL; + + return av; +} + +static void +run_command(const char *cmd, bool errCheck) +{ + const char *p; + char *shargv[4]; + char **todo; + + shargv[0] = _PATH_BSHELL; + + shargv[1] = errCheck ? "-ec" : "-c"; + shargv[2] = (char *)cmd; + shargv[3] = NULL; + + todo = shargv; + + + /* Search for meta characters in the command. If there are no meta + * characters, there's no need to execute a shell to execute the + * command. */ + for (p = cmd; !meta[(unsigned char)*p]; p++) + continue; + if (*p == '\0') { + char *bp; + char **av; + int argc; + /* No meta-characters, so probably no need to exec a shell. + * Break the command into words to form an argument vector + * we can execute. */ + av = brk_string(cmd, &argc, &bp); + av = recheck_command_for_shell(av); + if (av != NULL) + todo = av; + } + execvp(todo[0], todo); + + if (errno == ENOENT) + fprintf(stderr, "%s: not found\n", todo[0]); + else + perror(todo[0]); + _exit(1); +} + +/*- + *----------------------------------------------------------------------- + * setup_and_run_command -- + * Execute the next command for a target. If the command returns an + * error, the node's made field is set to ERROR and creation stops. + * + * Results: + * 0 in case of error, 1 if ok. + * + * Side Effects: + * The node's 'made' field may be set to ERROR. + *----------------------------------------------------------------------- + */ +static int +setup_and_run_command(char *cmd, GNode *gn, int dont_fork) +{ + char *cmdStart; /* Start of expanded command */ + bool silent; /* Don't print command */ + bool doExecute; /* Execute the command */ + bool errCheck; /* Check errors */ + int reason; /* Reason for child's death */ + int status; /* Description of child's death */ + pid_t cpid; /* Child actually found */ + pid_t stat; /* Status of fork */ + + silent = gn->type & OP_SILENT; + errCheck = !(gn->type & OP_IGNORE); + doExecute = !noExecute; + + cmdStart = Var_Subst(cmd, &gn->context, false); + + /* How can we execute a null command ? we warn the user that the + * command expanded to nothing (is this the right thing to do?). */ + if (*cmdStart == '\0') { + free(cmdStart); + Error("%s expands to empty string", cmd); + return 1; + } else + cmd = cmdStart; + + for (;; cmd++) { + if (*cmd == '@') + silent = DEBUG(LOUD) ? false : true; + else if (*cmd == '-') + errCheck = false; + else if (*cmd == '+') + doExecute = true; + else + break; + } + while (isspace(*cmd)) + cmd++; + /* Print the command before echoing if we're not supposed to be quiet + * for this one. We also print the command if -n given. */ + if (!silent || noExecute) { + printf("%s\n", cmd); + fflush(stdout); + } + /* If we're not supposed to execute any commands, this is as far as + * we go... */ + if (!doExecute) + return 1; + + /* if we're running in parallel mode, we try not to fork the last + * command, since it's exit status will be just fine... unless + * errCheck is not set, in which case we must deal with the + * status ourselves. + */ + if (dont_fork && errCheck) + run_command(cmd, errCheck); + /*NOTREACHED*/ + + /* Fork and execute the single command. If the fork fails, we abort. */ + switch (cpid = fork()) { + case -1: + Fatal("Could not fork"); + /*NOTREACHED*/ + case 0: + run_command(cmd, errCheck); + /*NOTREACHED*/ + default: + break; + } + free(cmdStart); + + /* The child is off and running. Now all we can do is wait... */ + while (1) { + + while ((stat = wait(&reason)) != cpid) { + if (stat == -1 && errno != EINTR) + break; + } + + if (got_signal) + break; + + if (stat != -1) { + if (WIFSTOPPED(reason)) + status = WSTOPSIG(reason); /* stopped */ + else if (WIFEXITED(reason)) { + status = WEXITSTATUS(reason); /* exited */ + if (status != 0) + printf("*** Error code %d", status); + } else { + status = WTERMSIG(reason); /* signaled */ + printf("*** Signal %d", status); + } + + + if (!WIFEXITED(reason) || status != 0) { + if (errCheck) { + gn->made = ERROR; + if (keepgoing) + /* Abort the current target, + * but let others continue. */ + printf(" (continuing)\n"); + } else { + /* Continue executing commands for + * this target. If we return 0, + * this will happen... */ + printf(" (ignored)\n"); + status = 0; + } + } + return !status; + } else + Fatal("error in wait: %d", stat); + /*NOTREACHED*/ + } + return 0; +} + +static void +handle_compat_interrupts(GNode *gn) +{ + if (!Targ_Precious(gn)) { + char *file = Varq_Value(TARGET_INDEX, gn); + + if (!noExecute && eunlink(file) != -1) + Error("*** %s removed\n", file); + } + if (got_SIGINT) { + signal(SIGINT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGHUP, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + got_signal = 0; + got_SIGINT = 0; + run_gnode(interrupt_node, 0); + exit(255); + } + exit(255); +} + +int +run_gnode(GNode *gn, int parallel) +{ + LstNode ln, nln; + + if (gn != NULL && (gn->type & OP_DUMMY) == 0) { + gn->made = MADE; + for (ln = Lst_First(&gn->commands); ln != NULL; ln = nln) { + nln = Lst_Adv(ln); + if (setup_and_run_command(Lst_Datum(ln), gn, + parallel && nln == NULL) == 0) + break; + } + if (got_signal && !parallel) + handle_compat_interrupts(gn); + return gn->made; + } else + return NOSUCHNODE; +} + +void +setup_engine() +{ + static int already_setup = 0; + + if (!already_setup) { + setup_meta(); + setup_all_signals(); + already_setup = 1; + } +} |