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
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
#ifdef SIGPWR
+ (1 << SIGPWR)
#endif
+ (1 << SIGUSR1)
+ (1 << SIGUSR2)
+ (1 << SIGTERM)
)) {
halt_reboot_pwoff(sig);
}
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)
#endif
| (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 */
#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
+ (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 */
/* 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 signal (typically it's SIGCHLD) */
check_delayed_sigs(NULL); /* NULL timespec makes it wait */
/* 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 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"