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>
This commit is contained in:
Denys Vlasenko 2019-12-03 13:48:55 +01:00
parent 113c776f4d
commit 356f23de20

View File

@ -210,6 +210,8 @@ struct globals {
#if !ENABLE_FEATURE_INIT_SYSLOG #if !ENABLE_FEATURE_INIT_SYSLOG
const char *log_console; const char *log_console;
#endif #endif
sigset_t delayed_sigset;
struct timespec zero_ts;
} FIX_ALIASING; } FIX_ALIASING;
#define G (*(struct globals*)bb_common_bufsiz1) #define G (*(struct globals*)bb_common_bufsiz1)
#define INIT_G() do { \ #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) static void reset_sighandlers_and_unblock_sigs(void)
{ {
bb_signals(0 bb_signals(0
+ (1 << SIGUSR1) | (1 << SIGTSTP)
+ (1 << SIGUSR2) | (1 << SIGSTOP)
+ (1 << SIGTERM)
+ (1 << SIGQUIT)
+ (1 << SIGINT)
+ (1 << SIGHUP)
+ (1 << SIGTSTP)
+ (1 << SIGSTOP)
, SIG_DFL); , SIG_DFL);
sigprocmask_allsigs(SIG_UNBLOCK); sigprocmask_allsigs(SIG_UNBLOCK);
} }
@ -484,16 +480,13 @@ static pid_t run(const struct init_action *a)
{ {
pid_t pid; 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)) if (BB_MMU && (a->action_type & ASKFIRST))
pid = fork(); pid = fork();
else else
pid = vfork(); pid = vfork();
if (pid < 0)
message(L_LOG | L_CONSOLE, "can't fork");
if (pid) { if (pid) {
sigprocmask_allsigs(SIG_UNBLOCK); if (pid < 0)
message(L_LOG | L_CONSOLE, "can't fork");
return pid; /* Parent or error */ return pid; /* Parent or error */
} }
@ -587,9 +580,11 @@ static void waitfor(pid_t pid)
while (1) { while (1) {
pid_t wpid = wait(NULL); pid_t wpid = wait(NULL);
mark_terminated(wpid); mark_terminated(wpid);
/* Unsafe. SIGTSTP handler might have wait'ed it already */ if (wpid == pid) /* this was the process we waited for */
/*if (wpid == pid) break;*/ break;
/* More reliable: */ /* 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)) if (kill(pid, 0))
break; break;
} }
@ -798,23 +793,17 @@ static void run_shutdown_and_kill_processes(void)
* Delayed handlers just set a flag variable. The variable is checked * Delayed handlers just set a flag variable. The variable is checked
* in the main loop and acted upon. * 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 * SIGSTOP and SIGTSTP have immediate handlers. They just wait
* for SIGCONT to happen. * for SIGCONT to happen.
* *
* halt/poweroff/reboot and restart have delayed handlers.
*
* SIGHUP has a delayed handler, because modifying linked list * SIGHUP has a delayed handler, because modifying linked list
* of struct action's from a signal handler while it is manipulated * of struct action's from a signal handler while it is manipulated
* by the program may be disastrous. * by the program may be disastrous.
* *
* Ctrl-Alt-Del has a delayed handler. Not a must, but allowing * Ctrl-Alt-Del has a delayed handler. Not a must, but allowing
* it to happen even somewhere inside "sysinit" would be a bit awkward. * 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 */ /* The SIGPWR/SIGUSR[12]/SIGTERM handler */
@ -898,11 +887,9 @@ static void exec_restart_action(void)
*/ */
static void stop_handler(int sig UNUSED_PARAM) static void stop_handler(int sig UNUSED_PARAM)
{ {
smallint saved_bb_got_signal; int saved_errno = errno;
int saved_errno;
saved_bb_got_signal = bb_got_signal; bb_got_signal = 0;
saved_errno = errno;
signal(SIGCONT, record_signo); signal(SIGCONT, record_signo);
while (1) { while (1) {
@ -916,12 +903,12 @@ static void stop_handler(int sig UNUSED_PARAM)
*/ */
wpid = wait_any_nohang(NULL); wpid = wait_any_nohang(NULL);
mark_terminated(wpid); mark_terminated(wpid);
sleep(1); if (wpid <= 0) /* no processes exited? sleep a bit */
sleep(1);
} }
signal(SIGCONT, SIG_DFL); signal(SIGCONT, SIG_DFL);
errno = saved_errno; errno = saved_errno;
bb_got_signal = saved_bb_got_signal;
} }
#if ENABLE_FEATURE_USE_INITTAB #if ENABLE_FEATURE_USE_INITTAB
@ -986,43 +973,39 @@ static void reload_inittab(void)
} }
#endif #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) { /* The signal "sig" was caught */
smallint sig = bb_got_signal;
if (!sig)
return sigs_seen;
bb_got_signal = 0;
sigs_seen = 1;
#if ENABLE_FEATURE_USE_INITTAB #if ENABLE_FEATURE_USE_INITTAB
if (sig == SIGHUP) if (sig == SIGHUP)
reload_inittab(); reload_inittab();
#endif #endif
if (sig == SIGINT) if (sig == SIGINT)
run_actions(CTRLALTDEL); run_actions(CTRLALTDEL);
if (sig == SIGQUIT) { if (sig == SIGQUIT) {
exec_restart_action(); exec_restart_action();
/* returns only if no restart action defined */ /* returns only if no restart action defined */
}
if ((1 << sig) & (0
#ifdef SIGPWR
+ (1 << SIGPWR)
#endif
+ (1 << SIGUSR1)
+ (1 << SIGUSR2)
+ (1 << SIGTERM)
)) {
halt_reboot_pwoff(sig);
}
} }
if ((1 << sig) & (0
#ifdef SIGPWR
| (1 << SIGPWR)
#endif
| (1 << SIGUSR1)
| (1 << SIGUSR2)
| (1 << SIGTERM)
)) {
halt_reboot_pwoff(sig);
}
/* if (sig == SIGCHLD) do nothing */
} }
#if DEBUG_SEGV_HANDLER #if DEBUG_SEGV_HANDLER
static static void handle_sigsegv(int sig, siginfo_t *info, void *ucontext)
void handle_sigsegv(int sig, siginfo_t *info, void *ucontext)
{ {
long ip; long ip;
ucontext_t *uc; ucontext_t *uc;
@ -1049,50 +1032,62 @@ void handle_sigsegv(int sig, siginfo_t *info, void *ucontext)
static void sleep_much(void) 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, char **argv) MAIN_EXTERNALLY_VISIBLE;
int init_main(int argc UNUSED_PARAM, char **argv) int init_main(int argc UNUSED_PARAM, char **argv)
{ {
struct sigaction sa;
INIT_G(); 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) { if (argv[1] && strcmp(argv[1], "-q") == 0) {
return kill(1, SIGHUP); return kill(1, SIGHUP);
} }
#if DEBUG_SEGV_HANDLER #if !DEBUG_INIT
{ /* Expect to be invoked as init with PID=1 or be invoked as linuxrc */
struct sigaction sa; if (getpid() != 1
memset(&sa, 0, sizeof(sa)); && (!ENABLE_LINUXRC || applet_name[0] != 'l') /* not linuxrc? */
sa.sa_sigaction = handle_sigsegv; ) {
sa.sa_flags = SA_SIGINFO; bb_simple_error_msg_and_die("must be run as PID 1");
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGILL, &sa, NULL);
sigaction(SIGFPE, &sa, NULL);
sigaction(SIGBUS, &sa, NULL);
} }
#endif
if (!DEBUG_INIT) { # ifdef RB_DISABLE_CAD
/* Some users send poweroff signals to init VERY early. /* Turn off rebooting via CTL-ALT-DEL - we get a
* To handle this, mask signals early, * SIGINT on CAD so we can shut things down gracefully... */
* and unmask them only after signal handlers are installed. reboot(RB_DISABLE_CAD); /* misnomer */
*/ # endif
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 */
#endif #endif
}
/* If, say, xmalloc would ever die, we don't want to oops kernel /* If, say, xmalloc would ever die, we don't want to oops kernel
* by exiting. * by exiting.
@ -1156,106 +1151,65 @@ int init_main(int argc UNUSED_PARAM, char **argv)
} }
#endif #endif
if (ENABLE_FEATURE_INIT_MODIFY_CMDLINE) { #if ENABLE_FEATURE_INIT_MODIFY_CMDLINE
/* Make the command line just say "init" - that's all, nothing else */ /* Make the command line just say "init" - that's all, nothing else */
strncpy(argv[0], "init", strlen(argv[0])); strncpy(argv[0], "init", strlen(argv[0]));
/* Wipe argv[1]-argv[N] so they don't clutter the ps listing */ /* Wipe argv[1]-argv[N] so they don't clutter the ps listing */
while (*++argv) while (*++argv)
nuke_str(*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 #endif
+ (1 << SIGUSR1) /* halt */
+ (1 << SIGTERM) /* reboot */
+ (1 << SIGUSR2) /* poweroff */
#if ENABLE_FEATURE_USE_INITTAB
+ (1 << SIGHUP) /* reread /etc/inittab */
#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 */ /* Now run everything that needs to be run */
/* First run the sysinit command */ /* First run the sysinit command */
run_actions(SYSINIT); run_actions(SYSINIT);
check_delayed_sigs(); check_delayed_sigs(&G.zero_ts);
/* Next run anything that wants to block */ /* Next run anything that wants to block */
run_actions(WAIT); run_actions(WAIT);
check_delayed_sigs(); check_delayed_sigs(&G.zero_ts);
/* Next run anything to be run only once */ /* Next run anything to be run only once */
run_actions(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) { while (1) {
int maybe_WNOHANG;
maybe_WNOHANG = check_delayed_sigs();
/* (Re)run the respawn/askfirst stuff */ /* (Re)run the respawn/askfirst stuff */
run_actions(RESPAWN | ASKFIRST); run_actions(RESPAWN | ASKFIRST);
maybe_WNOHANG |= check_delayed_sigs();
/* Don't consume all CPU time - sleep a bit */ /* Wait for any signal (typically it's SIGCHLD) */
sleep(1); check_delayed_sigs(NULL); /* NULL timespec makes it wait */
maybe_WNOHANG |= check_delayed_sigs();
/* Wait for any child process(es) to exit. /* 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;
while (1) { while (1) {
pid_t wpid; pid_t wpid;
struct init_action *a; struct init_action *a;
/* If signals happen _in_ the wait, they interrupt it, wpid = waitpid(-1, NULL, WNOHANG);
* bb_signals_recursive_norestart set them up that way
*/
wpid = waitpid(-1, NULL, maybe_WNOHANG);
if (wpid <= 0) if (wpid <= 0)
break; break;
a = mark_terminated(wpid); a = mark_terminated(wpid);
if (a) { if (a) {
message(L_LOG, "process '%s' (pid %d) exited. " message(L_LOG, "process '%s' (pid %u) exited. "
"Scheduling for restart.", "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) */ } /* 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: "Init is the first process started during boot. It never exits."
//usage: IF_FEATURE_USE_INITTAB( //usage: IF_FEATURE_USE_INITTAB(
//usage: "\n""It (re)spawns children according to /etc/inittab." //usage: "\n""It (re)spawns children according to /etc/inittab."
//usage: "\n""Signals:"
//usage: "\n""HUP: reload /etc/inittab"
//usage: ) //usage: )
//usage: IF_NOT_FEATURE_USE_INITTAB( //usage: IF_NOT_FEATURE_USE_INITTAB(
//usage: "\n""This version of init doesn't use /etc/inittab," //usage: "\n""This version of init doesn't use /etc/inittab,"
//usage: "\n""has fixed set of processed to run." //usage: "\n""has fixed set of processed to run."
//usage: "\n""Signals:"
//usage: ) //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:
//usage:#define init_notes_usage //usage:#define init_notes_usage
//usage: "This version of init is designed to be run only by the kernel.\n" //usage: "This version of init is designed to be run only by the kernel.\n"