From d6281c023a187b9fe707e55807ccb44e648d94dd Mon Sep 17 00:00:00 2001 From: Ferass El Hafidi Date: Wed, 13 May 2026 19:57:04 +0000 Subject: [PATCH 1/2] openrc-run: don't exec plugin hook in atexit On exit, openrc's cleanup function, which is ran on exit(), checks if there are remaining hooks to run (by checking hook_out) and runs plugins. Running plugins means fork()ing and afterwards exit(). Calling exit() more than once like OpenRC does is undefined behavior. glibc seems to not care about this, but musl keeps track of if exit() is getting called >1 times (exit() being called in forks do count), and if it is, it will helpfully hang, as this should not happen. In practise this means, if a plugin (such as plymouth-openrc-plugin) is installed in a musl-based system, a failing `service <...> start` will cause a hang of that command. Remove all the handling of plugin hooks on exit, and instead explicitely run the OUT hooks on most failures, but *before* exit(). Given this plugin system will be replaced in the future anyway, and there aren't many users of it, this slight change of behavior should be fine. Signed-off-by: Ferass El Hafidi --- src/openrc-run/openrc-run.c | 99 +++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 36 deletions(-) diff --git a/src/openrc-run/openrc-run.c b/src/openrc-run/openrc-run.c index 4d8c4c753..22445b08a 100644 --- a/src/openrc-run/openrc-run.c +++ b/src/openrc-run/openrc-run.c @@ -93,7 +93,6 @@ static RC_STRINGLIST *restart_services; static RC_STRINGLIST *need_services; static RC_STRINGLIST *use_services; static RC_STRINGLIST *want_services; -static RC_HOOK hook_out; static int exclusive_fd = -1, master_tty = -1; static bool in_background, deps, dry_run; static volatile bool sighup, skip_mark, timedout; @@ -234,14 +233,6 @@ cleanup(void) restore_state(); if (!rc_in_plugin) { - if (hook_out) { - rc_plugin_run(hook_out, applet); - if (hook_out == RC_HOOK_SERVICE_START_DONE) - rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); - else if (hook_out == RC_HOOK_SERVICE_STOP_DONE) - rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); - } - if (restart_services) start_services(restart_services); } @@ -356,11 +347,21 @@ svc_exec(const char *command) }; /* Setup our signal pipe */ - if (pipe2(signal_pipe, O_CLOEXEC) == -1) + if (pipe2(signal_pipe, O_CLOEXEC) == -1) { + if (!strcmp(command, "start")) + rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); + else if (!strcmp(command, "stop")) + rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); eerrorx("%s: pipe2: %s", applet, applet); + } - if ((errno = posix_spawn_file_actions_init(&tty))) + if ((errno = posix_spawn_file_actions_init(&tty))) { + if (!strcmp(command, "start")) + rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); + else if (!strcmp(command, "stop")) + rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); eerrorx("%s: posix_spawn_file_actions_init: %s", applet, strerror(errno)); + } /* Open a pty for our prefixed output * We do this instead of mapping pipes to stdout, stderr so that @@ -382,8 +383,13 @@ svc_exec(const char *command) fcntl(slave_tty, F_SETFD, flags | FD_CLOEXEC); if ((errno = posix_spawn_file_actions_adddup2(&tty, slave_tty, STDOUT_FILENO)) - || (errno = posix_spawn_file_actions_adddup2(&tty, slave_tty, STDERR_FILENO))) + || (errno = posix_spawn_file_actions_adddup2(&tty, slave_tty, STDERR_FILENO))) { + if (!strcmp(command, "start")) + rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); + else if (!strcmp(command, "stop")) + rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); eerrorx("%s: posix_spawn_file_actions_adddup2: %s", applet, strerror(errno)); + } } if (faccessat(rc_dirfd(RC_DIR_SVCDIR), "openrc-run.sh", F_OK, 0) == 0) { @@ -395,8 +401,13 @@ svc_exec(const char *command) rc_environ_export(&env, (const char *const *) environ, &envp); einfov("Executing: %s %s %s", argv[0], service, command); - if ((errno = posix_spawn(&service_pid, argv[0], &tty, NULL, UNCONST(argv), envp ? UNCONST(envp) : environ))) + if ((errno = posix_spawn(&service_pid, argv[0], &tty, NULL, UNCONST(argv), envp ? UNCONST(envp) : environ))) { + if (!strcmp(command, "start")) + rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); + else if (!strcmp(command, "stop")) + rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); eerrorx("%s: exec '%s': %s", service, argv[0], strerror(errno)); + } posix_spawn_file_actions_destroy(&tty); free(openrc_sh); @@ -554,8 +565,10 @@ svc_start_check(void) state = rc_service_state(applet); if (in_background) { - if (!(state & (RC_SERVICE_INACTIVE | RC_SERVICE_STOPPED))) + if (!(state & (RC_SERVICE_INACTIVE | RC_SERVICE_STOPPED))) { + rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); exit(EXIT_FAILURE); + } if (rc_yesno(getenv("IN_HOTPLUG"))) rc_service_mark(applet, RC_SERVICE_HOTPLUGGED); if (strcmp(runlevel, RC_LEVEL_SYSINIT) == 0) @@ -564,6 +577,7 @@ svc_start_check(void) if (state & RC_SERVICE_STARTED) { ewarn("WARNING: %s has already been started", applet); + rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); exit(EXIT_SUCCESS); } else if (state & RC_SERVICE_INACTIVE && !in_background) { ewarnx("WARNING: %s has already started, but is inactive", applet); @@ -572,17 +586,18 @@ svc_start_check(void) if (exclusive_fd == -1) exclusive_fd = svc_lock(applet, !deps); if (exclusive_fd == -1) { - if (errno != EWOULDBLOCK) + if (errno != EWOULDBLOCK) { + rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); eerrorx("%s: failed to acquire lock: %s", applet, strerror(errno)); - else if (state & RC_SERVICE_STOPPING) + } else if (state & RC_SERVICE_STOPPING) { ewarnx("WARNING: %s is stopping", applet); - else + } else { ewarnx("WARNING: %s is already starting", applet); + } } fcntl(exclusive_fd, F_SETFD, fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC); rc_service_mark(applet, RC_SERVICE_STARTING); - hook_out = RC_HOOK_SERVICE_START_OUT; rc_plugin_run(RC_HOOK_SERVICE_START_IN, applet); } @@ -602,8 +617,10 @@ svc_start_deps(void) if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT) depoptions |= RC_DEP_STRICT; - if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL)) + if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL)) { + rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); eerrorx("failed to load deptree"); + } if (!deptypes_b) setup_deptypes(); @@ -620,6 +637,7 @@ svc_start_deps(void) fprintf(stderr, "%s", svc->value); } fprintf(stderr, "\n"); + rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); exit(EXIT_FAILURE); } rc_stringlist_free(services); @@ -684,10 +702,12 @@ svc_start_deps(void) continue; } if (rc_stringlist_find(need_services, svc->value)) { - if (state & (RC_SERVICE_INACTIVE | RC_SERVICE_WASINACTIVE)) + if (state & (RC_SERVICE_INACTIVE | RC_SERVICE_WASINACTIVE)) { rc_stringlist_add(tmplist, svc->value); - else if (!TAILQ_FIRST(tmplist)) + } else if (!TAILQ_FIRST(tmplist)) { + rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); eerrorx("ERROR: cannot start %s as %s would not start", applet, svc->value); + } } } @@ -758,22 +778,22 @@ static void svc_start_real(void) if (ibsave) setenv("IN_BACKGROUND", ibsave, 1); - hook_out = RC_HOOK_SERVICE_START_DONE; rc_plugin_run(RC_HOOK_SERVICE_START_NOW, applet); skip_mark = false; started = (svc_exec("start") == 0); if (ibsave) unsetenv("IN_BACKGROUND"); - if (rc_service_state(applet) & RC_SERVICE_INACTIVE) + if (rc_service_state(applet) & RC_SERVICE_INACTIVE) { ewarnx("WARNING: %s has started, but is inactive", applet); - else if (!started) + } else if (!started) { + rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); eerrorx("ERROR: %s failed to start", applet); + } if (!skip_mark) rc_service_mark(applet, RC_SERVICE_STARTED); exclusive_fd = svc_unlock(applet, exclusive_fd); - hook_out = RC_HOOK_SERVICE_START_OUT; rc_plugin_run(RC_HOOK_SERVICE_START_DONE, applet); /* Now start any scheduled services */ @@ -799,7 +819,6 @@ static void svc_start_real(void) tmplist = NULL; } - hook_out = 0; rc_plugin_run(RC_HOOK_SERVICE_START_OUT, applet); } @@ -827,11 +846,15 @@ svc_stop_check(RC_SERVICE *state) { *state = rc_service_state(applet); - if (rc_runlevel_stopping() && *state & RC_SERVICE_FAILED) + if (rc_runlevel_stopping() && *state & RC_SERVICE_FAILED) { + rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); exit(EXIT_FAILURE); + } - if (in_background && !(*state & (RC_SERVICE_STARTED) && !(*state & RC_SERVICE_INACTIVE))) + if (in_background && !(*state & (RC_SERVICE_STARTED) && !(*state & RC_SERVICE_INACTIVE))) { + rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); exit(EXIT_FAILURE); + } if (*state & RC_SERVICE_STOPPED) { ewarn("WARNING: %s is already stopped", applet); @@ -841,6 +864,7 @@ svc_stop_check(RC_SERVICE *state) if (exclusive_fd == -1) exclusive_fd = svc_lock(applet, !deps); if (exclusive_fd == -1) { + rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); if (errno != EWOULDBLOCK) eerrorx("%s: failed to acquire lock: %s", applet, strerror(errno)); else if (*state & RC_SERVICE_STOPPING) @@ -850,7 +874,6 @@ svc_stop_check(RC_SERVICE *state) fcntl(exclusive_fd, F_SETFD, fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC); rc_service_mark(applet, RC_SERVICE_STOPPING); - hook_out = RC_HOOK_SERVICE_STOP_OUT; rc_plugin_run(RC_HOOK_SERVICE_STOP_IN, applet); if (!rc_runlevel_stopping()) { @@ -877,8 +900,10 @@ svc_stop_deps(RC_SERVICE state) if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT) depoptions |= RC_DEP_STRICT; - if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL)) + if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL)) { + rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); eerrorx("failed to load deptree"); + } if (!deptypes_m) setup_deptypes(); @@ -924,6 +949,7 @@ svc_stop_deps(RC_SERVICE state) continue; rc_service_mark(applet, RC_SERVICE_FAILED); } + rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); eerrorx("ERROR: cannot stop %s as %s is still up", applet, svc->value); } rc_stringlist_free(tmplist); @@ -969,22 +995,21 @@ svc_stop_real(void) if (ibsave) setenv("IN_BACKGROUND", ibsave, 1); - hook_out = RC_HOOK_SERVICE_STOP_DONE; rc_plugin_run(RC_HOOK_SERVICE_STOP_NOW, applet); skip_mark = false; stopped = (svc_exec("stop") == 0); if (ibsave) unsetenv("IN_BACKGROUND"); - if (!stopped) + if (!stopped) { + rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); eerrorx("ERROR: %s failed to stop", applet); + } if (!skip_mark) rc_service_mark(applet, in_background ? RC_SERVICE_INACTIVE : RC_SERVICE_STOPPED); - hook_out = RC_HOOK_SERVICE_STOP_OUT; rc_plugin_run(RC_HOOK_SERVICE_STOP_DONE, applet); - hook_out = 0; rc_plugin_run(RC_HOOK_SERVICE_STOP_OUT, applet); } @@ -1086,8 +1111,9 @@ resolve_deptype(void) if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT) depoptions |= RC_DEP_STRICT; - if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL)) + if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL)) { eerrorx("failed to load deptree"); + } tmplist = rc_stringlist_new(); rc_stringlist_add(tmplist, optarg); @@ -1147,8 +1173,9 @@ static int cmd_zap(void) { einfo("Manually resetting %s to stopped state", applet); - if (!rc_service_mark(applet, RC_SERVICE_STOPPED)) + if (!rc_service_mark(applet, RC_SERVICE_STOPPED)) { eerrorx("rc_service_mark: %s", strerror(errno)); + } unhotplug(); return 0; From db6e5da18dbb7dda1d90e00b951f44344d661926 Mon Sep 17 00:00:00 2001 From: Ferass El Hafidi Date: Wed, 13 May 2026 20:47:54 +0000 Subject: [PATCH 2/2] openrc: don't exec plugin hook in atexit Just like openrc-run, the cleanup function in rc.c checks if there are remaining hooks to run (by checking hook_out) and runs plugins. Calling exit() more than once like OpenRC does is undefined behavior. For more details, see the previous commit message. In practise this means, if a plugin (such as plymouth-openrc-plugin) is installed in a musl-based system, 2 scenarios may occur: * A service fails on boot: OpenRC is sort of stuck. If a GUI is running already, it may not look like much has happened, but things like shutting down won't work (would need to use `reboot -f` without going through openrc) * A service fails on shutdown: shutdown just hangs, have to forcibly poweroff. Remove all the handling of plugin hooks on exit, and instead explicitely run the OUT hooks on most failures, but *before* exit(). Signed-off-by: Ferass El Hafidi --- src/openrc/rc.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/openrc/rc.c b/src/openrc/rc.c index f7ab41a16..0216a6ab6 100644 --- a/src/openrc/rc.c +++ b/src/openrc/rc.c @@ -81,7 +81,6 @@ static RC_STRINGLIST *main_types_nw; static RC_STRINGLIST *main_types_nwua; static RC_DEPTREE *main_deptree; static char *runlevel; -static RC_HOOK hook_out; struct termios *termios_orig = NULL; @@ -117,9 +116,6 @@ cleanup(void) if (!rc_in_logger && !rc_in_plugin && applet && (strcmp(applet, "rc") == 0 || strcmp(applet, "openrc") == 0)) { - if (hook_out) - rc_plugin_run(hook_out, runlevel); - rc_plugin_unload(); if (termios_orig) { @@ -229,8 +225,10 @@ run_program(const char *prog) sigprocmask(SIG_SETMASK, &full, &old); pid = fork(); - if (pid == -1) + if (pid == -1) { + rc_plugin_run(RC_HOOK_RUNLEVEL_START_OUT, runlevel); eerrorx("%s: fork: %s", applet, strerror(errno)); + } if (pid == 0) { /* Restore default handlers */ sigaction(SIGCHLD, &sa, NULL); @@ -255,8 +253,10 @@ run_program(const char *prog) /* Unmask signals and wait for child */ sigprocmask(SIG_SETMASK, &old, NULL); - if (rc_waitpid(pid) == -1) + if (rc_waitpid(pid) == -1) { + rc_plugin_run(RC_HOOK_RUNLEVEL_START_OUT, runlevel); eerrorx("%s: failed to exec `%s'", applet, prog); + } } static void @@ -954,17 +954,20 @@ int main(int argc, char **argv) } else { rc_plugin_run(RC_HOOK_RUNLEVEL_STOP_IN, runlevel); } - hook_out = RC_HOOK_RUNLEVEL_STOP_OUT; /* Check if runlevel is valid if we're changing */ if (newlevel && strcmp(runlevel, newlevel) != 0 && !going_down) { - if (!rc_runlevel_exists(newlevel)) + if (!rc_runlevel_exists(newlevel)) { + rc_plugin_run(RC_HOOK_RUNLEVEL_STOP_OUT, runlevel); eerrorx("%s: is not a valid runlevel", newlevel); + } } /* Load our deptree */ - if ((main_deptree = _rc_deptree_load(0, ®en)) == NULL) + if ((main_deptree = _rc_deptree_load(0, ®en)) == NULL) { + rc_plugin_run(RC_HOOK_RUNLEVEL_STOP_OUT, runlevel); eerrorx("failed to load deptree"); + } if (faccessat(rc_dirfd(RC_DIR_SVCDIR), "clock-skewed", F_OK, 0) == 0) ewarn("WARNING: clock skew detected!"); @@ -972,8 +975,10 @@ int main(int argc, char **argv) /* Clean the failed services state dir */ clean_failed(); - if (mkdirat(rc_dirfd(RC_DIR_SVCDIR), "rc.stopping", 0755) != 0) + if (mkdirat(rc_dirfd(RC_DIR_SVCDIR), "rc.stopping", 0755) != 0) { + rc_plugin_run(RC_HOOK_RUNLEVEL_STOP_OUT, runlevel); eerrorx("%s: failed to create stopping dir '%s/rc.stopping': %s", applet, rc_svcdir(), strerror(errno)); + } /* Create a list of all services which we could stop (assuming * they won't be active in the new or current runlevel) including @@ -1052,7 +1057,6 @@ int main(int argc, char **argv) /* Notify the plugins we have finished */ rc_plugin_run(RC_HOOK_RUNLEVEL_STOP_OUT, going_down ? newlevel : runlevel); - hook_out = 0; unlinkat(rc_dirfd(RC_DIR_SVCDIR), "rc.stopping", AT_REMOVEDIR); @@ -1074,7 +1078,6 @@ int main(int argc, char **argv) mkdirat(rc_dirfd(RC_DIR_SVCDIR), "rc.starting", 0755); rc_plugin_run(RC_HOOK_RUNLEVEL_START_IN, runlevel); - hook_out = RC_HOOK_RUNLEVEL_START_OUT; /* Re-add our hotplugged services if they stopped */ if (main_hotplugged_services) @@ -1135,7 +1138,6 @@ int main(int argc, char **argv) #endif rc_plugin_run(RC_HOOK_RUNLEVEL_START_OUT, runlevel); - hook_out = 0; /* If we're in the boot runlevel and we regenerated our dependencies * we need to delete them so that they are regenerated again in the