Reap children

This commit is contained in:
C. McEnroe 2020-08-14 18:38:14 -04:00
parent e68a29c15e
commit ba2175979c
3 changed files with 177 additions and 9 deletions

113
daemon.c
View File

@ -20,6 +20,7 @@
#include <grp.h>
#include <poll.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
@ -27,7 +28,9 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/timespec.h>
#include <sys/wait.h>
#include <sysexits.h>
#include <syslog.h>
#include <unistd.h>
@ -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);

View File

@ -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);

View File

@ -25,6 +25,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
@ -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);
}
}