Skip to content

Commit

Permalink
daemon: decouple init logic from main loop
Browse files Browse the repository at this point in the history
main() func contained both initialization and main loop logic.
This made certain operations like restarting problematic and
required dirty hacks in form of goto jumps.

This commit moves the main loop logic into daemon_eventloop(),
cleans up main, and makes restart logic clear: daemon_mainloop()
is run in a loop with a restart condition checked at the end.
  • Loading branch information
ngortheone committed Mar 22, 2023
1 parent ed52baf commit ac0a64b
Showing 1 changed file with 110 additions and 85 deletions.
195 changes: 110 additions & 85 deletions usr.sbin/daemon/daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ __FBSDID("$FreeBSD$");
#define LBUF_SIZE 4096

struct daemon_state {
sigset_t mask_orig;
sigset_t mask_read;
sigset_t mask_term;
sigset_t mask_susp;
int pipe_fd[2];
char **argv;
const char *child_pidfile;
const char *parent_pidfile;
const char *output_filename;
Expand Down Expand Up @@ -96,6 +101,7 @@ static void open_pid_files(struct daemon_state *);
static void do_output(const unsigned char *, size_t, struct daemon_state *);
static void daemon_sleep(time_t, long);
static void daemon_state_init(struct daemon_state *);
static void daemon_eventloop(struct daemon_state *);
static void daemon_terminate(struct daemon_state *);

static volatile sig_atomic_t terminate = 0;
Expand Down Expand Up @@ -163,46 +169,9 @@ main(int argc, char *argv[])
char *p = NULL;
int ch = 0;
struct daemon_state state;
sigset_t mask_orig;
sigset_t mask_read;
sigset_t mask_term;
sigset_t mask_susp;

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_read);
sigemptyset(&mask_term);
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:
Expand Down Expand Up @@ -309,6 +278,7 @@ main(int argc, char *argv[])
}
argc -= optind;
argv += optind;
state.argv = argv;

