summary refs log tree commit diff
diff options
context:
space:
mode:
authorLeah Neukirchen <leah@vuxu.org>2022-01-06 16:53:18 +0100
committerLeah Neukirchen <leah@vuxu.org>2022-01-06 16:53:18 +0100
commitbbfb3110de8846e2815e309b20604300532d235f (patch)
tree30ac769c2c93900e8c8b006ce7d4b0cd2323011f
downloadrvnit-bbfb3110de8846e2815e309b20604300532d235f.tar.gz
rvnit-bbfb3110de8846e2815e309b20604300532d235f.tar.xz
rvnit-bbfb3110de8846e2815e309b20604300532d235f.zip
initial commit
-rw-r--r--rvnit.c624
1 files changed, 624 insertions, 0 deletions
diff --git a/rvnit.c b/rvnit.c
new file mode 100644
index 0000000..a93d2d0
--- /dev/null
+++ b/rvnit.c
@@ -0,0 +1,624 @@
+/* accept4 */
+#define _GNU_SOURCE
+
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+//#include <sys/stat.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define MAX_SV 512
+
+struct entry {
+	char level;
+	char name[62];
+	char logged;
+	pid_t pid;
+	pid_t display_pid;  // not reset to 0, needed for oneshots
+	int logfd[2];
+	time_t start;
+	int status;
+	enum {
+		UNDEF,
+		UP,
+		DOWN,
+	} state;
+};
+
+struct entry services[MAX_SV];
+int level;
+
+pthread_t main_thread;
+pthread_t socket_thread;
+pthread_t logger_thread;
+
+int selflogfd[2];
+
+sig_atomic_t got_intr;
+sig_atomic_t got_sigusr1;
+
+void
+on_sigint(int sig)
+{
+	(void)sig;
+	got_intr = 1;
+}
+
+void
+on_sigusr1(int sig)
+{
+	(void)sig;
+	got_sigusr1 = 1;
+}
+
+void
+on_sigusr2(int sig)
+{
+	(void)sig;
+}
+
+void
+restart(int i)
+{
+	if (services[i].pid > 0)
+		return;
+	if (services[i].state != UP)
+		return;
+
+	int delay = 0;
+	time_t now = time(0);
+	if (services[i].start == now)
+		delay = 1;
+
+	int loggerpipe[2] = { -1, -1 };
+
+	if (services[i].logfd[0] == -1) {
+		pipe(services[i].logfd);
+		fcntl(services[i].logfd[0], F_SETFL, O_NONBLOCK);
+		fcntl(services[i].logfd[1], F_SETFL, O_NONBLOCK);
+
+		pthread_kill(logger_thread, SIGUSR2);
+	}
+
+	if (services[i].name[2] == 'L') {
+		for (int j = 0; j < MAX_SV; j++) {
+			if (strcmp(services[i].name + 3,
+			    services[j].name + 3) == 0 &&
+			    services[j].name[2] == 'D') {
+				if (services[j].logfd[0] == -1) {
+					// hook up logger
+					pipe(loggerpipe);
+					services[j].logfd[0] = loggerpipe[0];
+					services[j].logfd[1] = loggerpipe[1];
+					services[j].logged = 1;
+				} else {
+					// recover loggerpipe
+					loggerpipe[0] = services[j].logfd[0];
+					loggerpipe[1] = services[j].logfd[1];
+				}
+				break;
+			}
+		}
+	}
+
+	pid_t child = fork();
+	if (child == 0) {
+		dup2(services[i].logfd[1], 1);
+		if (loggerpipe[0] != -1) { // loggers get read end of loggerpipe
+			dup2(loggerpipe[0], 0);
+			close(loggerpipe[0]);
+			close(loggerpipe[1]);
+		}
+
+		close(services[i].logfd[0]);
+		close(services[i].logfd[1]);
+		sleep(delay);
+		execl(services[i].name,
+		    services[i].name,
+		    (char *)0);
+		exit(-1);
+	} else if (child > 0) {
+		fcntl(services[i].logfd[0], F_SETFD, FD_CLOEXEC);
+		fcntl(services[i].logfd[1], F_SETFD, FD_CLOEXEC);
+		if (loggerpipe[0] != -1) {
+			fcntl(loggerpipe[0], F_SETFD, FD_CLOEXEC);
+			fcntl(loggerpipe[1], F_SETFD, FD_CLOEXEC);
+		}
+		services[i].pid = services[i].display_pid = child;
+		services[i].start = now;
+	} else {
+		abort();
+	}
+}
+
+void *
+socket_loop(void* ignored)
+{
+	(void)ignored;
+
+	const char *path = "/tmp/rvnit.sock";
+
+	struct sockaddr_un addr = { 0 };
+	addr.sun_family = AF_UNIX;
+	strncpy(addr.sun_path, path, sizeof addr.sun_path - 1);
+	int listenfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+	if (listenfd < 0) {
+		perror("socket");
+		exit(111);
+	}
+	unlink(path);
+	int r = bind(listenfd, (struct sockaddr *)&addr, sizeof addr);
+	if (r < 0) {
+		perror("bind");
+		exit(111);
+	}
+
+	r = listen(listenfd, SOMAXCONN);
+        if (r < 0) {
+                perror("listen");
+                exit(111);
+        }
+
+	while (1) {
+		int connfd = accept4(listenfd, 0, 0, SOCK_CLOEXEC);
+		if (connfd < 0)
+			continue;
+
+		char cmd = 0;
+		read(connfd, &cmd, 1);
+		write(connfd, "ok\n", 3);
+
+		if (cmd == 'l') {
+			dprintf(connfd, "level=%d\n", level);
+		}
+
+		if (cmd == 's') {
+			for (int i = 0; i < MAX_SV; i++) {
+				if (services[i].name[2] == 'D' ||
+				    services[i].name[2] == 'L')
+					dprintf(connfd, "%s pid=%d status=%d\n",
+					    services[i].name,
+					    services[i].pid,
+					    services[i].status);
+			}
+		}
+
+		if (cmd == 'd' || cmd == 'u') {
+			char buf[63];
+			ssize_t rd = read(connfd, buf, sizeof buf);
+			if (rd > 0) {
+				buf[rd] = 0;
+				printf("got %c|%s|\n", cmd, buf);
+
+				for (int i = 0; i < MAX_SV; i++) {
+					if (strcmp(services[i].name, buf) == 0) {
+						if (cmd == 'd') {
+							printf("setting %s down\n",
+							    services[i].name);
+							services[i].state = DOWN;
+						} else if (cmd == 'u') {
+							printf("setting %s up\n",
+							    services[i].name);
+							services[i].state = UP;
+						}
+					}
+				}
+			}
+
+			pthread_kill(main_thread, SIGUSR1);
+		}
+
+		if (cmd == 'X') {
+			pthread_kill(main_thread, SIGPWR);
+		}
+
+		close(connfd);
+	}
+
+	return 0;
+}
+
+void *
+logger_loop(void* ignored)
+{
+	(void)ignored;
+
+//	sigaction(SIGUSR2, &(struct sigaction){.sa_handler=on_sigusr2}, 0);
+
+	while (1) {
+		struct pollfd fds[MAX_SV];
+		nfds_t nfds = 0;
+
+		fds[nfds].fd = selflogfd[0];
+		fds[nfds].events = POLLIN;
+		nfds++;
+
+		for (int i = 0; i < MAX_SV; i++) {
+			if (/*services[i].pid > 0 && */ !services[i].logged && services[i].logfd[0] > 0) {
+				fds[nfds].fd = services[i].logfd[0];
+				fds[nfds].events = POLLIN;
+				nfds++;
+			}
+		}
+
+//		printf("waiting for %d logs\n", (int)nfds);
+		int n = poll(fds, nfds, -1);
+		if (n < 0)
+			continue;
+
+		if (n == 1 && (fds[0].revents & POLLHUP) &&
+		    !(fds[0].revents & POLLIN)) {
+			/* selflog was closed, end thread */
+			break;
+		}
+
+		for (nfds_t j = 0; j < nfds; j++) {
+			// XXX HUP, NVAL
+			if (!(fds[j].revents & POLLIN))
+				continue;
+
+			char buf[4096];
+			ssize_t rd = read(fds[j].fd, buf, sizeof buf);
+			if (rd < 0) {
+				perror("read");
+				continue;
+			}
+			if (fds[j].fd == selflogfd[0]) {
+				printf("%s[%d]: %.*s",
+				    "rvnit",
+				    (int)getpid(),
+				    (int)rd,
+				    buf);
+				continue;
+			}
+			for (int i = 0; i < MAX_SV; i++) {
+				if (services[i].logfd[0] == fds[j].fd) {
+					printf("%s[%d]: %.*s",
+					    services[i].name,
+					    services[i].display_pid,
+					    (int)rd,
+					    buf
+						);
+					break;
+				}
+			}
+		}
+	}
+
+	return 0;
+}
+
+int
+reap(pid_t pid, int status)
+{
+	for (int i = 0; i < MAX_SV; i++) {
+		if (services[i].pid != pid)
+			continue;
+
+		services[i].pid = 0;
+		services[i].status = status;
+
+		dprintf(2, "reaped %s[%d] with status %d during level=%d\n",
+		    services[i].name, pid, status, level);
+
+		return i;
+	}
+
+	return -1;
+}
+
+int
+main()
+{
+	pipe(selflogfd);
+	fcntl(selflogfd[0], F_SETFL, O_NONBLOCK);
+	fcntl(selflogfd[1], F_SETFL, O_NONBLOCK);
+	fcntl(selflogfd[0], F_SETFD, FD_CLOEXEC);
+	fcntl(selflogfd[1], F_SETFD, FD_CLOEXEC);
+
+	sigaction(SIGUSR2, &(struct sigaction){.sa_handler=on_sigusr2}, 0);
+
+	main_thread = pthread_self();
+	pthread_create(&socket_thread, 0, socket_loop, 0);
+	pthread_create(&logger_thread, 0, logger_loop, 0);
+
+	if (chdir("sv") < 0) {
+		perror("chdir");
+		return 111;
+	}
+
+	DIR *dir = opendir(".");
+	if (!dir)
+		return 111;
+
+	int i = 0;
+
+	struct dirent *ent;
+	while ((ent = readdir(dir))) {
+		if (!isdigit(ent->d_name[0]) || !isdigit(ent->d_name[1]))
+			continue;
+
+		services[i].level = 10 * (ent->d_name[0] - '0') +
+		    (ent->d_name[1] - '0');
+		(void)snprintf(services[i].name, sizeof services[i].name,
+		    "%s", ent->d_name);
+		// follow symlinks
+		services[i].state = access(ent->d_name, X_OK) == 0 ? UP : DOWN;
+		services[i].logfd[0] = -1;
+		services[i].logfd[1] = -1;
+		services[i].logged = 0;
+		i++;
+	}
+
+	closedir(dir);
+
+	dprintf(selflogfd[1], "booting\n");
+
+	for (level = 0; level < 100; level++) {
+		/* spawn all of level */
+		int oneshot = 0;
+
+		// spawn loggers first
+		for (i = 0; i < MAX_SV; i++) {
+			if (services[i].level != level)
+				continue;
+
+			if (services[i].state != UP)
+				continue;
+
+			if (services[i].name[2] == 'L')
+				restart(i);
+		}
+
+		// spawn oneshots and daemons
+		for (i = 0; i < MAX_SV; i++) {
+			if (services[i].level != level)
+				continue;
+
+			if (services[i].state != UP)
+				continue;
+
+			if (services[i].name[2] == 'S') {
+				restart(i);
+				oneshot++;
+			} else if (services[i].name[2] == 'D') {
+				restart(i);
+			}
+		}
+
+		while (oneshot) {
+			int status = 0;
+			int pid = wait(&status);
+
+			if (pid < 0 && errno == ECHILD)
+				break;
+
+			int i = reap(pid, status);
+			if (i < 0)
+				continue;
+
+			if (services[i].level != level)
+				continue;
+
+			if (services[i].name[2] == 'S') {
+				oneshot--;
+			} else if (services[i].name[2] == 'D' ||
+			    services[i].name[2] == 'L') {
+				restart(i);
+			}
+		}
+	}
+
+	sigaction(SIGINT, &(struct sigaction){.sa_handler=on_sigint}, 0);
+	sigaction(SIGPWR, &(struct sigaction){.sa_handler=on_sigint}, 0);
+	sigaction(SIGUSR1, &(struct sigaction){.sa_handler=on_sigusr1}, 0);
+
+	dprintf(selflogfd[1], "system up\n");
+
+	while (1) {
+		if (got_intr)
+			break;
+
+		if (got_sigusr1) {
+			printf("rescanning state\n");
+			for (i = 0; i < MAX_SV; i++) {
+				if (services[i].name[2] == 'D' ||
+				    services[i].name[2] == 'L') {
+					if (services[i].state == UP &&
+					    services[i].pid == 0)
+						restart(i);
+					if (services[i].state == DOWN &&
+					    services[i].pid > 0)
+						kill(services[i].pid, SIGTERM);
+				}
+			}
+
+			got_sigusr1 = 0;
+		}
+
+		int status = 0;
+		errno = 0;
+		int pid = wait(&status);
+
+		if (pid < 0) {
+			if (errno == ECHILD)
+				break;
+			if (errno == EINTR)
+				continue;
+		}
+
+		int i = reap(pid, status);
+		if (i < 0)
+			continue;
+
+		if (services[i].name[2] == 'D' ||
+		    services[i].name[2] == 'L') {
+			dprintf(selflogfd[1], "%s terminated with status %d\n", services[i].name, services[i].status);
+			if (services[i].state == UP) {
+				dprintf(selflogfd[1], "restarting %s\n", services[i].name);
+				restart(i);
+			}
+		}
+	}
+
+	dprintf(selflogfd[1], "shutting down\n");
+
+//	sigaction(SIGINT, &(struct sigaction){.sa_handler=SIG_DFL}, 0);
+
+
+	struct timespec timeout = {7, 0};
+	sigset_t childset;
+	sigemptyset(&childset);
+	sigaddset(&childset, SIGCHLD);
+
+
+	for (level = 99; level >= 0; level--) {
+		/* kill all of level */
+		int oneshot = 0;
+		int daemons = 0;
+
+		for (i = 0; i < MAX_SV; i++) {
+			if (services[i].level != level)
+				continue;
+
+			if (services[i].name[2] == 'K' &&
+			    services[i].state == UP) {
+				dprintf(2, "todo: shutdown oneshot %s\n", services[i].name);
+				oneshot++;
+			}
+			if (services[i].name[2] == 'D' &&
+			    services[i].pid > 0) {
+				dprintf(2, "todo: sending sigterm to %s\n", services[i].name);
+				daemons++;
+			}
+		}
+
+		dprintf(2, "level=%d oneshot=%d daemons=%d\n",
+		    level, oneshot, daemons);
+	}
+
+	for (level = 99; level >= 0; level--) {
+		/* kill all of level */
+		int oneshot = 0;
+		int daemons = 0;
+
+		for (i = 0; i < MAX_SV; i++) {
+			if (services[i].level != level)
+				continue;
+
+			if (services[i].name[2] == 'K' &&
+			    services[i].state == UP) {
+				dprintf(selflogfd[1], "starting shutdown oneshot %s\n", services[i].name);
+				restart(i);
+				oneshot++;
+			}
+			if (services[i].name[2] == 'D' &&
+			    services[i].pid > 0) {
+				dprintf(selflogfd[1], "sending sigterm to %s\n", services[i].name);
+				kill(services[i].pid, SIGTERM);
+				kill(services[i].pid, SIGCONT);
+				daemons++;
+			}
+		}
+
+		dprintf(2, "level=%d oneshot=%d daemons=%d\n",
+		    level, oneshot, daemons);
+		usleep(100000);
+
+		// XXX figure out logger shutdown
+
+		while (oneshot) {
+			int status = 0;
+			int pid = wait(&status);
+
+			if (pid < 0 && errno == ECHILD)
+				break;
+
+			int i = reap(pid, status);
+			if (i < 0)
+				continue;
+
+			if (services[i].level != level)
+				continue;
+
+			if (services[i].name[2] == 'K') {
+				dprintf(selflogfd[1], "oneshot %s exited with status %d\n", services[i].name, services[i].status);
+				oneshot--;
+			} else if (services[i].name[2] == 'D') {
+				dprintf(selflogfd[1], "daemon %s exited with status %d\n", services[i].name, services[i].status);
+//				if (services[i].logged)
+//					close(services[i].logfd[1]);
+				daemons--;
+			}
+		}
+
+		// only daemons are left, wait up to 7s before sending
+		// them SIGKILL.
+
+		if (!daemons)
+			continue;
+
+		sigprocmask(SIG_BLOCK, &childset, 0);
+
+		while (daemons) {
+			int status = 0;
+			int pid = waitpid(-1, &status, WNOHANG);
+			if (pid == 0) { // nothing to reap
+				if (sigtimedwait(&childset, 0, &timeout) == SIGCHLD)
+					continue;
+				if (errno == EAGAIN) { // hit timeout
+					printf("need kill\n");
+					for (i = 0; i < MAX_SV; i++) {
+						if (services[i].level != level)
+							continue;
+						if (services[i].name[2] == 'D' &&
+						    services[i].pid > 0)
+							kill(services[i].pid, SIGKILL);
+					}
+					// XXX ensure this is only run once,
+					// then force next level
+					continue;
+				}
+			}
+
+			if (pid < 0 && errno == ECHILD)
+				break;
+
+			int i = reap(pid, status);
+			if (i < 0)
+				continue;
+
+			if (services[i].level != level)
+				continue;
+
+			if (services[i].name[2] == 'K') {
+				// can't happen
+				dprintf(selflogfd[1], "oneshot %s exited with status %d\n", services[i].name, services[i].status);
+				oneshot--;
+			} else if (services[i].name[2] == 'D') {
+				dprintf(selflogfd[1], "daemon %s exited with status %d\n", services[i].name, services[i].status);
+//				if (services[i].logged)
+//					close(services[i].logfd[1]);
+				daemons--;
+			}
+		}
+
+		sigprocmask(SIG_UNBLOCK, &childset, 0);
+	}
+
+	dprintf(selflogfd[1], "shutdown\n");
+	close(selflogfd[1]);
+	pthread_join(logger_thread, 0);
+}