Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/daemon/daemon.c
| Show First 20 Lines • Show All 80 Lines • ▼ Show 20 Lines | struct daemon_state { | ||||
| const char *user; | const char *user; | ||||
| struct pidfh *parent_pidfh; | struct pidfh *parent_pidfh; | ||||
| struct pidfh *child_pidfh; | struct pidfh *child_pidfh; | ||||
| enum daemon_mode mode; | enum daemon_mode mode; | ||||
| int pid; | int pid; | ||||
| int pipe_rd; | int pipe_rd; | ||||
| int pipe_wr; | int pipe_wr; | ||||
| int keep_cur_workdir; | int keep_cur_workdir; | ||||
| int kqueue_fd; | |||||
| int restart_delay; | int restart_delay; | ||||
| int stdmask; | int stdmask; | ||||
| int syslog_priority; | int syslog_priority; | ||||
| int syslog_facility; | int syslog_facility; | ||||
| int keep_fds_open; | int keep_fds_open; | ||||
| int output_fd; | int output_fd; | ||||
| bool restart_enabled; | bool restart_enabled; | ||||
| bool syslog_enabled; | bool syslog_enabled; | ||||
| Show All 11 Lines | |||||
| static void do_output(const unsigned char *, size_t, struct daemon_state *); | static void do_output(const unsigned char *, size_t, struct daemon_state *); | ||||
| static void daemon_sleep(struct daemon_state *); | static void daemon_sleep(struct daemon_state *); | ||||
| static void daemon_state_init(struct daemon_state *); | static void daemon_state_init(struct daemon_state *); | ||||
| static void daemon_eventloop(struct daemon_state *); | static void daemon_eventloop(struct daemon_state *); | ||||
| static void daemon_terminate(struct daemon_state *); | static void daemon_terminate(struct daemon_state *); | ||||
| static void daemon_exec(struct daemon_state *); | static void daemon_exec(struct daemon_state *); | ||||
| static bool daemon_is_child_dead(struct daemon_state *); | static bool daemon_is_child_dead(struct daemon_state *); | ||||
| static void daemon_set_child_pipe(struct daemon_state *); | static void daemon_set_child_pipe(struct daemon_state *); | ||||
| static int daemon_setup_kqueue(void); | |||||
| static int pidfile_truncate(struct pidfh *); | static int pidfile_truncate(struct pidfh *); | ||||
| static const char shortopts[] = "+cfHSp:P:ru:o:s:l:t:m:R:T:C:h"; | static const char shortopts[] = "+cfHSp:P:ru:o:s:l:t:m:R:T:C:h"; | ||||
| static const struct option longopts[] = { | static const struct option longopts[] = { | ||||
| { "change-dir", no_argument, NULL, 'c' }, | { "change-dir", no_argument, NULL, 'c' }, | ||||
| { "close-fds", no_argument, NULL, 'f' }, | { "close-fds", no_argument, NULL, 'f' }, | ||||
| ▲ Show 20 Lines • Show All 215 Lines • ▼ Show 20 Lines | main(int argc, char *argv[]) | ||||
| if (state.mode == MODE_DAEMON) { | if (state.mode == MODE_DAEMON) { | ||||
| daemon_exec(&state); | daemon_exec(&state); | ||||
| } | } | ||||
| /* Write out parent pidfile if needed. */ | /* Write out parent pidfile if needed. */ | ||||
| pidfile_write(state.parent_pidfh); | pidfile_write(state.parent_pidfh); | ||||
| state.kqueue_fd = daemon_setup_kqueue(); | |||||
| do { | do { | ||||
| state.mode = MODE_SUPERVISE; | state.mode = MODE_SUPERVISE; | ||||
| daemon_eventloop(&state); | daemon_eventloop(&state); | ||||
| daemon_sleep(&state); | daemon_sleep(&state); | ||||
| if (state.restart_enabled && state.restart_count > -1) { | if (state.restart_enabled && state.restart_count > -1) { | ||||
| if (state.restarted_count >= state.restart_count) { | if (state.restarted_count >= state.restart_count) { | ||||
| state.restart_enabled = false; | state.restart_enabled = false; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | daemon_eventloop(struct daemon_state *state) | ||||
| (void)madvise(NULL, 0, MADV_PROTECT); | (void)madvise(NULL, 0, MADV_PROTECT); | ||||
| if (pipe(pipe_fd)) { | if (pipe(pipe_fd)) { | ||||
| err(1, "pipe"); | err(1, "pipe"); | ||||
| } | } | ||||
| state->pipe_rd = pipe_fd[0]; | state->pipe_rd = pipe_fd[0]; | ||||
| state->pipe_wr = pipe_fd[1]; | state->pipe_wr = pipe_fd[1]; | ||||
| kq = kqueuex(KQUEUE_CLOEXEC); | kq = state->kqueue_fd; | ||||
| EV_SET(&event, state->pipe_rd, EVFILT_READ, EV_ADD|EV_CLEAR, 0, 0, | EV_SET(&event, state->pipe_rd, EVFILT_READ, EV_ADD|EV_CLEAR, 0, 0, | ||||
| NULL); | NULL); | ||||
| if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) { | if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) { | ||||
| err(EXIT_FAILURE, "failed to register kevent"); | err(EXIT_FAILURE, "failed to register kevent"); | ||||
| } | } | ||||
| EV_SET(&event, SIGHUP, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); | |||||
| if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) { | |||||
| err(EXIT_FAILURE, "failed to register kevent"); | |||||
| } | |||||
| EV_SET(&event, SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); | |||||
| if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) { | |||||
| err(EXIT_FAILURE, "failed to register kevent"); | |||||
| } | |||||
| EV_SET(&event, SIGCHLD, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); | |||||
| if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) { | |||||
| err(EXIT_FAILURE, "failed to register kevent"); | |||||
| } | |||||
| memset(&event, 0, sizeof(struct kevent)); | memset(&event, 0, sizeof(struct kevent)); | ||||
| /* Spawn a child to exec the command. */ | /* Spawn a child to exec the command. */ | ||||
| state->pid = fork(); | state->pid = fork(); | ||||
| /* fork failed, this can only happen when supervision is enabled */ | /* fork failed, this can only happen when supervision is enabled */ | ||||
| switch (state->pid) { | switch (state->pid) { | ||||
| case -1: | case -1: | ||||
| ▲ Show 20 Lines • Show All 76 Lines • ▼ Show 20 Lines | case EVFILT_READ: | ||||
| * EVFILT_READ events | * EVFILT_READ events | ||||
| */ | */ | ||||
| if (event.data > 0) { | if (event.data > 0) { | ||||
| (void)listen_child(state); | (void)listen_child(state); | ||||
| } | } | ||||
| continue; | continue; | ||||
| default: | default: | ||||
| assert(0 && "Unexpected kevent filter type"); | |||||
| continue; | continue; | ||||
| } | } | ||||
| } | } | ||||
| close(kq); | /* EVFILT_READ kqueue filter goes away here. */ | ||||
| close(state->pipe_rd); | close(state->pipe_rd); | ||||
| state->pipe_rd = -1; | state->pipe_rd = -1; | ||||
| /* | /* | ||||
| * We don't have to truncate the pidfile, but it's easier to test | * We don't have to truncate the pidfile, but it's easier to test | ||||
| * daemon(8) behavior in some respects if we do. We won't bother if | * daemon(8) behavior in some respects if we do. We won't bother if | ||||
| * the child won't be restarted. | * the child won't be restarted. | ||||
| */ | */ | ||||
| if (state->child_pidfh != NULL && state->restart_enabled) { | if (state->child_pidfh != NULL && state->restart_enabled) { | ||||
| pidfile_truncate(state->child_pidfh); | pidfile_truncate(state->child_pidfh); | ||||
| } | } | ||||
| } | } | ||||
| /* | |||||
| * Note that daemon_sleep() should not be called with anything but the signal | |||||
| * events in the kqueue without further consideration. | |||||
| */ | |||||
| static void | static void | ||||
| daemon_sleep(struct daemon_state *state) | daemon_sleep(struct daemon_state *state) | ||||
markj: After this change, it's not safe to use daemon_sleep() in code paths where there are still any… | |||||
| { | { | ||||
| struct timespec ts = { state->restart_delay, 0 }; | struct kevent event = { 0 }; | ||||
| int ret; | |||||
| assert(state->pipe_rd == -1); | |||||
| assert(state->pipe_wr == -1); | |||||
| if (!state->restart_enabled) { | if (!state->restart_enabled) { | ||||
| return; | return; | ||||
| } | } | ||||
| while (nanosleep(&ts, &ts) == -1) { | |||||
| EV_SET(&event, 0, EVFILT_TIMER, EV_ADD|EV_ONESHOT, NOTE_SECONDS, | |||||
| state->restart_delay, NULL); | |||||
| if (kevent(state->kqueue_fd, &event, 1, NULL, 0, NULL) == -1) { | |||||
| err(1, "failed to register timer"); | |||||
| } | |||||
| for (;;) { | |||||
| ret = kevent(state->kqueue_fd, NULL, 0, &event, 1, NULL); | |||||
| if (ret == -1) { | |||||
| if (errno != EINTR) { | if (errno != EINTR) { | ||||
| err(1, "nanosleep"); | err(1, "kevent"); | ||||
| } | } | ||||
| continue; | |||||
| } | } | ||||
| /* | |||||
| * Any other events being raised are indicative of a problem | |||||
| * that we need to investigate. Most likely being that | |||||
| * something was not cleaned up from the eventloop. | |||||
| */ | |||||
| assert(event.filter == EVFILT_TIMER || | |||||
| event.filter == EVFILT_SIGNAL); | |||||
| if (event.filter == EVFILT_TIMER) { | |||||
| /* Break's over, back to work. */ | |||||
| break; | |||||
| } | } | ||||
| /* Process any pending signals. */ | |||||
| switch (event.ident) { | |||||
| case SIGTERM: | |||||
| /* | |||||
| * We could disarm the timer, but we'll be terminating | |||||
| * promptly anyways. | |||||
| */ | |||||
| state->restart_enabled = false; | |||||
| return; | |||||
| case SIGHUP: | |||||
| if (state->log_reopen && state->output_fd >= 0) { | |||||
| reopen_log(state); | |||||
| } | |||||
| break; | |||||
| case SIGCHLD: | |||||
| default: | |||||
| /* Discard */ | |||||
| break; | |||||
| } | |||||
| } | |||||
| /* SIGTERM should've returned immediately. */ | |||||
| assert(state->restart_enabled); | |||||
| } | |||||
| static void | static void | ||||
| open_pid_files(struct daemon_state *state) | open_pid_files(struct daemon_state *state) | ||||
| { | { | ||||
| pid_t fpid; | pid_t fpid; | ||||
| int serrno; | int serrno; | ||||
| if (state->child_pidfile) { | if (state->child_pidfile) { | ||||
| state->child_pidfh = pidfile_open(state->child_pidfile, 0600, &fpid); | state->child_pidfh = pidfile_open(state->child_pidfile, 0600, &fpid); | ||||
| ▲ Show 20 Lines • Show All 175 Lines • ▼ Show 20 Lines | *state = (struct daemon_state) { | ||||
| .title = NULL, | .title = NULL, | ||||
| .user = NULL, | .user = NULL, | ||||
| .mode = MODE_DAEMON, | .mode = MODE_DAEMON, | ||||
| .restart_enabled = false, | .restart_enabled = false, | ||||
| .pid = 0, | .pid = 0, | ||||
| .pipe_rd = -1, | .pipe_rd = -1, | ||||
| .pipe_wr = -1, | .pipe_wr = -1, | ||||
| .keep_cur_workdir = 1, | .keep_cur_workdir = 1, | ||||
| .kqueue_fd = -1, | |||||
| .restart_delay = 1, | .restart_delay = 1, | ||||
| .stdmask = STDOUT_FILENO | STDERR_FILENO, | .stdmask = STDOUT_FILENO | STDERR_FILENO, | ||||
| .syslog_enabled = false, | .syslog_enabled = false, | ||||
| .log_reopen = false, | .log_reopen = false, | ||||
| .syslog_priority = LOG_NOTICE, | .syslog_priority = LOG_NOTICE, | ||||
| .syslog_tag = "daemon", | .syslog_tag = "daemon", | ||||
| .syslog_facility = LOG_DAEMON, | .syslog_facility = LOG_DAEMON, | ||||
| .keep_fds_open = 1, | .keep_fds_open = 1, | ||||
| .output_fd = -1, | .output_fd = -1, | ||||
| .output_filename = NULL, | .output_filename = NULL, | ||||
| .restart_count = -1, | .restart_count = -1, | ||||
| .restarted_count = 0 | .restarted_count = 0 | ||||
| }; | }; | ||||
| } | } | ||||
| static _Noreturn void | static _Noreturn void | ||||
| daemon_terminate(struct daemon_state *state) | daemon_terminate(struct daemon_state *state) | ||||
| { | { | ||||
| assert(state != NULL); | assert(state != NULL); | ||||
| if (state->kqueue_fd >= 0) { | |||||
| close(state->kqueue_fd); | |||||
| } | |||||
| if (state->output_fd >= 0) { | if (state->output_fd >= 0) { | ||||
| close(state->output_fd); | close(state->output_fd); | ||||
| } | } | ||||
| if (state->pipe_rd >= 0) { | if (state->pipe_rd >= 0) { | ||||
| close(state->pipe_rd); | close(state->pipe_rd); | ||||
| } | } | ||||
| if (state->pipe_wr >= 0) { | if (state->pipe_wr >= 0) { | ||||
| ▲ Show 20 Lines • Show All 52 Lines • ▼ Show 20 Lines | daemon_set_child_pipe(struct daemon_state *state) | ||||
| } | } | ||||
| if (state->pipe_wr != STDERR_FILENO && | if (state->pipe_wr != STDERR_FILENO && | ||||
| state->pipe_wr != STDOUT_FILENO) { | state->pipe_wr != STDOUT_FILENO) { | ||||
| close(state->pipe_wr); | close(state->pipe_wr); | ||||
| } | } | ||||
| /* The child gets dup'd pipes. */ | /* The child gets dup'd pipes. */ | ||||
| close(state->pipe_rd); | close(state->pipe_rd); | ||||
| } | |||||
| static int | |||||
| daemon_setup_kqueue(void) | |||||
| { | |||||
| int kq; | |||||
| struct kevent event = { 0 }; | |||||
| kq = kqueuex(KQUEUE_CLOEXEC); | |||||
| if (kq == -1) { | |||||
| err(EXIT_FAILURE, "kqueue"); | |||||
| } | |||||
| EV_SET(&event, SIGHUP, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); | |||||
| if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) { | |||||
| err(EXIT_FAILURE, "failed to register kevent"); | |||||
| } | |||||
| EV_SET(&event, SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); | |||||
| if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) { | |||||
| err(EXIT_FAILURE, "failed to register kevent"); | |||||
| } | |||||
| EV_SET(&event, SIGCHLD, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); | |||||
| if (kevent(kq, &event, 1, NULL, 0, NULL) == -1) { | |||||
| err(EXIT_FAILURE, "failed to register kevent"); | |||||
| } | |||||
| return (kq); | |||||
| } | } | ||||
| static int | static int | ||||
| pidfile_truncate(struct pidfh *pfh) | pidfile_truncate(struct pidfh *pfh) | ||||
| { | { | ||||
| int pfd = pidfile_fileno(pfh); | int pfd = pidfile_fileno(pfh); | ||||
| assert(pfd >= 0); | assert(pfd >= 0); | ||||
| Show All 12 Lines | |||||
After this change, it's not safe to use daemon_sleep() in code paths where there are still any other event types on the queue. That's fine today, but it might cause problems later. I'd at least add a comment explaining this.