#include #include #include #include #include #include #include #include #include #include #include #include struct proc { pid_t pid; char *cmd; char **argv; int _stdin; int _stdout; int _stderr; time_t last_started; time_t next_start; int num_starts; } *procs; int num_procs = 0; struct fd { int fds[2]; } *fds; volatile sig_atomic_t terminate = 0; volatile sig_atomic_t sendsignal = 0; void reap_all() { int i; for (i = 0; i < num_procs; i++) { if (procs[i].pid != -1) kill(procs[i].pid, SIGTERM); } sleep(5); for (i = 0; i < num_procs; i++) { if (procs[i].pid != -1) kill(procs[i].pid, SIGKILL); } while (wait(NULL) != -1 || errno == EINTR) ; } void fail(int exit, const char *msg, ...) { reap_all(); va_list args; va_start(args, msg); verr(exit, msg, args); va_end(args); // Not reached? } void failx(int exit, const char *msg, ...) { reap_all(); va_list args; va_start(args, msg); verrx(exit, msg, args); va_end(args); // Not reached? } void grow_argv(char ***argv, size_t *len) { #define GROW_SIZE 32 char **ret = realloc(*argv, sizeof(char *) * (*len + GROW_SIZE)); if (ret == NULL) err(1, "realloc"); *argv = ret; *len += GROW_SIZE; #undef GROW_SIZE } void gen_argv(struct proc *proc) { char *saveptr, *tmp, *cmd; char **argv = NULL, **head; size_t len = 0, used = 0; cmd = strdup(proc->cmd); if (cmd == NULL) err(1, "strdup"); grow_argv(&argv, &len); head = argv; tmp = strtok_r(cmd, " \t", &saveptr); while (tmp != NULL) { *(head++) = strdup(tmp); used++; if (used >= len) { grow_argv(&argv, &len); head = argv + used; } tmp = strtok_r(NULL, " \t", &saveptr); } *head = NULL; free(cmd); proc->argv = argv; } void run_cmd(struct proc *proc) { pid_t cpid; // XXX: This REALLY needs to be done better time_t t = time(NULL); if (t < procs->next_start) sleep(procs->next_start - t); procs->last_started = time(NULL); cpid = fork(); if (cpid == 0) { if (proc->_stdin != -1) { if (close(0) == -1) fail(1, "close"); if (dup2(proc->_stdin, 0) == -1) fail(1, "dup2"); } if (proc->_stdout != -1) { if (close(1) == -1) fail(1, "close"); if (dup2(proc->_stdout, 1) == -1) fail(1, "dup2"); } if (proc->_stderr != -1) { if (close(2) == -1) fail(1, "close"); if (dup2(proc->_stderr, 2) == -1) fail(1, "dup2"); } int i; for (i = 0; i < num_procs - 1; i++) { close(fds[i].fds[0]); // These aren't very important if they fail close(fds[i].fds[1]); } execvp(proc->argv[0], proc->argv); fail(1, "execvp"); } else if (cpid == -1) { fail(1, "fork"); } else { proc->pid = cpid; } } int have_children() { int i; for (i = 0; i < num_procs; i++) { if (procs[i].pid != -1) return 1; } return 0; } struct proc * proc_by_pid(pid_t pid) { int i; for (i = 0; i < num_procs; i++) { if (procs[i].pid == pid) return &procs[i]; } return NULL; } void signal_procs() { int i; for (i = 0; i < num_procs; i++) { if (procs[i].pid != -1) kill(procs[i].pid, sendsignal); //XXX: Check return? } sendsignal = 0; } void handle_nonfatal(int sig, siginfo_t *siginfo, void *ucontext) { sendsignal = sig; } void handle_fatal(int sig, siginfo_t *siginfo, void *ucontext) { terminate = 1; sendsignal = sig; } void setup_signals() { struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_sigaction = &handle_fatal; act.sa_flags = SA_SIGINFO; if (sigaction(SIGINT, &act, NULL) == -1) err(1, "sigaction"); if (sigaction(SIGHUP, &act, NULL) == -1) err(1, "sigaction"); if (sigaction(SIGTERM, &act, NULL) == -1) err(1, "sigaction"); act.sa_sigaction = &handle_nonfatal; if (sigaction(SIGUSR1, &act, NULL) == -1) err(1, "sigaction"); if (sigaction(SIGUSR2, &act, NULL) == -1) err(1, "sigaction"); } void start_missing_procs() { int i; for (i = 0; i < num_procs; i++) { if (procs[i].pid == -1) run_cmd(&procs[i]); } } void print_argv() { int i; for (i = 0; i < num_procs; i++) { char **tmp = procs[i].argv; while (*tmp) printf("%s ", *tmp++); printf("\n"); } } int have_nonstarted() { return 0; } int main(int argc, char **argv) { int i; if (argc < 2) { fprintf(stderr, "Usage: piper …\n"); return 1; } setup_signals(); num_procs = argc - 1; procs = malloc(sizeof(struct proc) * num_procs); for (i = 0; i < num_procs; i++) { procs[i].cmd = strdup(argv[i + 1]); if (procs[i].cmd == NULL) err(1, "strdup"); procs[i].pid = -1; procs[i]._stdin = -1; procs[i]._stdout = -1; procs[i]._stderr = -1; procs[i].last_started = 0; procs[i].next_start = 0; procs[i].num_starts = 0; gen_argv(&procs[i]); } fds = malloc(sizeof(struct fd) * (num_procs - 1)); if (fds == NULL) err(1, "malloc"); for (i = 0; i < num_procs - 1; i++) { if (pipe(fds[i].fds) == -1) err(1, "pipe"); procs[i]._stdout = fds[i].fds[1]; procs[i]._stderr = fds[i].fds[1]; procs[i + 1]._stdin = fds[i].fds[0]; } while (!terminate || have_children()) { pid_t pid; if (!terminate) start_missing_procs(); if (sendsignal != 0) signal_procs(); pid = waitpid(0, NULL, 0); if (pid == -1) { if (errno == EINTR) { continue; } else fail(1, "wait"); } struct proc *p = proc_by_pid(pid); if (p == NULL) continue; // XXX: Log something here? p->pid = -1; time_t t = time(NULL); if (p->last_started >= t - (p->num_starts + 1)) { p->next_start = t + p->num_starts; p->num_starts++; } else p->num_starts = 0; } return 0; }