diff --git a/README.md b/README.md index daacad3..1c851f2 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ A simple Unix utility in C to run multiple commands concurrently. * A very light alternative to classic init processes or supervisord to run multiple services in the same Docker container. * Is dead-simple to use. * Can be run without root permissions. -* Cleanly kills all the processes it starts. +* Cleanly kills all the processes it starts, including their subprocesses. * Delegates the restart duty to the upper level. -* Forwards stdout and stderr for proper logging with Docker. +* Forwards stdout and stderr for proper logging with Docker or systemd. Usage: `multirun "command1" "command2" ...` @@ -88,18 +88,21 @@ Here is an example of bad use case: A common cause for these problems is invalid usage of shell scripts that causes signals to not be propagated properly. -If you call multirun in a shell script, check that you launch it with the `exec` sh command. Example: +If you call multirun in a shell script, check that you launch it with the [`exec`](https://ss64.com/bash/exec.html) sh command. Example: ```bash #!/bin/sh + # any init code exec multirun arg1 arg2 ``` +If you don't use `exec` your main process will likely remain `/bin/sh` which won't forward signals correctly to your multirun process. + This advice is not specific to multirun and does apply to most containers that have a shell script as entrypoint. -Also, if you launch scripts with multirun that will launch the service you want, be sure to add `exec` in these scripts as well. Example: +Also, if you launch scripts with multirun that will launch the service you want, it is recommended to add `exec` in these scripts as well. Example: ```bash # multirun call diff --git a/multirun.c b/multirun.c index 327a95e..d630d22 100644 --- a/multirun.c +++ b/multirun.c @@ -63,12 +63,12 @@ int main(int argc, char *const *argv) { default: printf("Unknown option: %c\n", (char)optopt); print_help(); - exit(-1); + exit(-2); } } if (optind >= argc) { print_help(); - exit(-1); + exit(-2); } commands = &argv[optind]; nbr_processes = argc - optind; @@ -76,16 +76,16 @@ int main(int argc, char *const *argv) { } void launch_processes() { - int wstatus; - struct sigaction ssig; - subprocesses = malloc(sizeof(subprocess) * nbr_processes); for (int i = 0; i < nbr_processes; i++) { pid_t pid = fork(); if (pid == 0) { + // declare a new group + setpgrp(); + // execute subcommand sub_exec(commands[i]); - exit(-1); // should not happen + exit(-2); // should not happen } else { if (verbose) { printf("multirun: launched command %s with pid %d\n", commands[i], pid); @@ -99,14 +99,30 @@ void launch_processes() { } } - ssig.sa_handler = sig_receive; - if (sigaction(SIGINT, &ssig, NULL)) - exit(-1); - if (sigaction(SIGTERM, &ssig, NULL)) - exit(-1); + if (signal(SIGINT, sig_receive) == SIG_ERR) { + exit(-2); + } + if (signal(SIGTERM, sig_receive) == SIG_ERR) { + exit(-2); + } while (1) { - pid_t pid = wait(&wstatus); + int wstatus; + pid_t pid = waitpid(-1, &wstatus, 0); + + if (pid == -1) { + if (errno == ECHILD) { + break; // no more children + } if (errno == EINTR) { + continue; // interrupted + } else { + fprintf(stderr, "multirun: error during wait: %d\n", errno); + exit(-2); + } + } else if (pid == 0) { + break; // no more children + } + subprocess* process = NULL; for (int i = 0; i < nbr_processes; i++) { if (subprocesses[i].pid == pid) { @@ -114,6 +130,7 @@ void launch_processes() { break; } } + if (process != NULL) { // check if process is down if (WIFEXITED(wstatus) || WIFSIGNALED(wstatus)) { @@ -122,11 +139,11 @@ void launch_processes() { || (WIFSIGNALED(wstatus) && WTERMSIG(wstatus) != SIGINT && WTERMSIG(wstatus) != SIGTERM)) { process->error = 1; if (verbose) { - printf("multirun: command %s with pid %d exited abnormally\n", process->command, pid); + printf("multirun: command %s with pid %d exited abnormally\n", process->command, process->pid); } } else { if (verbose) { - printf("multirun: command %s with pid %d exited normally\n", process->command, pid); + printf("multirun: command %s with pid %d exited normally\n", process->command, process->pid); } } if (! closing) { @@ -140,23 +157,33 @@ void launch_processes() { } } else { if (verbose) { - printf("multirun: reaped zombie process with pid %d\n", pid); + printf("multirun: subchild process with pid %d ended\n", pid); } } - // check if all processes are stopped - int running = 0; - for (int i = 0; i < nbr_processes; i++) { - if (subprocesses[i].up) { - running = 1; - break; + } + + // ensure all child died in all groups + for (int i = 0; i < nbr_processes; i++) { + while (1) { + int wstatus; + int pid = waitpid(-subprocesses[i].pid, &wstatus, 0); + if (pid == -1) { + if (errno == ECHILD) { + break; // no more children in group + } if (errno == EINTR) { + continue; // interrupted + } else { + fprintf(stderr, "multirun: error during wait: %d\n", errno); + exit(-2); + } + } else if (pid == 0) { + break; // no more children } + // some more child must still exit } - if (! running) - break; } - // reap all potential zombies - reap_zombies(); - // check if there are errors + + // check if there were errors int error = 0; for (int i = 0; i < nbr_processes; i++) { if (subprocesses[i].error) { @@ -199,13 +226,21 @@ void sub_exec(const char* command) { ret = execlp("sh", "sh", "-c", ccommand, (char*) NULL); if (ret != 0) { fprintf(stderr, "multirun: error launching the subprocess: %s\n", strerror(errno)); - exit(-1); + exit(-2); } } void kill_all(int signal) { for (int i = 0; i < nbr_processes; i++) { - kill(subprocesses[i].pid, signal); + int ret = kill(-subprocesses[i].pid, signal); + if (ret != 0) { + if (errno == ESRCH) { + // ignore + } else { + fprintf(stderr, "multirun: error %d while killing processes\n", errno); + exit(-2); + } + } } } @@ -218,25 +253,3 @@ void sig_receive(int signum) { } kill_all(signum); } - -void reap_zombies() { - while (1) { - pid_t childpid; - int wstatus; - childpid = waitpid(-1, &wstatus, WNOHANG); - if (childpid == -1) { - if (errno == ECHILD) { - break; - } else { - fprintf(stderr, "multirun: error while reaping zombies\n"); - exit(-1); - } - } else if (childpid == 0) { - break; - } else { - if (verbose) { - printf("multirun: reaped zombie process with pid %d\n", childpid); - } - } - } -} diff --git a/zombiemaster/zombiemaster.c b/zombiemaster/zombiemaster.c index dc19e1f..1071c4e 100644 --- a/zombiemaster/zombiemaster.c +++ b/zombiemaster/zombiemaster.c @@ -2,36 +2,69 @@ #include #include #include +#include +#include +#include +#include +#include + + +void sig_receive_zombie_master(int signum); +void sig_receive_zombie(int signum); + +int stop = 0; int main(int argc, char* const* argv) { - int nbr_processes = 3; + struct sigaction ssig; + memset(&ssig, 0, sizeof ssig); + sigemptyset(&ssig.sa_mask); + + int nbr_processes = 1; printf("Executing zombie master\n"); for (int i = 0; i < nbr_processes; i++) { pid_t pid = fork(); if (pid == 0) { - printf("Executing secondary zombie master %d\n", i); - for (int j = 0; j < nbr_processes; j++) { - pid_t pid2 = fork(); - if (pid2 == 0) { - printf("Executing zombie %d.%d\n", i, j); - while (1) { - sleep(1); - printf("Hi! I'm zombie %d.%d and I die\n", i, j); - exit(-1); - } - } else { - // nothing - } + printf("Executing zombie %d\n", i); + ssig.sa_handler = sig_receive_zombie; + if (sigaction(SIGINT, &ssig, NULL)) + exit(-2); + if (sigaction(SIGTERM, &ssig, NULL)) + exit(-2); + while (!stop) { + sleep(1); } - sleep(2); - printf("Hi! I'm secondary zombie master %d and I die\n", i); - exit(-1); + printf("Zombie %d sleeps a little\n", i); + sleep(3); + printf("Hi! I'm zombie %d and I die\n", i); + exit(0); } else { // nothing } } + ssig.sa_handler = sig_receive_zombie_master; + if (sigaction(SIGINT, &ssig, NULL)) + exit(-2); + if (sigaction(SIGTERM, &ssig, NULL)) + exit(-2); + + while (!stop) { + sleep(1); + } + printf("Zombie master sleeps a little\n"); sleep(3); + printf("Hi! I'm zombie master and I die\n"); exit(0); } + +void sig_receive_zombie_master(int signum) { + printf("Me zombie master, me received %s bullet in the head\n", strsignal(signum)); + exit(0); + stop = 1; +} + +void sig_receive_zombie(int signum) { + printf("Me zombie, me received %s bullet in the head\n", strsignal(signum)); + stop = 1; +}