daemon: move signal setup into a function

No functional change intended.

Reviewed by:	kevans
This commit is contained in:
Ihor Antonov 2023-03-20 23:40:04 -05:00 committed by Kyle Evans
parent 535946ce11
commit 9ee1faeeba

View File

@ -83,6 +83,7 @@ struct daemon_state {
bool log_reopen; bool log_reopen;
}; };
static void setup_signals(struct daemon_state *);
static void restrict_process(const char *); static void restrict_process(const char *);
static void handle_term(int); static void handle_term(int);
static void handle_chld(int); static void handle_chld(int);
@ -168,10 +169,40 @@ main(int argc, char *argv[])
sigset_t mask_susp; sigset_t mask_susp;
daemon_state_init(&state); daemon_state_init(&state);
/*
* Signal handling logic:
*
* - SIGTERM is masked while there is no child.
*
* - SIGCHLD is masked while reading from the pipe. SIGTERM has to be
* caught, to avoid indefinite blocking on read().
*
* - Both SIGCHLD and SIGTERM are masked before calling sigsuspend()
* to avoid racing.
*
* - After SIGTERM is recieved and propagated to the child there are
* several options on what to do next:
* - read until EOF
* - read until EOF but only for a while
* - bail immediately
* Currently the third option is used, because otherwise there is no
* guarantee that read() won't block indefinitely if the child refuses
* to depart. To handle the second option, a different approach
* would be needed (procctl()?).
*
* - Child's exit might be detected by receiveing EOF from the pipe.
* But the child might have closed its stdout and stderr, so deamon
* must wait for the SIGCHLD to ensure that the child is actually gone.
*/
sigemptyset(&mask_susp); sigemptyset(&mask_susp);
sigemptyset(&mask_read); sigemptyset(&mask_read);
sigemptyset(&mask_term); sigemptyset(&mask_term);
sigemptyset(&mask_orig); sigemptyset(&mask_orig);
sigaddset(&mask_susp, SIGTERM);
sigaddset(&mask_susp, SIGCHLD);
sigaddset(&mask_term, SIGTERM);
sigaddset(&mask_read, SIGCHLD);
/* /*
* Supervision mode is enabled if one of the following options are used: * Supervision mode is enabled if one of the following options are used:
@ -312,57 +343,20 @@ main(int argc, char *argv[])
pidfile_write(state.parent_pidfh); pidfile_write(state.parent_pidfh);
if (state.supervision_enabled) { if (state.supervision_enabled) {
struct sigaction act_term = { 0 };
struct sigaction act_chld = { 0 };
struct sigaction act_hup = { 0 };
/* Avoid PID racing with SIGCHLD and SIGTERM. */
act_term.sa_handler = handle_term;
sigemptyset(&act_term.sa_mask);
sigaddset(&act_term.sa_mask, SIGCHLD);
act_chld.sa_handler = handle_chld;
sigemptyset(&act_chld.sa_mask);
sigaddset(&act_chld.sa_mask, SIGTERM);
act_hup.sa_handler = handle_hup;
sigemptyset(&act_hup.sa_mask);
/* Block these when avoiding racing before sigsuspend(). */
sigaddset(&mask_susp, SIGTERM);
sigaddset(&mask_susp, SIGCHLD);
/* Block SIGTERM when we lack a valid child PID. */
sigaddset(&mask_term, SIGTERM);
/*
* When reading, we wish to avoid SIGCHLD. SIGTERM
* has to be caught, otherwise we'll be stuck until
* the read() returns - if it returns.
*/
sigaddset(&mask_read, SIGCHLD);
/* Block SIGTERM to avoid racing until we have forked. */ /* Block SIGTERM to avoid racing until we have forked. */
if (sigprocmask(SIG_BLOCK, &mask_term, &mask_orig)) { if (sigprocmask(SIG_BLOCK, &mask_term, &mask_orig)) {
warn("sigprocmask"); warn("sigprocmask");
daemon_terminate(&state); daemon_terminate(&state);
} }
if (sigaction(SIGTERM, &act_term, NULL) == -1) {
warn("sigaction"); setup_signals(&state);
daemon_terminate(&state);
}
if (sigaction(SIGCHLD, &act_chld, NULL) == -1) {
warn("sigaction");
daemon_terminate(&state);
}
/* /*
* Try to protect against pageout kill. Ignore the * Try to protect against pageout kill. Ignore the
* error, madvise(2) will fail only if a process does * error, madvise(2) will fail only if a process does
* not have superuser privileges. * not have superuser privileges.
*/ */
(void)madvise(NULL, 0, MADV_PROTECT); (void)madvise(NULL, 0, MADV_PROTECT);
if (state.log_reopen && state.output_fd >= 0 &&
sigaction(SIGHUP, &act_hup, NULL) == -1) {
warn("sigaction");
daemon_terminate(&state);
}
restart: restart:
if (pipe(state.pipe_fd)) { if (pipe(state.pipe_fd)) {
err(1, "pipe"); err(1, "pipe");
@ -419,9 +413,9 @@ main(int argc, char *argv[])
/* /*
* else: pid > 0 * else: pid > 0
* fork succeeded, this is the parent branch, this can only happen when * fork succeeded, this is the parent branch, this can only happen when
* supervision is enabled * supervision is enabled.
* *
* Unblock SIGTERM after we know we have a valid child PID to signal. * Unblock SIGTERM - now there is a valid child PID to signal to.
*/ */
if (sigprocmask(SIG_UNBLOCK, &mask_term, NULL)) { if (sigprocmask(SIG_UNBLOCK, &mask_term, NULL)) {
warn("sigprocmask"); warn("sigprocmask");
@ -431,28 +425,7 @@ main(int argc, char *argv[])
state.pipe_fd[1] = -1; state.pipe_fd[1] = -1;
setproctitle("%s[%d]", state.title, (int)pid); setproctitle("%s[%d]", state.title, (int)pid);
/*
* As we have closed the write end of pipe for parent process,
* we might detect the child's exit by reading EOF. The child
* might have closed its stdout and stderr, so we must wait for
* the SIGCHLD to ensure that the process is actually gone.
*/
for (;;) { for (;;) {
/*
* We block SIGCHLD when listening, but SIGTERM we accept
* so the read() won't block if we wish to depart.
*
* Upon receiving SIGTERM, we have several options after
* sending the SIGTERM to our child:
* - read until EOF
* - read until EOF but only for a while
* - bail immediately
*
* We go for the third, as otherwise we have no guarantee
* that we won't block indefinitely if the child refuses
* to depart. To handle the second option, a different
* approach would be needed (procctl()?)
*/
if (child_gone && state.child_eof) { if (child_gone && state.child_eof) {
break; break;
} }
@ -516,6 +489,49 @@ daemon_sleep(time_t secs, long nsecs)
} }
} }
/*
* Setup SIGTERM, SIGCHLD and SIGHUP handlers.
* To avoid racing SIGCHLD with SIGTERM corresponding
* signal handlers mask the other signal.
*/
static void
setup_signals(struct daemon_state *state)
{
struct sigaction act_term = { 0 };
struct sigaction act_chld = { 0 };
struct sigaction act_hup = { 0 };
/* Setup SIGTERM */
act_term.sa_handler = handle_term;
sigemptyset(&act_term.sa_mask);
sigaddset(&act_term.sa_mask, SIGCHLD);
if (sigaction(SIGTERM, &act_term, NULL) == -1) {
warn("sigaction");
daemon_terminate(state);
}
/* Setup SIGCHLD */
act_chld.sa_handler = handle_chld;
sigemptyset(&act_chld.sa_mask);
sigaddset(&act_chld.sa_mask, SIGTERM);
if (sigaction(SIGCHLD, &act_chld, NULL) == -1) {
warn("sigaction");
daemon_terminate(state);
}
/* Setup SIGHUP if configured */
if (!state->log_reopen || state->output_fd < 0) {
return;
}
act_hup.sa_handler = handle_hup;
sigemptyset(&act_hup.sa_mask);
if (sigaction(SIGHUP, &act_hup, NULL) == -1) {
warn("sigaction");
daemon_terminate(state);
}
}
static void static void
open_pid_files(struct daemon_state *state) open_pid_files(struct daemon_state *state)
{ {