if (argc == 0) {
usage(1);
Expand Down Expand Up @@ -343,8 +313,9 @@ main(int argc, char *argv[])
pidfile_write(state.parent_pidfh);

if (state.supervision_enabled) {
/* Block SIGTERM to avoid racing until we have forked. */
if (sigprocmask(SIG_BLOCK, &mask_term, &mask_orig)) {

/* Block SIGTERM to avoid racing until the child is spawned. */
if (sigprocmask(SIG_BLOCK, &state.mask_term, &state.mask_orig)) {
warn("sigprocmask");
daemon_terminate(&state);
}
Expand All @@ -357,8 +328,50 @@ main(int argc, char *argv[])
* not have superuser privileges.
*/
(void)madvise(NULL, 0, MADV_PROTECT);
restart:
if (pipe(state.pipe_fd)) {
}
do {
daemon_eventloop(&state);
close(state.pipe_fd[0]);
state.pipe_fd[0] = -1;
} while (state.restart_enabled && !terminate);

daemon_terminate(&state);
}


/*
* Main event loop: fork the child and watch for events.
* In legacy mode simply execve into the target process.
*
* 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.
*/
static void
daemon_eventloop(struct daemon_state *state) {

if (state->supervision_enabled) {
if (pipe(state->pipe_fd)) {
err(1, "pipe");
}
/*
Expand All @@ -371,43 +384,43 @@ main(int argc, char *argv[])
/* fork failed, this can only happen when supervision is enabled */
if (pid == -1) {
warn("fork");
daemon_terminate(&state);
daemon_terminate(state);
}

/* fork succeeded, this is child's branch or supervision is disabled */
if (pid == 0) {
pidfile_write(state.child_pidfh);
pidfile_write(state->child_pidfh);

if (state.user != NULL) {
restrict_process(state.user);
if (state->user != NULL) {
restrict_process(state->user);
}
/*
* In supervision mode, the child gets the original sigmask,
* and dup'd pipes.
*/
if (state.supervision_enabled) {
close(state.pipe_fd[0]);
if (sigprocmask(SIG_SETMASK, &mask_orig, NULL)) {
if (state->supervision_enabled) {
close(state->pipe_fd[0]);
if (sigprocmask(SIG_SETMASK, &state->mask_orig, NULL)) {
err(1, "sigprogmask");
}
if (state.stdmask & STDERR_FILENO) {
if (dup2(state.pipe_fd[1], STDERR_FILENO) == -1) {
if (state->stdmask & STDERR_FILENO) {
if (dup2(state->pipe_fd[1], STDERR_FILENO) == -1) {
err(1, "dup2");
}
}
if (state.stdmask & STDOUT_FILENO) {
if (dup2(state.pipe_fd[1], STDOUT_FILENO) == -1) {
if (state->stdmask & STDOUT_FILENO) {
if (dup2(state->pipe_fd[1], STDOUT_FILENO) == -1) {
err(1, "dup2");
}
}
if (state.pipe_fd[1] != STDERR_FILENO &&
state.pipe_fd[1] != STDOUT_FILENO) {
close(state.pipe_fd[1]);
if (state->pipe_fd[1] != STDERR_FILENO &&
state->pipe_fd[1] != STDOUT_FILENO) {
close(state->pipe_fd[1]);
}
}
execvp(argv[0], argv);
execvp(state->argv[0], state->argv);
/* execvp() failed - report error and exit this process */
err(1, "%s", argv[0]);
err(1, "%s", state->argv[0]);
}

/*
Expand All @@ -417,64 +430,65 @@ main(int argc, char *argv[])
*
* Unblock SIGTERM - now there is a valid child PID to signal to.
*/
if (sigprocmask(SIG_UNBLOCK, &mask_term, NULL)) {
if (sigprocmask(SIG_UNBLOCK, &state->mask_term, NULL)) {
warn("sigprocmask");
daemon_terminate(&state);
daemon_terminate(state);
}
close(state.pipe_fd[1]);
state.pipe_fd[1] = -1;
close(state->pipe_fd[1]);
state->pipe_fd[1] = -1;

setproctitle("%s[%d]", state.title, (int)pid);
setproctitle("%s[%d]", state->title, (int)pid);
for (;;) {
if (child_gone && state.child_eof) {
if (child_gone && state->child_eof) {
break;
}

if (terminate) {
daemon_terminate(&state);
daemon_terminate(state);
}

if (state.child_eof) {
if (sigprocmask(SIG_BLOCK, &mask_susp, NULL)) {
if (state->child_eof) {
if (sigprocmask(SIG_BLOCK, &state->mask_susp, NULL)) {
warn("sigprocmask");
daemon_terminate(&state);
daemon_terminate(state);
}
while (!terminate && !child_gone) {
sigsuspend(&mask_orig);
sigsuspend(&state->mask_orig);
}
if (sigprocmask(SIG_UNBLOCK, &mask_susp, NULL)) {
if (sigprocmask(SIG_UNBLOCK, &state->mask_susp, NULL)) {
warn("sigprocmask");
daemon_terminate(&state);
daemon_terminate(state);
}
continue;
}

if (sigprocmask(SIG_BLOCK, &mask_read, NULL)) {
if (sigprocmask(SIG_BLOCK, &state->mask_read, NULL)) {
warn("sigprocmask");
daemon_terminate(&state);
daemon_terminate(state);
}

state.child_eof = !listen_child(state.pipe_fd[0], &state);
state->child_eof = !listen_child(state->pipe_fd[0], state);

if (sigprocmask(SIG_UNBLOCK, &mask_read, NULL)) {
if (sigprocmask(SIG_UNBLOCK, &state->mask_read, NULL)) {
warn("sigprocmask");
daemon_terminate(&state);
daemon_terminate(state);
}

}
if (state.restart_enabled && !terminate) {
daemon_sleep(state.restart_delay, 0);
}
if (sigprocmask(SIG_BLOCK, &mask_term, NULL)) {

/*
* At the end of the loop the the child is already gone.
* Block SIGTERM to avoid racing until the child is spawned.
*/
if (sigprocmask(SIG_BLOCK, &state->mask_term, NULL)) {
warn("sigprocmask");
daemon_terminate(&state);
daemon_terminate(state);
}
if (state.restart_enabled && !terminate) {
close(state.pipe_fd[0]);
state.pipe_fd[0] = -1;
goto restart;

/* sleep before exiting mainloop if restart is enabled */
if (state->restart_enabled && !terminate) {
daemon_sleep(state->restart_delay, 0);
}
daemon_terminate(&state);
}

static void
Expand Down Expand Up @@ -746,6 +760,7 @@ daemon_state_init(struct daemon_state *state)
{
*state = (struct daemon_state) {
.pipe_fd = { -1, -1 },
.argv = NULL,
.parent_pidfh = NULL,
.child_pidfh = NULL,
.child_pidfile = NULL,
Expand All @@ -767,6 +782,16 @@ daemon_state_init(struct daemon_state *state)
.output_fd = -1,
.output_filename = NULL,
};

sigemptyset(&state->mask_susp);
sigemptyset(&state->mask_read);
sigemptyset(&state->mask_term);
sigemptyset(&state->mask_orig);
sigaddset(&state->mask_susp, SIGTERM);
sigaddset(&state->mask_susp, SIGCHLD);
sigaddset(&state->mask_term, SIGTERM);
sigaddset(&state->mask_read, SIGCHLD);

}

static _Noreturn void
Expand Down

0 comments on commit ac0a64b

Please sign in to comment.