From e8bc98ab474296c30724fc6444d7920ee3978cc2 Mon Sep 17 00:00:00 2001 From: Mike Gilbert Date: Sat, 25 May 2024 13:50:01 -0400 Subject: [PATCH 1/2] 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 | 209 +++++++++++++++++++++++----------- 1 file changed, 145 insertions(+), 64 deletions(-) diff --git a/src/openrc-init/openrc-init.c b/src/openrc-init/openrc-init.c index 90fdcbb4d..2601699ab 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,98 @@ 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); + sigdelset(signals, signum); +} + +static void setup_signals() +{ + sigset_t signals; + sigfillset(&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_SETMASK, &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 +362,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 +376,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 +385,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); From 4ad169586395456c266e76786bd8b1d9f20cb74a Mon Sep 17 00:00:00 2001 From: Mike Gilbert Date: Sun, 26 May 2024 18:42:13 -0400 Subject: [PATCH 2/2] openrc-init: refactor code to read the command fifo Move fifo-related code out of main into a separate function. Use raw open/read calls instead of stdio to read from fifo. Make default_runlevel a global variable to simplify init() calls. Signed-off-by: Mike Gilbert --- src/openrc-init/openrc-init.c | 107 +++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 42 deletions(-) diff --git a/src/openrc-init/openrc-init.c b/src/openrc-init/openrc-init.c index 2601699ab..20212ab99 100644 --- a/src/openrc-init/openrc-init.c +++ b/src/openrc-init/openrc-init.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -44,8 +45,10 @@ #include "wtmp.h" #include "version.h" +static const char *default_runlevel = NULL; +static const char *my_name = "openrc-init"; static const char *path_default = "/sbin:/usr/sbin:/bin:/usr/bin"; -static const char *rc_default_runlevel = "default"; +static const char * const rc_default_runlevel = "default"; /* wait for children until a signal occurs or we run out of children */ static pid_t wait_children(int options) @@ -99,7 +102,7 @@ static void do_openrc(const char *runlevel) } } -static void init(const char *default_runlevel) +static void init() { const char *runlevel = NULL; do_openrc("sysinit"); @@ -118,7 +121,7 @@ static void init(const char *default_runlevel) log_wtmp("reboot", "~~", 0, RUN_LVL, "~~"); } -static void handle_reexec(char *my_name) +static void handle_reexec() { execlp(my_name, my_name, "reexec", NULL); return; @@ -315,12 +318,62 @@ static void process_signals() wait_children(WNOHANG); } +static void read_fifo() +{ + static int fifo = -1; + ssize_t count; + char buf[10]; + + if (fifo < 0) + /* This will block until a process opens the fifo for writing */ + fifo = open(RC_INIT_FIFO, O_RDONLY|O_CLOEXEC); + + if (fifo < 0) { + if (errno != EINTR) + fprintf(stderr, "%s: open(%s): %s\n", my_name, + RC_INIT_FIFO, strerror(errno)); + return; + } + + count = read(fifo, buf, sizeof(buf) - 1); + + if (count < 0) { + if (errno != EINTR) + fprintf(stderr, "%s: read(%s): %s\n", my_name, + RC_INIT_FIFO, strerror(errno)); + /* Keep the fifo open to avoid sending SIGPIPE to the writer */ + return; + } + + buf[count] = 0; + + close(fifo); + fifo = -1; + + if (count == 0) + /* Another process opened the fifo without writing anything */ + return; + + printf("PID1: Received \"%s\" from FIFO...\n", buf); + if (strcmp(buf, "halt") == 0) + handle_shutdown("shutdown", RB_HALT_SYSTEM); + else if (strcmp(buf, "kexec") == 0) + handle_shutdown("reboot", RB_KEXEC); + else if (strcmp(buf, "poweroff") == 0) + handle_shutdown("shutdown", RB_POWER_OFF); + else if (strcmp(buf, "reboot") == 0) + handle_shutdown("reboot", RB_AUTOBOOT); + else if (strcmp(buf, "reexec") == 0) + handle_reexec(); + else if (strcmp(buf, "single") == 0) { + handle_single(); + open_shell(); + init(); + } +} + int main(int argc, char **argv) { - char *default_runlevel; - char buf[2048]; - size_t count; - FILE *fifo; bool reexec = false; #ifdef HAVE_SELINUX int enforce = 0; @@ -354,10 +407,11 @@ int main(int argc, char **argv) printf("OpenRC init version %s starting\n", VERSION); + if (argc > 0) + my_name = argv[0]; + if (argc > 1) default_runlevel = argv[1]; - else - default_runlevel = NULL; if (default_runlevel && strcmp(default_runlevel, "reexec") == 0) reexec = true; @@ -370,45 +424,14 @@ int main(int argc, char **argv) setenv("PATH", path_default, 1); if (!reexec) - init(default_runlevel); + init(); if (mkfifo(RC_INIT_FIFO, 0600) == -1 && errno != EEXIST) perror("mkfifo"); for (;;) { process_signals(); - - /* This will block until a command is sent down the pipe... */ - fifo = fopen(RC_INIT_FIFO, "r"); - if (!fifo) { - if (errno != EINTR) - perror("fopen"); - continue; - } - - 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); - else if (strcmp(buf, "kexec") == 0) - handle_shutdown("reboot", RB_KEXEC); - else if (strcmp(buf, "poweroff") == 0) - handle_shutdown("shutdown", RB_POWER_OFF); - else if (strcmp(buf, "reboot") == 0) - handle_shutdown("reboot", RB_AUTOBOOT); - else if (strcmp(buf, "reexec") == 0) - handle_reexec(argv[0]); - else if (strcmp(buf, "single") == 0) { - handle_single(); - open_shell(); - init(default_runlevel); - } + read_fifo(); } return 0; }