From d307e9b276ae0d2157072ebdbc65d0041fb5a0af Mon Sep 17 00:00:00 2001 From: Mike Gilbert Date: Sat, 25 May 2024 13:50:01 -0400 Subject: [PATCH] openrc-init: rework signal handling Minimize the amount of work done in the signal handler by maintaining a set of received signals and deferring the actual work to the main loop. Treat SIGRTMIN+4 as a power-off signal for compatibility with systemd-machined. Adjust handle_shutdown to skip sending SIGTERM / SIGKILL if there are no processes remaining. Signed-off-by: Mike Gilbert --- src/openrc-init/openrc-init.c | 213 ++++++++++++++++++++++++---------- 1 file changed, 149 insertions(+), 64 deletions(-) diff --git a/src/openrc-init/openrc-init.c b/src/openrc-init/openrc-init.c index 90fdcbb4d..47ab1b87e 100644 --- a/src/openrc-init/openrc-init.c +++ b/src/openrc-init/openrc-init.c @@ -18,6 +18,7 @@ * except according to the terms contained in the LICENSE file. */ +#include #include #include #include @@ -38,6 +39,7 @@ #endif #include "rc.h" +#include "helpers.h" #include "plugin.h" #include "wtmp.h" #include "version.h" @@ -45,6 +47,26 @@ static const char *path_default = "/sbin:/usr/sbin:/bin:/usr/bin"; static const char *rc_default_runlevel = "default"; +/* wait for children until a signal occurs or we run out of children */ +static pid_t wait_children(int options) +{ + pid_t pid; + do { + pid = waitpid(-1, NULL, options); + } while (pid > 0); + return pid < 0 ? -errno : pid; +} + +/* wait for children to exit, stop when a specific child exits */ +static pid_t wait_child(pid_t child) +{ + pid_t pid; + do { + pid = waitpid(-1, NULL, 0); + } while ((pid > 0 && pid != child) || (pid < 0 && errno == EINTR)); + return pid < 0 ? -errno : pid; +} + static void do_openrc(const char *runlevel) { pid_t pid; @@ -72,9 +94,7 @@ static void do_openrc(const char *runlevel) default: /* restore our signal mask */ sigprocmask(SIG_SETMASK, &our_signals, NULL); - while (waitpid(pid, NULL, 0) != pid) - if (errno == ECHILD) - break; + wait_child(pid); break; } } @@ -104,18 +124,44 @@ static void handle_reexec(char *my_name) return; } +static void alarm_handler(int signum RC_UNUSED) { + /* do nothing */ +} + static void handle_shutdown(const char *runlevel, int cmd) { - struct timespec ts; - do_openrc(runlevel); - printf("Sending the final term signal\n"); - kill(-1, SIGTERM); - ts.tv_sec = 3; - ts.tv_nsec = 0; - nanosleep(&ts, NULL); - printf("Sending the final kill signal\n"); - kill(-1, SIGKILL); + + /* wait on any children that have already exited */ + if (wait_children(WNOHANG) != -ECHILD) { + pid_t pid; + sigset_t signals; + struct sigaction sa = { .sa_handler = alarm_handler }; + + sigaction(SIGALRM, &sa, NULL); + + sigfillset(&signals); + sigdelset(&signals, SIGALRM); + sigprocmask(SIG_SETMASK, &signals, NULL); + + printf("Sending the final term signal\n"); + kill(-1, SIGTERM); + + /* Wait up to 3 seconds for children to exit */ + alarm(3); + pid = wait_children(0); + alarm(0); + + if (pid != -ECHILD) { + printf("Sending the final kill signal\n"); + kill(-1, SIGKILL); + + alarm(3); + wait_children(0); + alarm(0); + } + } + sync(); reboot(cmd); } @@ -143,7 +189,7 @@ static void run_program(const char *prog) } /* Unmask signals and wait for child */ sigprocmask(SIG_SETMASK, &old, NULL); - if (rc_waitpid(pid) == -1) + if (wait_child(pid) < 0) perror("init"); } @@ -184,53 +230,102 @@ static void handle_single(void) do_openrc("single"); } -static void reap_zombies(void) -{ - pid_t pid; +static volatile sig_atomic_t received_signals[64] = { 0 }; - for (;;) { - pid = waitpid(-1, NULL, WNOHANG); - if (pid == 0) - break; - else if (pid == -1) { - if (errno == ECHILD) - break; - perror("waitpid"); - continue; - } +static bool check_signal(int signum) +{ + assert(signum >= 1 && signum <= 64); + if (received_signals[signum - 1]) { + received_signals[signum - 1] = 0; + return true; } + return false; } -static void signal_handler(int sig) +static void signal_handler(int signum) { - switch (sig) { - case SIGINT: - handle_shutdown("reboot", RB_AUTOBOOT); - break; - case SIGTERM: + assert(signum >= 1 && signum <= 64); + received_signals[signum - 1] = 1; +} + +static void setup_signal(sigset_t *signals, int signum) +{ + struct sigaction sa = { .sa_handler = signal_handler }; + sigaction(signum, &sa, NULL); + sigaddset(signals, signum); +} + +static void setup_signals() +{ + sigset_t signals; + + sigfillset(&signals); + sigprocmask(SIG_SETMASK, &signals, NULL); + + sigemptyset(&signals); + setup_signal(&signals, SIGCHLD); + setup_signal(&signals, SIGINT); + setup_signal(&signals, SIGTERM); #ifdef SIGPWR - case SIGPWR: + setup_signal(&signals, SIGPWR); #endif - handle_shutdown("shutdown", RB_HALT_SYSTEM); - break; - case SIGCHLD: - reap_zombies(); - break; - default: - printf("Unknown signal received, %d\n", sig); - break; - } + setup_signal(&signals, SIGRTMIN+3); + setup_signal(&signals, SIGRTMIN+4); + setup_signal(&signals, SIGRTMIN+5); + setup_signal(&signals, SIGRTMIN+6); + setup_signal(&signals, SIGRTMIN+13); + setup_signal(&signals, SIGRTMIN+14); + setup_signal(&signals, SIGRTMIN+15); + setup_signal(&signals, SIGRTMIN+16); + sigprocmask(SIG_UNBLOCK, &signals, NULL); +} + +static void process_signals() +{ + if (check_signal(SIGRTMIN+16)) + reboot(RB_KEXEC); + + if (check_signal(SIGRTMIN+15)) + reboot(RB_AUTOBOOT); + + if (check_signal(SIGRTMIN+14)) + reboot(RB_POWER_OFF); + + if (check_signal(SIGRTMIN+13)) + reboot(RB_HALT_SYSTEM); + + if (check_signal(SIGRTMIN+6)) + handle_shutdown("reboot", RB_KEXEC); + + if (check_signal(SIGRTMIN+5)) + handle_shutdown("reboot", RB_AUTOBOOT); + + if (check_signal(SIGINT)) + handle_shutdown("reboot", RB_AUTOBOOT); + + if (check_signal(SIGRTMIN+4)) + handle_shutdown("shutdown", RB_POWER_OFF); + + if (check_signal(SIGTERM)) + handle_shutdown("shutdown", RB_POWER_OFF); +#ifdef SIGPWR + if (check_signal(SIGPWR)) + handle_shutdown("shutdown", RB_HALT_SYSTEM); +#endif + if (check_signal(SIGRTMIN+3)) + handle_shutdown("shutdown", RB_HALT_SYSTEM); + + if (check_signal(SIGCHLD)) + wait_children(WNOHANG); } int main(int argc, char **argv) { char *default_runlevel; char buf[2048]; - int count; + size_t count; FILE *fifo; bool reexec = false; - sigset_t signals; - struct sigaction sa; #ifdef HAVE_SELINUX int enforce = 0; #endif @@ -271,25 +366,8 @@ int main(int argc, char **argv) if (default_runlevel && strcmp(default_runlevel, "reexec") == 0) reexec = true; - /* block all signals we do not handle */ - sigfillset(&signals); - sigdelset(&signals, SIGCHLD); - sigdelset(&signals, SIGINT); - sigdelset(&signals, SIGTERM); -#ifdef SIGPWR - sigdelset(&signals, SIGPWR); -#endif - sigprocmask(SIG_SETMASK, &signals, NULL); + setup_signals(); - /* install signal handler */ - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = signal_handler; - sigaction(SIGCHLD, &sa, NULL); - sigaction(SIGINT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); -#ifdef SIGPWR - sigaction(SIGPWR, &sa, NULL); -#endif reboot(RB_DISABLE_CAD); /* set default path */ @@ -302,6 +380,8 @@ int main(int argc, char **argv) perror("mkfifo"); for (;;) { + process_signals(); + /* This will block until a command is sent down the pipe... */ fifo = fopen(RC_INIT_FIFO, "r"); if (!fifo) { @@ -309,9 +389,14 @@ int main(int argc, char **argv) perror("fopen"); continue; } - count = fread(buf, 1, sizeof(buf) - 1, fifo); + + do { + count = fread(buf, 1, sizeof(buf) - 1, fifo); + } while (count == 0 && ferror(fifo) && errno == EINTR); + buf[count] = 0; fclose(fifo); + printf("PID1: Received \"%s\" from FIFO...\n", buf); if (strcmp(buf, "halt") == 0) handle_shutdown("shutdown", RB_HALT_SYSTEM);