summary refs log tree commit diff
diff options
context:
space:
mode:
authorC. McEnroe <june@causal.agency>2020-08-14 18:38:14 -0400
committerC. McEnroe <june@causal.agency>2020-08-15 01:08:01 -0400
commitba2175979c923bd7b1630a5bc45d21dc5625ca10 (patch)
tree2934cbf59f29e0b4c9349de856093f5fec39cfff
parentImplement serviceSignal, serviceStop, serviceRestart (diff)
downloadcatsit-ba2175979c923bd7b1630a5bc45d21dc5625ca10.tar.gz
catsit-ba2175979c923bd7b1630a5bc45d21dc5625ca10.zip
Reap children
-rw-r--r--daemon.c113
-rw-r--r--daemon.h1
-rw-r--r--service.c72
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 <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);
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 <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);
+	}
+}