From ba2175979c923bd7b1630a5bc45d21dc5625ca10 Mon Sep 17 00:00:00 2001 From: "C. McEnroe" Date: Fri, 14 Aug 2020 18:38:14 -0400 Subject: Reap children --- daemon.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- daemon.h | 1 + service.c | 72 +++++++++++++++++++++++++++++++++++---- 3 files changed, 177 insertions(+), 9 deletions(-) diff --git a/daemon.c b/daemon.c index 3dc0640..cd12f92 100644 --- a/daemon.c +++ b/daemon.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -27,7 +28,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -47,6 +50,11 @@ struct timespec restartInterval = { .tv_sec = 1 }; struct Set256 stopExits; +static volatile sig_atomic_t signals[NSIG]; +static void signalHandler(int signal) { + signals[signal]++; +} + static void configerr(bool exit, const char *format, ...) { va_list ap; va_start(ap, format); @@ -219,10 +227,111 @@ int main(int argc, char *argv[]) { if (len < 0) syslog(LOG_WARNING, "%s: %m", pidPath); } - // TODO: Main loop. - + signal(SIGHUP, signalHandler); + signal(SIGINT, signalHandler); + signal(SIGTERM, signalHandler); + signal(SIGCHLD, signalHandler); + signal(SIGINFO, signalHandler); + + for (size_t i = 0; i < services.len; ++i) { + serviceStart(&services.ptr[i]); + } + + // TODO: setproctitle to number of services currently running. + + sigset_t mask; + sigemptyset(&mask); + for (;;) { + struct pollfd fds[1 + 2 * services.len]; + fds[0].fd = fifo; + fds[0].events = POLLIN; + + struct timespec deadline = {0}; + for (size_t i = 0; i < services.len; ++i) { + struct Service *service = &services.ptr[i]; + + fds[1 + 2 * i].fd = service->outPipe[0]; + fds[2 + 2 * i].fd = service->errPipe[0]; + fds[1 + 2 * i].events = POLLIN; + fds[2 + 2 * i].events = POLLIN; + + if (service->intent != Start) continue; + if (service->state == Start) continue; + if ( + !timespecisset(&deadline) || + timespeccmp(&service->restartDeadline, &deadline, <) + ) deadline = service->restartDeadline; + } + + struct timespec now = {0}; + struct timespec timeout = {0}; + if (timespecisset(&deadline)) { + clock_gettime(CLOCK_MONOTONIC_FAST, &now); + timespecsub(&deadline, &now, &timeout); + } + if (timeout.tv_sec < 0 || timeout.tv_nsec < 0) { + timespecclear(&timeout); + } + + int nfds = ppoll( + fds, 1 + 2 * services.len, + (timespecisset(&deadline) ? &timeout : NULL), + &mask + ); + + // TODO: Handle FIFO and pipes. + + clock_gettime(CLOCK_MONOTONIC_FAST, &now); + for (size_t i = 0; i < services.len; ++i) { + struct Service *service = &services.ptr[i]; + if (service->intent != Start) continue; + if (service->state == Start) continue; + if (timespeccmp(&service->restartDeadline, &now, <=)) { + serviceStart(service); + } + } + + if (signals[SIGCHLD]) { + int status; + pid_t pid; + while (0 < (pid = waitpid(-1, &status, WNOHANG))) { + serviceReap(pid, status); + } + if (pid < 0 && errno != ECHILD) syslog(LOG_ERR, "waitpid: %m"); + signals[SIGCHLD] = 0; + } + + if (signals[SIGINT] || signals[SIGTERM]) { + break; + } + if (signals[SIGHUP]) { + parseConfig(false, configPath); + signals[SIGHUP] = 0; + } + if (signals[SIGINFO]) { + // TODO: status * + signals[SIGINFO] = 0; + } + } + close(fifo); unlink(fifoPath); + + size_t count = 0; + for (size_t i = 0; i < services.len; ++i) { + serviceStop(&services.ptr[i]); + if (services.ptr[i].state == Start) count++; + } + while (count--) { + int status; + pid_t pid = wait(&status); + if (pid < 0) { + syslog(LOG_ERR, "wait: %m"); + continue; + } + serviceReap(pid, status); + } + if (pidPath) { close(pidFile); unlink(pidPath); diff --git a/daemon.h b/daemon.h index 5975034..4a9f4d8 100644 --- a/daemon.h +++ b/daemon.h @@ -100,6 +100,7 @@ void serviceStart(struct Service *service); void serviceStop(struct Service *service); void serviceRestart(struct Service *service); void serviceSignal(struct Service *service, int signal); +void serviceReap(pid_t pid, int status); extern char configError[]; int configParse(const char *path); diff --git a/service.c b/service.c index 290372d..35d15f3 100644 --- a/service.c +++ b/service.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -109,6 +110,14 @@ err: return -1; } +static void setDeadline(struct Service *service) { + clock_gettime(CLOCK_MONOTONIC_FAST, &service->restartDeadline); + timespecadd( + &service->restartDeadline, &service->restartInterval, + &service->restartDeadline + ); +} + void serviceStart(struct Service *service) { if (service->state != Stop) return; @@ -118,11 +127,7 @@ void serviceStart(struct Service *service) { } else { service->restartInterval = restartInterval; } - clock_gettime(CLOCK_MONOTONIC_FAST, &service->restartDeadline); - timespecadd( - &service->restartDeadline, &service->restartInterval, - &service->restartDeadline - ); + setDeadline(service); service->intent = Start; service->pid = fork(); @@ -131,6 +136,10 @@ void serviceStart(struct Service *service) { return; } if (service->pid) { + syslog( + LOG_INFO, "%s[%jd] start", + service->name, (intmax_t)service->pid + ); service->state = Start; return; } @@ -172,8 +181,8 @@ void serviceSignal(struct Service *service, int signal) { int error = kill(service->pid, signal); if (error) { syslog( - LOG_ERR, "signal %s %s[%ju]: %m", - sys_signame[signal], service->name, (uintmax_t)service->pid + LOG_ERR, "kill(%s[%jd], %s): %m", + service->name, (intmax_t)service->pid, sys_signame[signal] ); } } @@ -191,3 +200,52 @@ void serviceRestart(struct Service *service) { serviceStart(service); } } + +void serviceReap(pid_t pid, int status) { + struct Service *service = NULL; + for (size_t i = 0; i < services.len; ++i) { + if (services.ptr[i].state != Start) continue; + if (services.ptr[i].pid != pid) continue; + service = &services.ptr[i]; + break; + } + if (!service) { + syslog(LOG_WARNING, "reaping unknown child %jd", (intmax_t)pid); + return; + } + + // TODO: Flush line buffers. + + service->state = Stop; + if (WIFEXITED(status)) { + int exit = WEXITSTATUS(status); + if (exit == StopExit || setTest(&stopExits, exit)) { + service->intent = Stop; + } + if (exit) { + syslog( + LOG_WARNING, "%s[%jd] exit %d", + service->name, (intmax_t)pid, exit + ); + } + } else if (WIFSIGNALED(status)) { + syslog( + LOG_WARNING, "%s[%jd] signal %s", + service->name, (intmax_t)pid, sys_signame[WTERMSIG(status)] + ); + } + + if (service->intent == Start) { + setDeadline(service); + syslog( + LOG_INFO, "%s[%jd] restart in %jds", + service->name, (intmax_t)pid, + (intmax_t)service->restartInterval.tv_sec + ); + } else { + syslog(LOG_INFO, "%s[%jd] stop", service->name, (intmax_t)pid); + } + if (service->intent == Restart) { + serviceStart(service); + } +} -- cgit 1.4.1