mirror of
https://git.busybox.net/busybox.git
synced 2024-11-23 13:43:28 +08:00
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:
parent
113c776f4d
commit
356f23de20
280
init/init.c
280
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
|
||||
#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"
|
||||
|
Loading…
Reference in New Issue
Block a user