aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Denys Vlasenko <vda.linux@googlemail.com>2019-12-03 13:48:55 +0100
committerGravatar Denys Vlasenko <vda.linux@googlemail.com>2019-12-03 14:05:32 +0100
commit356f23de20b48382f5a2c5db29dc4f6dc9d10289 (patch)
tree9e1658b4c6930c713f7fe417832a586a2b9e5a55
parent113c776f4d2ce4ead7c2d11a3ca62adeec9a2e34 (diff)
downloadbusybox-356f23de20b48382f5a2c5db29dc4f6dc9d10289.tar.gz
busybox-356f23de20b48382f5a2c5db29dc4f6dc9d10289.tar.bz2
init: improve handling of signals racing with each other
Before this change, a request to reboot could be "overwritten" by e.g. SIGHUP. function old new delta init_main 709 793 +84 packed_usage 33273 33337 +64 run_actions 109 117 +8 stop_handler 87 88 +1 check_delayed_sigs 340 335 -5 run 214 198 -16 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 4/2 up/down: 157/-21) Total: 136 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r--init/init.c278
1 files changed, 119 insertions, 159 deletions
diff --git a/init/init.c b/init/init.c
index db1d99add..28775a65c 100644
--- a/init/init.c
+++ b/init/init.c
@@ -210,6 +210,8 @@ struct globals {
#if !ENABLE_FEATURE_INIT_SYSLOG
const char *log_console;
#endif
+ sigset_t delayed_sigset;
+ struct timespec zero_ts;
} FIX_ALIASING;
#define G (*(struct globals*)bb_common_bufsiz1)
#define INIT_G() do { \
@@ -411,14 +413,8 @@ static int open_stdio_to_tty(const char* tty_name)
static void reset_sighandlers_and_unblock_sigs(void)
{
bb_signals(0
- + (1 << SIGUSR1)
- + (1 << SIGUSR2)
- + (1 << SIGTERM)
- + (1 << SIGQUIT)
- + (1 << SIGINT)
- + (1 << SIGHUP)
- + (1 << SIGTSTP)
- + (1 << SIGSTOP)
+ | (1 << SIGTSTP)
+ | (1 << SIGSTOP)
, SIG_DFL);
sigprocmask_allsigs(SIG_UNBLOCK);
}
@@ -484,16 +480,13 @@ static pid_t run(const struct init_action *a)
{
pid_t pid;
- /* Careful: don't be affected by a signal in vforked child */
- sigprocmask_allsigs(SIG_BLOCK);
if (BB_MMU && (a->action_type & ASKFIRST))
pid = fork();
else
pid = vfork();
- if (pid < 0)
- message(L_LOG | L_CONSOLE, "can't fork");
if (pid) {
- sigprocmask_allsigs(SIG_UNBLOCK);
+ if (pid < 0)
+ message(L_LOG | L_CONSOLE, "can't fork");
return pid; /* Parent or error */
}
@@ -587,9 +580,11 @@ static void waitfor(pid_t pid)
while (1) {
pid_t wpid = wait(NULL);
mark_terminated(wpid);
- /* Unsafe. SIGTSTP handler might have wait'ed it already */
- /*if (wpid == pid) break;*/
- /* More reliable: */
+ if (wpid == pid) /* this was the process we waited for */
+ break;
+ /* The above is not reliable enough: SIGTSTP handler might have
+ * wait'ed it already. Double check, exit if process is gone:
+ */
if (kill(pid, 0))
break;
}
@@ -798,23 +793,17 @@ static void run_shutdown_and_kill_processes(void)
* Delayed handlers just set a flag variable. The variable is checked
* in the main loop and acted upon.
*
- * halt/poweroff/reboot and restart have immediate handlers.
- * They only traverse linked list of struct action's, never modify it,
- * this should be safe to do even in signal handler. Also they
- * never return.
- *
* SIGSTOP and SIGTSTP have immediate handlers. They just wait
* for SIGCONT to happen.
*
+ * halt/poweroff/reboot and restart have delayed handlers.
+ *
* SIGHUP has a delayed handler, because modifying linked list
* of struct action's from a signal handler while it is manipulated
* by the program may be disastrous.
*
* Ctrl-Alt-Del has a delayed handler. Not a must, but allowing
* it to happen even somewhere inside "sysinit" would be a bit awkward.
- *
- * There is a tiny probability that SIGHUP and Ctrl-Alt-Del will collide
- * and only one will be remembered and acted upon.
*/
/* The SIGPWR/SIGUSR[12]/SIGTERM handler */
@@ -898,11 +887,9 @@ static void exec_restart_action(void)
*/
static void stop_handler(int sig UNUSED_PARAM)
{
- smallint saved_bb_got_signal;
- int saved_errno;
+ int saved_errno = errno;
- saved_bb_got_signal = bb_got_signal;
- saved_errno = errno;
+ bb_got_signal = 0;
signal(SIGCONT, record_signo);
while (1) {
@@ -916,12 +903,12 @@ static void stop_handler(int sig UNUSED_PARAM)
*/
wpid = wait_any_nohang(NULL);
mark_terminated(wpid);
- sleep(1);
+ if (wpid <= 0) /* no processes exited? sleep a bit */
+ sleep(1);
}
signal(SIGCONT, SIG_DFL);
errno = saved_errno;
- bb_got_signal = saved_bb_got_signal;
}
#if ENABLE_FEATURE_USE_INITTAB
@@ -986,43 +973,39 @@ static void reload_inittab(void)
}
#endif
-static int check_delayed_sigs(void)
+static void check_delayed_sigs(struct timespec *ts)
{
- int sigs_seen = 0;
+ int sig = sigtimedwait(&G.delayed_sigset, /* siginfo_t */ NULL, ts);
+ if (sig <= 0)
+ return;
- while (1) {
- smallint sig = bb_got_signal;
+ /* The signal "sig" was caught */
- if (!sig)
- return sigs_seen;
- bb_got_signal = 0;
- sigs_seen = 1;
#if ENABLE_FEATURE_USE_INITTAB
- if (sig == SIGHUP)
- reload_inittab();
+ if (sig == SIGHUP)
+ reload_inittab();
#endif
- if (sig == SIGINT)
- run_actions(CTRLALTDEL);
- if (sig == SIGQUIT) {
- exec_restart_action();
- /* returns only if no restart action defined */
- }
- if ((1 << sig) & (0
+ if (sig == SIGINT)
+ run_actions(CTRLALTDEL);
+ if (sig == SIGQUIT) {
+ exec_restart_action();
+ /* returns only if no restart action defined */
+ }
+ if ((1 << sig) & (0
#ifdef SIGPWR
- + (1 << SIGPWR)
+ | (1 << SIGPWR)
#endif
- + (1 << SIGUSR1)
- + (1 << SIGUSR2)
- + (1 << SIGTERM)
- )) {
- halt_reboot_pwoff(sig);
- }
+ | (1 << SIGUSR1)
+ | (1 << SIGUSR2)
+ | (1 << SIGTERM)
+ )) {
+ halt_reboot_pwoff(sig);
}
+ /* if (sig == SIGCHLD) do nothing */
}
#if DEBUG_SEGV_HANDLER
-static
-void handle_sigsegv(int sig, siginfo_t *info, void *ucontext)
+static void handle_sigsegv(int sig, siginfo_t *info, void *ucontext)
{
long ip;
ucontext_t *uc;
@@ -1049,50 +1032,62 @@ void handle_sigsegv(int sig, siginfo_t *info, void *ucontext)
static void sleep_much(void)
{
- sleep(30 * 24*60*60);
+ sleep(30 * 24*60*60);
}
int init_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int init_main(int argc UNUSED_PARAM, char **argv)
{
+ struct sigaction sa;
+
INIT_G();
+ /* Some users send poweroff signals to init VERY early.
+ * To handle this, mask signals early.
+ */
+ /* sigemptyset(&G.delayed_sigset); - done by INIT_G() */
+ sigaddset(&G.delayed_sigset, SIGINT); /* Ctrl-Alt-Del */
+ sigaddset(&G.delayed_sigset, SIGQUIT); /* re-exec another init */
+#ifdef SIGPWR
+ sigaddset(&G.delayed_sigset, SIGPWR); /* halt */
+#endif
+ sigaddset(&G.delayed_sigset, SIGUSR1); /* halt */
+ sigaddset(&G.delayed_sigset, SIGTERM); /* reboot */
+ sigaddset(&G.delayed_sigset, SIGUSR2); /* poweroff */
+#if ENABLE_FEATURE_USE_INITTAB
+ sigaddset(&G.delayed_sigset, SIGHUP); /* reread /etc/inittab */
+#endif
+ sigaddset(&G.delayed_sigset, SIGCHLD); /* make sigtimedwait() exit on SIGCHLD */
+ sigprocmask(SIG_BLOCK, &G.delayed_sigset, NULL);
+
+#if DEBUG_SEGV_HANDLER
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_sigaction = handle_sigsegv;
+ sa.sa_flags = SA_SIGINFO;
+ sigaction_set(SIGSEGV, &sa);
+ sigaction_set(SIGILL, &sa);
+ sigaction_set(SIGFPE, &sa);
+ sigaction_set(SIGBUS, &sa);
+#endif
+
if (argv[1] && strcmp(argv[1], "-q") == 0) {
return kill(1, SIGHUP);
}
-#if DEBUG_SEGV_HANDLER
- {
- struct sigaction sa;
- memset(&sa, 0, sizeof(sa));
- sa.sa_sigaction = handle_sigsegv;
- sa.sa_flags = SA_SIGINFO;
- sigaction(SIGSEGV, &sa, NULL);
- sigaction(SIGILL, &sa, NULL);
- sigaction(SIGFPE, &sa, NULL);
- sigaction(SIGBUS, &sa, NULL);
+#if !DEBUG_INIT
+ /* Expect to be invoked as init with PID=1 or be invoked as linuxrc */
+ if (getpid() != 1
+ && (!ENABLE_LINUXRC || applet_name[0] != 'l') /* not linuxrc? */
+ ) {
+ bb_simple_error_msg_and_die("must be run as PID 1");
}
-#endif
-
- if (!DEBUG_INIT) {
- /* Some users send poweroff signals to init VERY early.
- * To handle this, mask signals early,
- * and unmask them only after signal handlers are installed.
- */
- sigprocmask_allsigs(SIG_BLOCK);
- /* Expect to be invoked as init with PID=1 or be invoked as linuxrc */
- if (getpid() != 1
- && (!ENABLE_LINUXRC || applet_name[0] != 'l') /* not linuxrc? */
- ) {
- bb_simple_error_msg_and_die("must be run as PID 1");
- }
-#ifdef RB_DISABLE_CAD
- /* Turn off rebooting via CTL-ALT-DEL - we get a
- * SIGINT on CAD so we can shut things down gracefully... */
- reboot(RB_DISABLE_CAD); /* misnomer */
+# ifdef RB_DISABLE_CAD
+ /* Turn off rebooting via CTL-ALT-DEL - we get a
+ * SIGINT on CAD so we can shut things down gracefully... */
+ reboot(RB_DISABLE_CAD); /* misnomer */
+# endif
#endif
- }
/* If, say, xmalloc would ever die, we don't want to oops kernel
* by exiting.
@@ -1156,106 +1151,65 @@ int init_main(int argc UNUSED_PARAM, char **argv)
}
#endif
- if (ENABLE_FEATURE_INIT_MODIFY_CMDLINE) {
- /* Make the command line just say "init" - that's all, nothing else */
- strncpy(argv[0], "init", strlen(argv[0]));
- /* Wipe argv[1]-argv[N] so they don't clutter the ps listing */
- while (*++argv)
- nuke_str(*argv);
- }
-
- /* Set up signal handlers */
- if (!DEBUG_INIT) {
- struct sigaction sa;
-
- /* Stop handler must allow only SIGCONT inside itself */
- memset(&sa, 0, sizeof(sa));
- sigfillset(&sa.sa_mask);
- sigdelset(&sa.sa_mask, SIGCONT);
- sa.sa_handler = stop_handler;
- /* NB: sa_flags doesn't have SA_RESTART.
- * It must be able to interrupt wait().
- */
- sigaction_set(SIGTSTP, &sa); /* pause */
- /* Does not work as intended, at least in 2.6.20.
- * SIGSTOP is simply ignored by init:
- */
- sigaction_set(SIGSTOP, &sa); /* pause */
-
- /* These signals must interrupt wait(),
- * setting handler without SA_RESTART flag.
- */
- bb_signals_recursive_norestart(0
- + (1 << SIGINT) /* Ctrl-Alt-Del */
- + (1 << SIGQUIT) /* re-exec another init */
-#ifdef SIGPWR
- + (1 << SIGPWR) /* halt */
-#endif
- + (1 << SIGUSR1) /* halt */
- + (1 << SIGTERM) /* reboot */
- + (1 << SIGUSR2) /* poweroff */
-#if ENABLE_FEATURE_USE_INITTAB
- + (1 << SIGHUP) /* reread /etc/inittab */
+#if ENABLE_FEATURE_INIT_MODIFY_CMDLINE
+ /* Make the command line just say "init" - that's all, nothing else */
+ strncpy(argv[0], "init", strlen(argv[0]));
+ /* Wipe argv[1]-argv[N] so they don't clutter the ps listing */
+ while (*++argv)
+ nuke_str(*argv);
#endif
- , record_signo);
- sigprocmask_allsigs(SIG_UNBLOCK);
- }
+ /* Set up STOP signal handlers */
+ /* Stop handler must allow only SIGCONT inside itself */
+ memset(&sa, 0, sizeof(sa));
+ sigfillset(&sa.sa_mask);
+ sigdelset(&sa.sa_mask, SIGCONT);
+ sa.sa_handler = stop_handler;
+ sa.sa_flags = SA_RESTART;
+ sigaction_set(SIGTSTP, &sa); /* pause */
+ /* Does not work as intended, at least in 2.6.20.
+ * SIGSTOP is simply ignored by init
+ * (NB: behavior might differ under strace):
+ */
+ sigaction_set(SIGSTOP, &sa); /* pause */
/* Now run everything that needs to be run */
/* First run the sysinit command */
run_actions(SYSINIT);
- check_delayed_sigs();
+ check_delayed_sigs(&G.zero_ts);
/* Next run anything that wants to block */
run_actions(WAIT);
- check_delayed_sigs();
+ check_delayed_sigs(&G.zero_ts);
/* Next run anything to be run only once */
run_actions(ONCE);
- /* Now run the looping stuff for the rest of forever.
- */
+ /* Now run the looping stuff for the rest of forever */
while (1) {
- int maybe_WNOHANG;
-
- maybe_WNOHANG = check_delayed_sigs();
-
/* (Re)run the respawn/askfirst stuff */
run_actions(RESPAWN | ASKFIRST);
- maybe_WNOHANG |= check_delayed_sigs();
- /* Don't consume all CPU time - sleep a bit */
- sleep(1);
- maybe_WNOHANG |= check_delayed_sigs();
-
- /* Wait for any child process(es) to exit.
- *
- * If check_delayed_sigs above reported that a signal
- * was caught, wait will be nonblocking. This ensures
- * that if SIGHUP has reloaded inittab, respawn and askfirst
- * actions will not be delayed until next child death.
- */
- if (maybe_WNOHANG)
- maybe_WNOHANG = WNOHANG;
+ /* Wait for any signal (typically it's SIGCHLD) */
+ check_delayed_sigs(NULL); /* NULL timespec makes it wait */
+
+ /* Wait for any child process(es) to exit */
while (1) {
pid_t wpid;
struct init_action *a;
- /* If signals happen _in_ the wait, they interrupt it,
- * bb_signals_recursive_norestart set them up that way
- */
- wpid = waitpid(-1, NULL, maybe_WNOHANG);
+ wpid = waitpid(-1, NULL, WNOHANG);
if (wpid <= 0)
break;
a = mark_terminated(wpid);
if (a) {
- message(L_LOG, "process '%s' (pid %d) exited. "
+ message(L_LOG, "process '%s' (pid %u) exited. "
"Scheduling for restart.",
- a->command, wpid);
+ a->command, (unsigned)wpid);
}
- /* See if anyone else is waiting to be reaped */
- maybe_WNOHANG = WNOHANG;
}
+
+ /* Don't consume all CPU time - sleep a bit */
+ sleep(1);
} /* while (1) */
}
@@ -1268,11 +1222,17 @@ int init_main(int argc UNUSED_PARAM, char **argv)
//usage: "Init is the first process started during boot. It never exits."
//usage: IF_FEATURE_USE_INITTAB(
//usage: "\n""It (re)spawns children according to /etc/inittab."
+//usage: "\n""Signals:"
+//usage: "\n""HUP: reload /etc/inittab"
//usage: )
//usage: IF_NOT_FEATURE_USE_INITTAB(
//usage: "\n""This version of init doesn't use /etc/inittab,"
//usage: "\n""has fixed set of processed to run."
+//usage: "\n""Signals:"
//usage: )
+//usage: "\n""TSTP: stop respawning until CONT"
+//usage: "\n""QUIT: re-exec another init"
+//usage: "\n""USR1/TERM/USR2/INT: run halt/reboot/poweroff/Ctrl-Alt-Del script"
//usage:
//usage:#define init_notes_usage
//usage: "This version of init is designed to be run only by the kernel.\n"