commit 59de2eb1ab962e05b08e2dc029d6589cca457e41
parent 0ea4110cd11edc0e69f8c45270c5c7608e38ad3f
Author: Friedel Schön <[email protected]>
Date: Wed, 7 Jun 2023 21:22:18 +0200
excluding src/bin && detaching finit and fsvc
Diffstat:
25 files changed, 2251 insertions(+), 63 deletions(-)
diff --git a/Makefile b/Makefile
@@ -5,7 +5,7 @@ SRC_DIR := src
BUILD_DIR := build
INCLUDE_DIR := include
BIN_DIR := bin
-EXEC_DIR := src/bin
+EXEC_DIR := src/exec
MAN_DIR := src/man
TEMPL_DIR := src/docs
ROFF_DIR := man
diff --git a/include/config.h b/include/config.h
@@ -33,6 +33,10 @@
# define SV_RUNLEVEL_DEFAULT "default"
#endif
+#ifndef SV_SUPERVISE_EXEC
+# define SV_SUPERVISE_EXEC "/sbin/fsvs"
+#endif
+
// path to service-dir
#ifndef SV_SERVICE_DIR
# define SV_SERVICE_DIR "/etc/service.d"
diff --git a/include/service.h b/include/service.h
@@ -110,7 +110,6 @@ int service_refresh_directory(void);
service_t* service_register(int dir, const char* name, bool is_log_service);
void service_run(service_t* s);
int service_send_command(char command, char extra, const char* service, service_t* response, int response_max);
-void service_stage(int stage);
void service_start(service_t* s);
const char* service_status_name(service_t* s);
void service_stop(service_t* s);
@@ -118,3 +117,4 @@ int service_supervise(const char* service_dir, const char* runlevel);
void service_update_dependency(service_t* s);
bool service_is_dependency(service_t* s);
void service_update_state(service_t* s, int state);
+void service_write(service_t* s);
diff --git a/include/stage.h b/include/stage.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <stdbool.h>
+
+
+bool handle_stage(int stage);
diff --git a/include/util.h b/include/util.h
@@ -22,3 +22,4 @@ int reclaim_console(void);
void sigblock_all(int unblock);
long parse_long(const char* str, const char* name);
char* progname(char* path);
+int fd_set_flag(int fd, int flags);
diff --git a/src/exec/chpst.c b/src/exec/chpst.c
@@ -0,0 +1,129 @@
+#include "parse.h"
+#include "util.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+
+
+int main(int argc, char** argv) {
+ int opt, lockfd, lockflags, gid_len = 0;
+ char *arg0 = NULL, *root = NULL, *cd = NULL, *lock = NULL, *exec = NULL;
+ uid_t uid = 0;
+ gid_t gid[61];
+ long nicelevel = 0;
+ bool ssid = false;
+ bool closestd[3] = { false, false, false };
+
+ while ((opt = getopt(argc, argv, "+u:U:b:e:m:d:o:p:f:c:r:t:/:C:n:l:L:vP012V")) != -1) {
+ switch (opt) {
+ case 'u':
+ case 'U':
+ gid_len = parse_ugid(optarg, &uid, gid);
+ break;
+ case 'b':
+ arg0 = optarg;
+ break;
+ case '/':
+ root = optarg;
+ break;
+ case 'C':
+ cd = optarg;
+ break;
+ case 'n':
+ nicelevel = parse_long(optarg, "nice-level");
+ break;
+ case 'l':
+ lock = optarg;
+ lockflags = LOCK_EX | LOCK_NB;
+ break;
+ case 'L':
+ lock = optarg;
+ lockflags = LOCK_EX;
+ break;
+ case 'v': // ignored
+ break;
+ case 'P':
+ ssid = true;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ closestd[opt - '0'] = true;
+ break;
+ case 'e':
+ case 'd':
+ case 'o':
+ case 'p':
+ case 'f':
+ case 'c':
+ case 'r':
+ case 't':
+ case 'm': // ignored
+ fprintf(stderr, "warning: '-%c' are ignored\n", optopt);
+ break;
+ case '?':
+ fprintf(stderr, "usage\n");
+ return 1;
+ }
+ }
+ argv += optind, argc -= optind;
+
+ if (argc == 0) {
+ fprintf(stderr, "command required\n");
+ return 1;
+ }
+
+ if (ssid) {
+ setsid();
+ }
+
+ if (uid) {
+ setgroups(gid_len, gid);
+ setgid(gid[0]);
+ setuid(uid);
+ // $EUID
+ }
+
+ if (root) {
+ if (chroot(root) == -1)
+ print_error("unable to change root directory: %s\n");
+
+ // chdir to '/', otherwise the next command will complain 'directory not found'
+ chdir("/");
+ }
+
+ if (cd) {
+ chdir(cd);
+ }
+
+ if (nicelevel != 0) {
+ if (nice(nicelevel) == -1)
+ print_error("unable to set nice level: %s\n");
+ }
+
+ if (lock) {
+ if ((lockfd = open(lock, O_WRONLY | O_APPEND)) == -1)
+ print_error("unable to open lock: %s\n");
+
+ if (flock(lockfd, lockflags) == -1)
+ print_error("unable to lock: %s\n");
+ }
+
+ if (closestd[0] && close(0) == -1)
+ print_error("unable to close stdin: %s\n");
+ if (closestd[1] && close(1) == -1)
+ print_error("unable to close stdout: %s\n");
+ if (closestd[2] && close(2) == -1)
+ print_error("unable to close stderr: %s\n");
+
+ exec = argv[0];
+ if (arg0)
+ argv[0] = arg0;
+
+ execvp(exec, argv);
+ print_error("cannot execute: %s\n");
+}
diff --git a/src/exec/finit.c b/src/exec/finit.c
@@ -0,0 +1,293 @@
+#include "config.h"
+#include "message.h"
+#include "util.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/reboot.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+
+char* stage[][4] = {
+ { SV_START_EXEC, NULL },
+ { SV_SUPERVISE_EXEC, SV_SERVICE_DIR, SV_RUNLEVEL_DEFAULT, NULL },
+ { SV_STOP_EXEC, NULL },
+};
+
+pipe_t selfpipe;
+int sigc = 0;
+int sigi = 0;
+
+void sig_cont_handler(int signo) {
+ (void) signo;
+
+ sigc++;
+ write(selfpipe.write, "", 1);
+}
+
+void sig_int_handler(int signo) {
+ (void) signo;
+
+ sigi++;
+ write(selfpipe.write, "", 1);
+}
+
+void sig_child_handler(int signo) {
+ (void) signo;
+
+ write(selfpipe.write, "", 1);
+}
+
+
+int main(int argc, char** argv) {
+ int pid;
+ int wstat;
+ struct pollfd pollst;
+ int ttyfd;
+ char ch;
+ sigset_t ss;
+ struct sigaction sa = { 0 };
+
+
+ if (getpid() != 1) {
+ if (argc != 2 || argv[1][1] != '\0' || (argv[1][0] != '0' && argv[1][0] != '6'))
+ print_usage_exit(PROG_FINIT, 1);
+
+ if (kill(1, argv[1][0] == '0' ? SIGCONT : SIGINT) == -1) {
+ fprintf(stderr, "unable to signal init: %s\n", strerror(errno));
+ return 1;
+ }
+ return 0;
+ }
+
+ setsid();
+
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGALRM);
+ sigaddset(&ss, SIGCHLD);
+ sigaddset(&ss, SIGCONT);
+ sigaddset(&ss, SIGHUP);
+ sigaddset(&ss, SIGINT);
+ sigaddset(&ss, SIGPIPE);
+ sigaddset(&ss, SIGTERM);
+ sigprocmask(SIG_BLOCK, &ss, NULL);
+
+ sa.sa_handler = sig_child_handler;
+ sigaction(SIGCHLD, &sa, NULL);
+
+ sa.sa_handler = sig_cont_handler;
+ sigaction(SIGCONT, &sa, NULL);
+
+ sa.sa_handler = sig_int_handler;
+ sigaction(SIGINT, &sa, NULL);
+
+
+ /* console */
+ if ((ttyfd = open("/dev/console", O_WRONLY)) != -1) {
+ dup2(ttyfd, 0);
+ dup2(ttyfd, 1);
+ dup2(ttyfd, 2);
+ if (ttyfd > 2)
+ close(ttyfd);
+ }
+
+ /* create selfpipe */
+ while (pipe((int*) &selfpipe) == -1) {
+ fprintf(stderr, "unable to create selfpipe, pausing: %s\n", strerror(errno));
+ sleep(5);
+ }
+
+ fd_set_flag(selfpipe.read, O_NONBLOCK);
+ fd_set_flag(selfpipe.write, O_NONBLOCK);
+
+#if RB_DISABLE_CAD == 0
+ /* activate ctrlaltdel handling, glibc, dietlibc */
+ reboot(RB_DISABLE_CAD);
+#endif
+
+ /* runit */
+ for (int st = 0; st < 3; st++) {
+ while ((pid = fork()) == -1) {
+ fprintf(stderr, "unable to fork for \"%s\", pausing: %s\n", stage[st][0], strerror(errno));
+ sleep(5);
+ }
+ if (pid == 0) {
+ /* child */
+
+ /* stage 1 gets full control of console */
+ if (st == 0) {
+ if ((ttyfd = open("/dev/console", O_RDWR)) != -1) {
+ ioctl(ttyfd, TIOCSCTTY, (char*) 0);
+ dup2(ttyfd, 0);
+ if (ttyfd > 2)
+ close(ttyfd);
+ } else {
+ fprintf(stderr, "warn: unable to open /dev/console: %s\n", strerror(errno));
+ }
+ } else {
+ setsid();
+ }
+
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGALRM);
+ sigaddset(&ss, SIGCHLD);
+ sigaddset(&ss, SIGCONT);
+ sigaddset(&ss, SIGHUP);
+ sigaddset(&ss, SIGINT);
+ sigaddset(&ss, SIGPIPE);
+ sigaddset(&ss, SIGTERM);
+ sigprocmask(SIG_UNBLOCK, &ss, (sigset_t*) 0);
+
+ sa.sa_handler = SIG_DFL;
+ sigaction(SIGCHLD, &sa, NULL);
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGCONT, &sa, NULL);
+
+ execv(stage[st][0], stage[st]);
+ fprintf(stderr, "unable to exec child '%s': %s", stage[st][0], strerror(errno));
+ _exit(1);
+ }
+
+ pollst.fd = selfpipe.read;
+ pollst.events = POLL_IN;
+
+ int child;
+
+ do_poll:
+
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGCHLD);
+ sigaddset(&ss, SIGCONT);
+ sigaddset(&ss, SIGINT);
+ sigprocmask(SIG_UNBLOCK, &ss, (sigset_t*) 0);
+
+ poll(&pollst, 1, 14000);
+
+ sigprocmask(SIG_BLOCK, &ss, (sigset_t*) 0);
+
+ while (read(selfpipe.read, &ch, 1) == 1) {}
+
+ child = waitpid(pid, &wstat, WNOHANG);
+
+ if (child == -1) {
+ fprintf(stderr, "cannot wait for %s, pausing: %s", stage[st][0], strerror(errno));
+ sleep(5);
+ }
+
+ /* reget stderr */
+ if ((ttyfd = open("/dev/console", O_WRONLY)) != -1) {
+ dup2(ttyfd, 1);
+ dup2(ttyfd, 2);
+
+ if (ttyfd > 2)
+ close(ttyfd);
+ }
+
+ if (child == pid) {
+ if (!WIFEXITED(wstat) || WEXITSTATUS(wstat) != 0) {
+ fprintf(stderr, "%s failed\n", stage[st][0]);
+ if (st == 0) {
+ /* this is stage 1 */
+ fprintf(stderr, "stage 1 failed, starting emergency runlevel");
+ stage[1][2] = "emergency";
+ break;
+ } else if (st == 1) {
+ fprintf(stderr, "killing all processes in stage 2 and retry...\n");
+ kill(-pid, 9);
+ sleep(5);
+ st--;
+ break;
+ }
+ }
+ fprintf(stderr, "leaving stage %d\n", st + 1);
+ } else if (child != 0) {
+ write(selfpipe.write, "", 1);
+ goto do_poll;
+ } else if (!sigc && !sigi) {
+ goto do_poll;
+ } else if (st != 1) {
+ fprintf(stderr, "signals only work in stage 2\n");
+ // sigc = sigi = 0;
+ goto do_poll;
+ } else {
+
+ kill(pid, SIGTERM);
+ for (int i = 0; i < 5; i++) {
+ if ((child = waitpid(-1, &wstat, WNOHANG)) == pid) {
+ // stage terminated!
+ pid = 0;
+ break;
+ } else if (child == -1) {
+ fprintf(stderr, "waiting for terminated stage 2 failed: %s\n", strerror(errno));
+ sleep(1);
+ }
+ }
+ if (pid) {
+ kill(pid, 9);
+ if (waitpid(pid, &wstat, 0) == -1)
+ fprintf(stderr, "waiting for killed stage 2 failed: %s\n", strerror(errno));
+ }
+ }
+ }
+
+ // xxxx
+
+ close(selfpipe.read);
+ close(selfpipe.write);
+
+ /* reget stderr */
+ if ((ttyfd = open("/dev/console", O_WRONLY)) != -1) {
+ dup2(ttyfd, 1);
+ dup2(ttyfd, 2);
+ if (ttyfd > 2)
+ close(ttyfd);
+ }
+
+#ifdef RB_AUTOBOOT
+ /* fallthrough stage 3 */
+ fprintf(stderr, "sending KILL signal to all processes...");
+ kill(-1, SIGKILL);
+
+ if ((pid = fork()) >= 0) {
+ if (sigi) { // wants reboot
+ fprintf(stderr, "system reboot\n");
+ sync();
+ reboot(RB_AUTOBOOT);
+ } else {
+# ifdef RB_POWER_OFF
+ fprintf(stderr, "system poweroff\n");
+ sync();
+ reboot(RB_POWER_OFF);
+ sleep(2);
+# endif
+ fprintf(stderr, "system halt\n");
+ sync();
+# if defined(RB_HALT_SYSTEM)
+ reboot(RB_HALT_SYSTEM);
+# elif defined(RB_HALT)
+ reboot(RB_HALT);
+# else
+ reboot(RB_AUTOBOOT);
+# endif
+ }
+ if (pid == 0)
+ _exit(0);
+ } else {
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGCHLD);
+ sigprocmask(SIG_UNBLOCK, &ss, (sigset_t*) 0);
+
+ while (waitpid(pid, NULL, 0) == -1) {}
+ }
+#endif
+
+ sigemptyset(&ss);
+
+ for (;;)
+ sigsuspend(&ss);
+}
diff --git a/src/exec/fsvc.c b/src/exec/fsvc.c
@@ -0,0 +1,331 @@
+#include "config.h"
+#include "message.h"
+#include "service.h"
+#include "signame.h"
+#include "util.h"
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+struct ident {
+ const char* name;
+ const char* command;
+ bool runit;
+};
+
+static struct ident command_names[] = {
+ { "up", "u", true }, // starts the services, pin as started
+ { "down", "d", true }, // stops the service, pin as stopped
+ { "xup", "U", true }, // stops the service, don't pin as stopped
+ { "xdown", "D", true }, // stops the service, don't pin as stopped
+ { "once", "o", true }, // same as xup
+ { "term", "t", true }, // same as down
+ { "kill", "k", true }, // sends kill, pin as stopped
+ { "pause", "p", true }, // pauses the service
+ { "cont", "c", true }, // resumes the service
+ { "reset", "r", true }, // resets the service
+ { "alarm", "a", true }, // sends alarm
+ { "hup", "h", true }, // sends hup
+ { "int", "i", true }, // sends interrupt
+ { "quit", "q", true }, // sends quit
+ { "1", "1", true }, // sends usr1
+ { "2", "2", true }, // sends usr2
+ { "exit", "x", true }, // does nothing
+ { "+up", "U0", false }, // starts the service, don't modify pin
+ { "+down", "D0", false }, // stops the service, don't modify pin
+ { "restart", "!D0U0", false }, // restarts the service, don't modify pin
+ { "start", "!u", true }, // start the service, pin as started, print status
+ { "stop", "!d", true }, // stop the service, pin as stopped, print status
+ { "status", "!", true }, // print status
+ { "check", "!", true }, // print status
+ { "enable", "!.e", false }, // enable service
+ { "disable", "!.d", false }, // disable service
+ { "enable-once", "!.e", false }, // enable service once
+ { "disable-once", "!.d", false }, // disable service once
+ { 0, 0, 0 }
+};
+
+static const struct option long_options[] = {
+ { "version", no_argument, NULL, 'V' },
+ { "wait", no_argument, NULL, 'w' },
+ { 0 }
+};
+
+static int check_service(int dir, char* runlevel) {
+ int fd;
+ ssize_t size;
+
+ if ((fd = openat(dir, "supervise/ok", O_WRONLY | O_NONBLOCK)) == -1)
+ return -1;
+ close(fd);
+
+ if ((fd = openat(dir, "supervise/runlevel", O_RDONLY)) == -1)
+ return -1;
+
+ if ((size = read(fd, runlevel, NAME_MAX)) == -1) {
+ close(fd);
+ return -1;
+ }
+ runlevel[size] = '\0';
+ close(fd);
+
+ return 0;
+}
+
+static time_t get_mtime(int dir) {
+ struct stat st;
+ if (fstatat(dir, "supervise/status", &st, 0) == -1)
+ return -1;
+ return st.st_mtim.tv_sec;
+}
+
+static int handle_command(int dir, char command, const char* runlevel) {
+ int fd;
+
+ char up_file[SV_NAME_MAX] = "up-";
+ char once_file[SV_NAME_MAX] = "once-";
+
+ strcat(up_file, runlevel);
+ strcat(once_file, runlevel);
+
+ switch (command) {
+ case 'e': // enable
+ if ((fd = openat(dir, up_file, O_WRONLY | O_TRUNC | O_CREAT, 0644)) != -1)
+ close(fd);
+ break;
+ case 'd':
+ unlinkat(dir, up_file, 0);
+ break;
+ case 'E': // enable
+ if ((fd = openat(dir, once_file, O_WRONLY | O_TRUNC | O_CREAT, 0644)) != -1)
+ close(fd);
+ break;
+ case 'D':
+ unlinkat(dir, once_file, 0);
+ break;
+ default:
+ return -1;
+ }
+ return 0;
+}
+
+static int send_command(int dir, const char* command, const char* runlevel) {
+ int fd;
+ if ((fd = openat(dir, "supervise/control", O_WRONLY | O_NONBLOCK)) == -1)
+ return -1;
+
+ for (const char* c = command; *c != '\0'; c++) {
+ if (*c == '.') {
+ c++;
+ if (handle_command(dir, *c, runlevel) == -1)
+ return -1;
+ } else {
+ if (write(fd, c, 1) == -1)
+ break;
+ }
+ }
+ close(fd);
+
+ return 0;
+}
+
+int status(int dir) {
+ int fd;
+ time_t timeval;
+ const char* timeunit = "sec";
+ struct service_serial buffer;
+ service_t s;
+
+ if ((fd = openat(dir, "supervise/status", O_RDONLY | O_NONBLOCK)) == -1)
+ return -1;
+
+ if (read(fd, &buffer, sizeof(buffer)) == -1) {
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+
+ service_decode(&s, &buffer);
+
+ timeval = time(NULL) - s.status_change;
+
+ if (timeval >= 60) {
+ timeval /= 60;
+ timeunit = "min";
+ if (timeval >= 60) {
+ timeval /= 60;
+ timeunit = "h";
+ if (timeval >= 24) {
+ timeval /= 24;
+ timeunit = "d";
+ }
+ }
+ }
+
+ switch (s.state) {
+ case STATE_SETUP:
+ printf("setting up");
+ break;
+ case STATE_STARTING:
+ printf("starting as %d", s.pid);
+ break;
+ case STATE_ACTIVE_FOREGROUND:
+ printf("active as %d", s.pid);
+ break;
+ case STATE_ACTIVE_BACKGROUND:
+ case STATE_ACTIVE_DUMMY:
+ printf("active");
+ break;
+ case STATE_FINISHING:
+ printf("finishing as %d", s.pid);
+ break;
+ case STATE_STOPPING:
+ printf("stopping as %d", s.pid);
+ break;
+ case STATE_INACTIVE:
+ printf("inactive");
+ break;
+ case STATE_DEAD:
+ printf("dead");
+ break;
+ }
+
+ if (s.paused)
+ printf(" & paused");
+
+ printf(" since %lu%s", timeval, timeunit);
+
+ if (s.restart_manual == S_ONCE && s.restart_file != S_ONCE)
+ printf(", manually started once");
+ else if (s.restart_manual == S_RESTART && s.restart_file != S_RESTART)
+ printf(", manually restart");
+ else if (s.restart_manual == S_FORCE_DOWN && s.restart_file != S_DOWN)
+ printf(", manually forced down");
+
+ if (s.restart_file == S_ONCE)
+ printf(", should started once");
+ else if (s.restart_file == S_RESTART)
+ printf(", should restart");
+
+ if (s.is_dependency)
+ printf(", started as dependency");
+
+ if (s.return_code > 0 && s.last_exit == EXIT_NORMAL)
+ printf(", exited with %d", s.return_code);
+
+ if (s.return_code > 0 && s.last_exit == EXIT_SIGNALED)
+ printf(", signaled with SIG%s", sigabbr(s.return_code));
+
+ if (s.fail_count > 0)
+ printf(", failed %d times", s.fail_count);
+
+ printf("\n");
+
+ return 0;
+}
+
+int main(int argc, char** argv) {
+ int opt, dir,
+ timeout = SV_STATUS_WAIT;
+ time_t mod, start;
+
+ const char* command = NULL;
+ char runlevel[SV_NAME_MAX];
+
+ while ((opt = getopt_long(argc, argv, ":Vw:", long_options, NULL)) != -1) {
+ switch (opt) {
+ case 'V':
+ // version
+ break;
+ case 'w':
+ timeout = parse_long(optarg, "seconds");
+ break;
+ default:
+ case '?':
+ if (optopt)
+ fprintf(stderr, "error: invalid option -%c\n", optopt);
+ else
+ fprintf(stderr, "error: invalid option %s\n", argv[optind - 1]);
+ print_usage_exit(PROG_FSVC, 1);
+ }
+ }
+
+ argc -= optind, argv += optind;
+
+ if (argc == 0) {
+ fprintf(stderr, "error: command omitted\n");
+ print_usage_exit(PROG_FSVC, 1);
+ }
+ for (struct ident* ident = command_names; ident->name != NULL; ident++) {
+ if (streq(ident->name, argv[0])) {
+ command = ident->command;
+ break;
+ }
+ }
+ if (command == NULL) {
+ fprintf(stderr, "error: unknown command '%s'\n", argv[0]);
+ print_usage_exit(PROG_FSVC, 1);
+ }
+
+ argc--, argv++;
+
+ if (argc == 0) {
+ fprintf(stderr, "error: at least one service must be specified\n");
+ print_usage_exit(PROG_FSVC, 1);
+ }
+
+ chdir(SV_SERVICE_DIR);
+
+ bool print_status;
+ if ((print_status = command[0] == '!')) {
+ command++;
+ }
+
+ for (int i = 0; i < argc; i++) {
+ if ((dir = open(argv[i], O_DIRECTORY)) == -1) {
+ fprintf(stderr, "warning: '%s' is not a valid directory\n", argv[i]);
+ continue;
+ }
+
+
+ if (check_service(dir, runlevel) == -1) {
+ fprintf(stderr, "warning: '%s' is not a valid service\n", argv[i]);
+ continue;
+ }
+
+ if ((mod = get_mtime(dir)) == -1) {
+ fprintf(stderr, "warning: cannot get modify-time of '%s'\n", argv[i]);
+ continue;
+ }
+
+ if (command[0] != '\0') {
+ if (send_command(dir, command, runlevel) == -1) {
+ fprintf(stderr, "warning: unable to send command to '%s'\n", argv[i]);
+ continue;
+ }
+ } else {
+ mod++; // avoid modtime timeout
+ }
+
+ start = time(NULL);
+ if (print_status) {
+ while (get_mtime(dir) == mod && time(NULL) - start < timeout)
+ usleep(500); // sleep half a secound
+
+ if (get_mtime(dir) == mod)
+ printf("timeout: ");
+
+ printf("%s: ", progname(argv[i]));
+
+ if (status(dir) == -1)
+ printf("unable to access supervise/status\n");
+ }
+ }
+}
diff --git a/src/exec/fsvs.c b/src/exec/fsvs.c
@@ -0,0 +1,57 @@
+// daemon manager
+
+#include "config.h"
+#include "message.h"
+#include "service.h"
+#include "util.h"
+
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+
+static const struct option long_options[] = {
+ { "version", no_argument, 0, 'V' },
+ { 0 }
+};
+
+static void signal_interrupt(int signum) {
+ (void) signum;
+
+ daemon_running = false;
+}
+
+int main(int argc, char** argv) {
+ int c;
+ while ((c = getopt_long(argc, argv, ":V", long_options, NULL)) > 0) {
+ switch (c) {
+ case 'V':
+ print_version_exit();
+ default:
+ case '?':
+ if (optopt)
+ fprintf(stderr, "error: invalid option -%c\n", optopt);
+ else
+ fprintf(stderr, "error: invalid option %s\n", argv[optind - 1]);
+ print_usage_exit(PROG_FSVS, 1);
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+ if (argc == 0) {
+ fprintf(stderr, "error: missing <service-dir>\n");
+ print_usage_exit(PROG_FSVS, 1);
+ } else if (argc == 1) {
+ fprintf(stderr, "error: missing <runlevel>\n");
+ print_usage_exit(PROG_FSVS, 1);
+ } else if (argc > 2) {
+ fprintf(stderr, "error: too many arguments\n");
+ print_usage_exit(PROG_FSVS, 1);
+ }
+
+ signal(SIGINT, signal_interrupt);
+
+ return service_supervise(argv[0], argv[1]);
+}
diff --git a/src/exec/halt.c b/src/exec/halt.c
@@ -0,0 +1,79 @@
+#include "util.h"
+#include "wtmp.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/reboot.h>
+#include <unistd.h>
+
+
+int main(int argc, char* argv[]) {
+ bool do_sync = true,
+ do_force = false,
+ do_wtmp = true,
+ noop = false;
+ int opt;
+ int rebootnum;
+ const char* initarg;
+
+ char* prog = progname(argv[0]);
+
+ if (streq(prog, "halt")) {
+ rebootnum = RB_HALT_SYSTEM;
+ initarg = "0";
+ } else if (streq(prog, "poweroff")) {
+ rebootnum = RB_POWER_OFF;
+ initarg = "0";
+ } else if (streq(prog, "reboot")) {
+ rebootnum = RB_AUTOBOOT;
+ initarg = "6";
+ } else {
+ fprintf(stderr, "invalid mode: %s\n", prog);
+ return 1;
+ }
+
+ while ((opt = getopt(argc, argv, "dfhinwB")) != -1)
+ switch (opt) {
+ case 'n':
+ do_sync = 0;
+ break;
+ case 'w':
+ noop = 1;
+ do_sync = 0;
+ break;
+ case 'd':
+ do_wtmp = 0;
+ break;
+ case 'h':
+ case 'i':
+ /* silently ignored. */
+ break;
+ case 'f':
+ do_force = 1;
+ break;
+ case 'B':
+ write_wtmp(1);
+ return 0;
+ default:
+ fprintf(stderr, "Usage: %s [-n] [-f] [-d] [-w] [-B]", prog);
+ return 1;
+ }
+
+ if (do_wtmp)
+ write_wtmp(0);
+
+ if (do_sync)
+ sync();
+
+ if (!noop) {
+ if (do_force) {
+ reboot(rebootnum);
+ } else {
+ execl("/sbin/init", "init", initarg, NULL);
+ }
+ print_error("reboot failed: %s\n");
+ }
+
+ return 0;
+}
diff --git a/src/exec/init.lnk b/src/exec/init.lnk
@@ -0,0 +1 @@
+finit
+\ No newline at end of file
diff --git a/src/exec/modules-load.c b/src/exec/modules-load.c
@@ -0,0 +1,136 @@
+#include "util.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define MAX_CMDLINE_SIZE 4096
+#define MAX_MODULE_SIZE 256
+#define MAX_MODULE_COUNT 64
+
+
+static char kernel_cmdline[MAX_CMDLINE_SIZE];
+static char modules[MAX_MODULE_COUNT][MAX_MODULE_SIZE];
+static int modules_size = 0;
+
+static void read_cmdline(void) {
+ int fd;
+ int size;
+ char *end, *match, *com;
+
+ if ((fd = open("/proc/cmdline", O_RDONLY)) == -1)
+ return;
+
+ if ((size = read(fd, kernel_cmdline, sizeof(kernel_cmdline))) == -1) {
+ print_error("cannot read /proc/cmdline: %s\n");
+ close(fd);
+ return;
+ }
+ kernel_cmdline[size] = '\0';
+
+ end = kernel_cmdline;
+
+ while (end < kernel_cmdline + size && (match = strstr(end, "modules-load=")) != NULL) {
+ if (match != end && match[-1] != '.' && match[-1] != ' ') {
+ end += sizeof("modules-load=") - 1; // -1 because of implicit '\0'
+ continue;
+ }
+
+ match += sizeof("modules-load=") - 1; // -1 because of implicit '\0'
+ if ((end = strchr(match, ' ')) == NULL)
+ end = kernel_cmdline + size;
+ *end = '\0';
+
+ while ((com = strchr(match, ',')) != NULL) {
+ *com = '\0';
+ strcpy(modules[modules_size++], match);
+ match = com + 1;
+ }
+ if (match[0] != '\0')
+ strcpy(modules[modules_size++], match);
+ }
+}
+
+static void read_file(const char* path) {
+ int fd;
+ char line[MAX_MODULE_SIZE];
+ char* comment;
+
+ if ((fd = open(path, O_RDONLY)) == -1) {
+ print_error("unable to open %s: %s\n", path);
+ return;
+ }
+
+ while (dgetline(fd, line, sizeof(line)) > 0) {
+ if ((comment = strchr(line, '#')) != NULL) {
+ *comment = '\0';
+ }
+ if ((comment = strchr(line, ';')) != NULL) {
+ *comment = '\0';
+ }
+
+ if (line[0] != '\0')
+ strcpy(modules[modules_size++], line);
+ }
+}
+
+static void read_dir(const char* path) {
+ DIR* dir;
+ struct dirent* de;
+
+ if ((dir = opendir(path)) == NULL) {
+ return;
+ }
+
+ char filepath[1024];
+ while ((de = readdir(dir)) != NULL) {
+ if (de->d_name[0] == '.')
+ continue;
+
+ strcpy(filepath, path);
+ strcat(filepath, de->d_name);
+
+ read_file(filepath);
+ }
+
+ closedir(dir);
+}
+
+int main(int argc, char** argv) {
+ read_cmdline();
+
+ read_dir("/etc/modules-load.d/");
+ read_dir("/run/modules-load.d/");
+ read_dir("/usr/lib/modules-load.d/");
+
+ for (int i = 0; i < modules_size; i++) {
+ printf("%s\n", modules[i]);
+ }
+
+ char* args[modules_size + argc - 1 + 2 + 1];
+ int argi = 0;
+
+ args[argi++] = "modprobe";
+ args[argi++] = "-ab";
+
+ for (int i = 1; i < argc; i++) {
+ args[argi++] = argv[i];
+ }
+
+ for (int i = 0; i < modules_size; i++) {
+ args[argi++] = modules[i];
+ }
+
+ args[argi++] = NULL;
+
+ execvp("modprobe", args);
+
+ print_error("cannot exec modprobe: %s");
+ return 1;
+}
diff --git a/src/exec/poweroff.lnk b/src/exec/poweroff.lnk
@@ -0,0 +1 @@
+halt
+\ No newline at end of file
diff --git a/src/exec/reboot.lnk b/src/exec/reboot.lnk
@@ -0,0 +1 @@
+halt
+\ No newline at end of file
diff --git a/src/exec/seedrng.c b/src/exec/seedrng.c
@@ -0,0 +1,468 @@
+/* Based on code from <https://git.zx2c4.com/seedrng/about/>. */
+
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/random.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/random.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+
+#ifndef LOCALSTATEDIR
+# define LOCALSTATEDIR "/var/lib"
+#endif
+
+#define SEED_DIR LOCALSTATEDIR "/seedrng"
+#define CREDITABLE_SEED "seed.credit"
+#define NON_CREDITABLE_SEED "seed.no-credit"
+
+enum blake2s_lengths {
+ BLAKE2S_BLOCK_LEN = 64,
+ BLAKE2S_HASH_LEN = 32,
+ BLAKE2S_KEY_LEN = 32
+};
+
+enum seedrng_lengths {
+ MAX_SEED_LEN = 512,
+ MIN_SEED_LEN = BLAKE2S_HASH_LEN
+};
+
+struct blake2s_state {
+ uint32_t h[8];
+ uint32_t t[2];
+ uint32_t f[2];
+ uint8_t buf[BLAKE2S_BLOCK_LEN];
+ unsigned int buflen;
+ unsigned int outlen;
+};
+
+#define le32_to_cpup(a) le32toh(*(a))
+#define cpu_to_le32(a) htole32(a)
+#ifndef ARRAY_SIZE
+# define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#endif
+#ifndef DIV_ROUND_UP
+# define DIV_ROUND_UP(n, d) (((n) + (d) -1) / (d))
+#endif
+
+static inline void cpu_to_le32_array(uint32_t* buf, unsigned int words) {
+ while (words--) {
+ *buf = cpu_to_le32(*buf);
+ ++buf;
+ }
+}
+
+static inline void le32_to_cpu_array(uint32_t* buf, unsigned int words) {
+ while (words--) {
+ *buf = le32_to_cpup(buf);
+ ++buf;
+ }
+}
+
+static inline uint32_t ror32(uint32_t word, unsigned int shift) {
+ return (word >> (shift & 31)) | (word << ((-shift) & 31));
+}
+
+static const uint32_t blake2s_iv[8] = {
+ 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL,
+ 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL
+};
+
+static const uint8_t blake2s_sigma[10][16] = {
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
+ { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
+ { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
+ { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
+ { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
+ { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
+ { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
+ { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
+ { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
+};
+
+static void blake2s_set_lastblock(struct blake2s_state* state) {
+ state->f[0] = -1;
+}
+
+static void blake2s_increment_counter(struct blake2s_state* state, const uint32_t inc) {
+ state->t[0] += inc;
+ state->t[1] += (state->t[0] < inc);
+}
+
+static void blake2s_init_param(struct blake2s_state* state, const uint32_t param) {
+ int i;
+
+ memset(state, 0, sizeof(*state));
+ for (i = 0; i < 8; ++i)
+ state->h[i] = blake2s_iv[i];
+ state->h[0] ^= param;
+}
+
+static void blake2s_init(struct blake2s_state* state, const size_t outlen) {
+ blake2s_init_param(state, 0x01010000 | outlen);
+ state->outlen = outlen;
+}
+
+static void blake2s_compress(struct blake2s_state* state, const uint8_t* block, size_t nblocks, const uint32_t inc) {
+ uint32_t m[16];
+ uint32_t v[16];
+ int i;
+
+ while (nblocks > 0) {
+ blake2s_increment_counter(state, inc);
+ memcpy(m, block, BLAKE2S_BLOCK_LEN);
+ le32_to_cpu_array(m, ARRAY_SIZE(m));
+ memcpy(v, state->h, 32);
+ v[8] = blake2s_iv[0];
+ v[9] = blake2s_iv[1];
+ v[10] = blake2s_iv[2];
+ v[11] = blake2s_iv[3];
+ v[12] = blake2s_iv[4] ^ state->t[0];
+ v[13] = blake2s_iv[5] ^ state->t[1];
+ v[14] = blake2s_iv[6] ^ state->f[0];
+ v[15] = blake2s_iv[7] ^ state->f[1];
+
+#define G(r, i, a, b, c, d) \
+ do { \
+ a += b + m[blake2s_sigma[r][2 * i + 0]]; \
+ d = ror32(d ^ a, 16); \
+ c += d; \
+ b = ror32(b ^ c, 12); \
+ a += b + m[blake2s_sigma[r][2 * i + 1]]; \
+ d = ror32(d ^ a, 8); \
+ c += d; \
+ b = ror32(b ^ c, 7); \
+ } while (0)
+
+#define ROUND(r) \
+ do { \
+ G(r, 0, v[0], v[4], v[8], v[12]); \
+ G(r, 1, v[1], v[5], v[9], v[13]); \
+ G(r, 2, v[2], v[6], v[10], v[14]); \
+ G(r, 3, v[3], v[7], v[11], v[15]); \
+ G(r, 4, v[0], v[5], v[10], v[15]); \
+ G(r, 5, v[1], v[6], v[11], v[12]); \
+ G(r, 6, v[2], v[7], v[8], v[13]); \
+ G(r, 7, v[3], v[4], v[9], v[14]); \
+ } while (0)
+ ROUND(0);
+ ROUND(1);
+ ROUND(2);
+ ROUND(3);
+ ROUND(4);
+ ROUND(5);
+ ROUND(6);
+ ROUND(7);
+ ROUND(8);
+ ROUND(9);
+
+#undef G
+#undef ROUND
+
+ for (i = 0; i < 8; ++i)
+ state->h[i] ^= v[i] ^ v[i + 8];
+
+ block += BLAKE2S_BLOCK_LEN;
+ --nblocks;
+ }
+}
+
+static void blake2s_update(struct blake2s_state* state, const void* inp, size_t inlen) {
+ const size_t fill = BLAKE2S_BLOCK_LEN - state->buflen;
+ const uint8_t* in = inp;
+
+ if (!inlen)
+ return;
+ if (inlen > fill) {
+ memcpy(state->buf + state->buflen, in, fill);
+ blake2s_compress(state, state->buf, 1, BLAKE2S_BLOCK_LEN);
+ state->buflen = 0;
+ in += fill;
+ inlen -= fill;
+ }
+ if (inlen > BLAKE2S_BLOCK_LEN) {
+ const size_t nblocks = DIV_ROUND_UP(inlen, BLAKE2S_BLOCK_LEN);
+ blake2s_compress(state, in, nblocks - 1, BLAKE2S_BLOCK_LEN);
+ in += BLAKE2S_BLOCK_LEN * (nblocks - 1);
+ inlen -= BLAKE2S_BLOCK_LEN * (nblocks - 1);
+ }
+ memcpy(state->buf + state->buflen, in, inlen);
+ state->buflen += inlen;
+}
+
+static void blake2s_final(struct blake2s_state* state, uint8_t* out) {
+ blake2s_set_lastblock(state);
+ memset(state->buf + state->buflen, 0, BLAKE2S_BLOCK_LEN - state->buflen);
+ blake2s_compress(state, state->buf, 1, state->buflen);
+ cpu_to_le32_array(state->h, ARRAY_SIZE(state->h));
+ memcpy(out, state->h, state->outlen);
+}
+
+static ssize_t getrandom_full(void* buf, size_t count, unsigned int flags) {
+ ssize_t ret, total = 0;
+ uint8_t* p = buf;
+
+ do {
+ ret = getrandom(p, count, flags);
+ if (ret < 0 && errno == EINTR)
+ continue;
+ else if (ret < 0)
+ return ret;
+ total += ret;
+ p += ret;
+ count -= ret;
+ } while (count);
+ return total;
+}
+
+static ssize_t read_full(int fd, void* buf, size_t count) {
+ ssize_t ret, total = 0;
+ uint8_t* p = buf;
+
+ do {
+ ret = read(fd, p, count);
+ if (ret < 0 && errno == EINTR)
+ continue;
+ else if (ret < 0)
+ return ret;
+ else if (ret == 0)
+ break;
+ total += ret;
+ p += ret;
+ count -= ret;
+ } while (count);
+ return total;
+}
+
+static ssize_t write_full(int fd, const void* buf, size_t count) {
+ ssize_t ret, total = 0;
+ const uint8_t* p = buf;
+
+ do {
+ ret = write(fd, p, count);
+ if (ret < 0 && errno == EINTR)
+ continue;
+ else if (ret < 0)
+ return ret;
+ total += ret;
+ p += ret;
+ count -= ret;
+ } while (count);
+ return total;
+}
+
+static size_t determine_optimal_seed_len(void) {
+ size_t ret = 0;
+ char poolsize_str[11] = { 0 };
+ int fd = open("/proc/sys/kernel/random/poolsize", O_RDONLY);
+
+ if (fd < 0 || read_full(fd, poolsize_str, sizeof(poolsize_str) - 1) < 0) {
+ perror("Unable to determine pool size, falling back to 256 bits");
+ ret = MIN_SEED_LEN;
+ } else
+ ret = DIV_ROUND_UP(strtoul(poolsize_str, NULL, 10), 8);
+ if (fd >= 0)
+ close(fd);
+ if (ret < MIN_SEED_LEN)
+ ret = MIN_SEED_LEN;
+ else if (ret > MAX_SEED_LEN)
+ ret = MAX_SEED_LEN;
+ return ret;
+}
+
+static int read_new_seed(uint8_t* seed, size_t len, bool* is_creditable) {
+ ssize_t ret;
+ int urandom_fd;
+
+ *is_creditable = false;
+ ret = getrandom_full(seed, len, GRND_NONBLOCK);
+ if (ret == (ssize_t) len) {
+ *is_creditable = true;
+ return 0;
+ } else if (ret < 0 && errno == ENOSYS) {
+ struct pollfd random_fd = {
+ .fd = open("/dev/random", O_RDONLY),
+ .events = POLLIN
+ };
+ if (random_fd.fd < 0)
+ return -errno;
+ *is_creditable = poll(&random_fd, 1, 0) == 1;
+ close(random_fd.fd);
+ } else if (getrandom_full(seed, len, GRND_INSECURE) == (ssize_t) len)
+ return 0;
+ urandom_fd = open("/dev/urandom", O_RDONLY);
+ if (urandom_fd < 0)
+ return -1;
+ ret = read_full(urandom_fd, seed, len);
+ if (ret == (ssize_t) len)
+ ret = 0;
+ else
+ ret = -errno ? -errno : -EIO;
+ close(urandom_fd);
+ errno = -ret;
+ return ret ? -1 : 0;
+}
+
+static int seed_rng(uint8_t* seed, size_t len, bool credit) {
+ struct {
+ int entropy_count;
+ int buf_size;
+ uint8_t buffer[MAX_SEED_LEN];
+ } req = {
+ .entropy_count = credit ? len * 8 : 0,
+ .buf_size = len
+ };
+ int random_fd, ret;
+
+ if (len > sizeof(req.buffer)) {
+ errno = EFBIG;
+ return -1;
+ }
+ memcpy(req.buffer, seed, len);
+
+ random_fd = open("/dev/urandom", O_RDONLY);
+ if (random_fd < 0)
+ return -1;
+ ret = ioctl(random_fd, RNDADDENTROPY, &req);
+ if (ret)
+ ret = -errno ? -errno : -EIO;
+ close(random_fd);
+ errno = -ret;
+ return ret ? -1 : 0;
+}
+
+static int seed_from_file_if_exists(const char* filename, int dfd, bool credit, struct blake2s_state* hash) {
+ uint8_t seed[MAX_SEED_LEN];
+ ssize_t seed_len;
+ int fd = -1, ret = 0;
+
+ fd = openat(dfd, filename, O_RDONLY);
+ if (fd < 0 && errno == ENOENT)
+ return 0;
+ else if (fd < 0) {
+ ret = -errno;
+ perror("Unable to open seed file");
+ goto out;
+ }
+ seed_len = read_full(fd, seed, sizeof(seed));
+ if (seed_len < 0) {
+ ret = -errno;
+ perror("Unable to read seed file");
+ goto out;
+ }
+ if ((unlinkat(dfd, filename, 0) < 0 || fsync(dfd) < 0) && seed_len) {
+ ret = -errno;
+ perror("Unable to remove seed after reading, so not seeding");
+ goto out;
+ }
+ if (!seed_len)
+ goto out;
+
+ blake2s_update(hash, &seed_len, sizeof(seed_len));
+ blake2s_update(hash, seed, seed_len);
+
+ printf("Seeding %zd bits %s crediting\n", seed_len * 8, credit ? "and" : "without");
+ if (seed_rng(seed, seed_len, credit) < 0) {
+ ret = -errno;
+ perror("Unable to seed");
+ }
+
+out:
+ if (fd >= 0)
+ close(fd);
+ errno = -ret;
+ return ret ? -1 : 0;
+}
+
+static bool skip_credit(void) {
+ const char* skip = getenv("SEEDRNG_SKIP_CREDIT");
+ return skip && (!strcmp(skip, "1") || !strcasecmp(skip, "true") ||
+ !strcasecmp(skip, "yes") || !strcasecmp(skip, "y"));
+}
+
+int main(int argc __attribute__((unused)), char* argv[] __attribute__((unused))) {
+ static const char seedrng_prefix[] = "SeedRNG v1 Old+New Prefix";
+ static const char seedrng_failure[] = "SeedRNG v1 No New Seed Failure";
+ int fd = -1, dfd = -1, program_ret = 0;
+ uint8_t new_seed[MAX_SEED_LEN];
+ size_t new_seed_len;
+ bool new_seed_creditable;
+ struct timespec realtime = { 0 }, boottime = { 0 };
+ struct blake2s_state hash;
+
+ umask(0077);
+ if (getuid()) {
+ errno = EACCES;
+ perror("This program requires root");
+ return 1;
+ }
+
+ blake2s_init(&hash, BLAKE2S_HASH_LEN);
+ blake2s_update(&hash, seedrng_prefix, strlen(seedrng_prefix));
+ clock_gettime(CLOCK_REALTIME, &realtime);
+ clock_gettime(CLOCK_BOOTTIME, &boottime);
+ blake2s_update(&hash, &realtime, sizeof(realtime));
+ blake2s_update(&hash, &boottime, sizeof(boottime));
+
+ if (mkdir(SEED_DIR, 0700) < 0 && errno != EEXIST) {
+ perror("Unable to create seed directory");
+ return 1;
+ }
+
+ dfd = open(SEED_DIR, O_DIRECTORY);
+ if (dfd < 0 || flock(dfd, LOCK_EX) < 0) {
+ perror("Unable to lock seed directory");
+ program_ret = 1;
+ goto out;
+ }
+
+ if (seed_from_file_if_exists(NON_CREDITABLE_SEED, dfd, false, &hash) < 0)
+ program_ret |= 1 << 1;
+ if (seed_from_file_if_exists(CREDITABLE_SEED, dfd, !skip_credit(), &hash) < 0)
+ program_ret |= 1 << 2;
+
+ new_seed_len = determine_optimal_seed_len();
+ if (read_new_seed(new_seed, new_seed_len, &new_seed_creditable) < 0) {
+ perror("Unable to read new seed");
+ new_seed_len = BLAKE2S_HASH_LEN;
+ strncpy((char*) new_seed, seedrng_failure, new_seed_len);
+ program_ret |= 1 << 3;
+ }
+ blake2s_update(&hash, &new_seed_len, sizeof(new_seed_len));
+ blake2s_update(&hash, new_seed, new_seed_len);
+ blake2s_final(&hash, new_seed + new_seed_len - BLAKE2S_HASH_LEN);
+
+ printf("Saving %zu bits of %s seed for next boot\n", new_seed_len * 8, new_seed_creditable ? "creditable" : "non-creditable");
+ fd = openat(dfd, NON_CREDITABLE_SEED, O_WRONLY | O_CREAT | O_TRUNC, 0400);
+ if (fd < 0) {
+ perror("Unable to open seed file for writing");
+ program_ret |= 1 << 4;
+ goto out;
+ }
+ if (write_full(fd, new_seed, new_seed_len) != (ssize_t) new_seed_len || fsync(fd) < 0) {
+ perror("Unable to write seed file");
+ program_ret |= 1 << 5;
+ goto out;
+ }
+ if (new_seed_creditable && renameat(dfd, NON_CREDITABLE_SEED, dfd, CREDITABLE_SEED) < 0) {
+ perror("Unable to make new seed creditable");
+ program_ret |= 1 << 6;
+ }
+out:
+ if (fd >= 0)
+ close(fd);
+ if (dfd >= 0)
+ close(dfd);
+ return program_ret;
+}
diff --git a/src/exec/shutdown.sh b/src/exec/shutdown.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+# shutdown - shutdown(8) lookalike for fiss
+
+abort() {
+ printf '%s\n' "$1" >&2
+ exit 1
+}
+
+usage() {
+ abort "Usage: ${0##*/} [-fF] [-kchPr] time [warning message]"
+}
+
+action=:
+
+while getopts akrhPHfFnct: opt; do
+ case "$opt" in
+ a|n|H) abort "'-$opt' is not implemented";;
+ t) ;;
+ f) touch /fastboot;;
+ F) touch /forcefsck;;
+ k) action=true;;
+ c) action=cancel;;
+ h|P) action=halt;;
+ r) action=reboot;;
+ [?]) usage;;
+ esac
+done
+shift $((OPTIND - 1))
+
+[ $# -eq 0 ] && usage
+
+time=$1; shift
+message="${*:-system is going down}"
+
+if [ "$action" = "cancel" ]; then
+ kill "$(cat /run/fiss/shutdown.pid)"
+ if [ -e /etc/nologin ] && ! [ -s /etc/nologin ]; then
+ rm /etc/nologin
+ fi
+ echo "${*:-shutdown cancelled}" | wall
+ exit
+fi
+
+touch /run/fiss/shutdown.pid 2>/dev/null || abort "Not enough permissions to execute ${0#*/}"
+echo $$ >/run/fiss/shutdown.pid
+
+case "$time" in
+ now) time=0;;
+ +*) time=${time#+};;
+ *:*) abort "absolute time is not implemented";;
+ *) abort "invalid time";;
+esac
+
+for break in 5 0; do
+ [ "$time" -gt "$break" ] || continue
+ [ "$break" = 0 ] && touch /etc/nologin
+
+ printf '%s in %s minutes\n' "$message" "$time" | wall
+ printf 'shutdown: sleeping for %s minutes... ' "$(( time - break ))"
+ sleep $(( (time - break) * 60 ))
+ time="$break"
+ printf '\n'
+
+ [ "$break" = 0 ] && rm /etc/nologin
+done
+
+printf '%s NOW\n' "$message" | wall
+
+$action
diff --git a/src/exec/sigremap.c b/src/exec/sigremap.c
@@ -0,0 +1,277 @@
+/* Copyright (c) 2015 Yelp, Inc.
+ With modification 2023 Friedel Schon
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+ */
+
+
+#include "message.h"
+#include "signame.h"
+#include "util.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define DEBUG(...) \
+ do { \
+ if (debug) \
+ fprintf(stderr, __VA_ARGS__); \
+ } while (0)
+
+#define set_signal_undefined(old, new) \
+ if (signal_remap[old] == -1) \
+ signal_remap[old] = new;
+
+
+// Signals we care about are numbered from 1 to 31, inclusive.
+// (32 and above are real-time signals.)
+// TODO: this is likely not portable outside of Linux, or on strange architectures
+#define MAXSIG 31
+
+// Indices are one-indexed (signal 1 is at index 1). Index zero is unused.
+// User-specified signal rewriting.
+static int signal_remap[MAXSIG + 1];
+// One-time ignores due to TTY quirks. 0 = no skip, 1 = skip the next-received signal.
+static bool signal_temporary_ignores[MAXSIG + 1];
+
+static int child_pid = -1;
+static bool debug = false;
+static bool use_setsid = true;
+
+
+/*
+ * The sigremap signal handler.
+ *
+ * The main job of this signal handler is to forward signals along to our child
+ * process(es). In setsid mode, this means signaling the entire process group
+ * rooted at our child. In non-setsid mode, this is just signaling the primary
+ * child.
+ *
+ * In most cases, simply proxying the received signal is sufficient. If we
+ * receive a job control signal, however, we should not only forward it, but
+ * also sleep sigremap itself.
+ *
+ * This allows users to run foreground processes using sigremap and to
+ * control them using normal shell job control features (e.g. Ctrl-Z to
+ * generate a SIGTSTP and suspend the process).
+ *
+ * The libc manual is useful:
+ * https://www.gnu.org/software/libc/manual/html_node/Job-Control-Signals.html
+ *
+ */
+static void handle_signal(int signum) {
+ DEBUG("Received signal %d.\n", signum);
+
+ if (signal_temporary_ignores[signum] == 1) {
+ DEBUG("Ignoring tty hand-off signal %d.\n", signum);
+ signal_temporary_ignores[signum] = 0;
+ } else if (signum == SIGCHLD) {
+ int status, exit_status;
+ int killed_pid;
+ while ((killed_pid = waitpid(-1, &status, WNOHANG)) > 0) {
+ if (WIFEXITED(status)) {
+ exit_status = WEXITSTATUS(status);
+ DEBUG("A child with PID %d exited with exit status %d.\n", killed_pid, exit_status);
+ } else {
+ assert(WIFSIGNALED(status));
+ exit_status = 128 + WTERMSIG(status);
+ DEBUG("A child with PID %d was terminated by signal %d.\n", killed_pid, exit_status - 128);
+ }
+
+ if (killed_pid == child_pid) {
+ kill(use_setsid ? -child_pid : child_pid, SIGTERM); // send SIGTERM to any remaining children
+ DEBUG("Child exited with status %d. Goodbye.\n", exit_status);
+ exit(exit_status);
+ }
+ }
+ } else {
+ if (signum <= MAXSIG && signal_remap[signum] != -1) {
+ DEBUG("Translating signal %d to %d.\n", signum, signal_remap[signum]);
+ signum = signal_remap[signum];
+ }
+
+ kill(use_setsid ? -child_pid : child_pid, signum);
+ DEBUG("Forwarded signal %d to children.\n", signum);
+
+ if (signum == SIGTSTP || signum == SIGTTOU || signum == SIGTTIN) {
+ DEBUG("Suspending self due to TTY signal.\n");
+ kill(getpid(), SIGSTOP);
+ }
+ }
+}
+
+static char** parse_command(int argc, char* argv[]) {
+ int opt;
+ struct option long_options[] = {
+ { "single", no_argument, NULL, 's' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "version", no_argument, NULL, 'V' },
+ { NULL, 0, NULL, 0 },
+ };
+ char *old, *new;
+ int oldsig, newsig;
+
+ while ((opt = getopt_long(argc, argv, "+:hvVs", long_options, NULL)) != -1) {
+ switch (opt) {
+ case 'v':
+ debug = true;
+ break;
+ case 'V':
+ print_version_exit();
+ case 'c':
+ use_setsid = false;
+ break;
+ default:
+ print_usage_exit(PROG_SIGREMAP, 1);
+ }
+ }
+
+ argc -= optind, argv += optind;
+
+ while (argc > 0) {
+ if ((new = strchr(argv[0], '=')) == NULL)
+ break;
+
+ old = argv[0];
+ *new = '\0';
+ new ++;
+
+ if ((oldsig = signame(old)) == -1) {
+ fprintf(stderr, "error: invalid old signal '%s'\n", old);
+ exit(1);
+ }
+ if ((newsig = signame(new)) == -1) {
+ fprintf(stderr, "error: invalid new signal '%s'\n", new);
+ exit(1);
+ }
+ signal_remap[oldsig] = newsig;
+
+ argc--, argv++;
+ }
+
+ if (argc < 1) {
+ print_usage_exit(PROG_SIGREMAP, 1);
+ }
+
+ if (use_setsid) {
+ set_signal_undefined(SIGTSTP, SIGSTOP);
+ set_signal_undefined(SIGTSTP, SIGTTOU);
+ set_signal_undefined(SIGTSTP, SIGTTIN);
+ }
+
+ return &argv[optind];
+}
+
+// A dummy signal handler used for signals we care about.
+// On the FreeBSD kernel, ignored signals cannot be waited on by `sigwait` (but
+// they can be on Linux). We must provide a dummy handler.
+// https://lists.freebsd.org/pipermail/freebsd-ports/2009-October/057340.html
+static void dummy(int signum) {
+ (void) signum;
+}
+
+int main(int argc, char* argv[]) {
+ char** cmd = parse_command(argc, argv);
+ sigset_t all_signals;
+ int signum;
+
+ sigfillset(&all_signals);
+ sigprocmask(SIG_BLOCK, &all_signals, NULL);
+
+ for (int i = 1; i <= MAXSIG; i++) {
+ signal_remap[i] = -1;
+ signal_temporary_ignores[i] = false;
+
+ signal(i, dummy);
+ }
+
+ /*
+ * Detach sigremap from controlling tty, so that the child's session can
+ * attach to it instead.
+ *
+ * We want the child to be able to be the session leader of the TTY so that
+ * it can do normal job control.
+ */
+ if (use_setsid) {
+ if (ioctl(STDIN_FILENO, TIOCNOTTY) == -1) {
+ DEBUG(
+ "Unable to detach from controlling tty (errno=%d %s).\n",
+ errno,
+ strerror(errno));
+ } else {
+ /*
+ * When the session leader detaches from its controlling tty via
+ * TIOCNOTTY, the kernel sends SIGHUP and SIGCONT to the process
+ * group. We need to be careful not to forward these on to the
+ * sigremap child so that it doesn't receive a SIGHUP and
+ * terminate itself (#136).
+ */
+ if (getsid(0) == getpid()) {
+ DEBUG("Detached from controlling tty, ignoring the first SIGHUP and SIGCONT we receive.\n");
+ signal_temporary_ignores[SIGHUP] = 1;
+ signal_temporary_ignores[SIGCONT] = 1;
+ } else {
+ DEBUG("Detached from controlling tty, but was not session leader.\n");
+ }
+ }
+ }
+
+ child_pid = fork();
+ if (child_pid < 0) {
+ print_error("error: unable to fork: %s\n");
+ return 1;
+ } else if (child_pid == 0) {
+ /* child */
+ sigprocmask(SIG_UNBLOCK, &all_signals, NULL);
+ if (use_setsid) {
+ if (setsid() == -1) {
+ print_error("error: unable to setsid: %s\n");
+ exit(1);
+ }
+
+ if (ioctl(STDIN_FILENO, TIOCSCTTY, 0) == -1) {
+ DEBUG(
+ "Unable to attach to controlling tty (errno=%d %s).\n",
+ errno,
+ strerror(errno));
+ }
+ DEBUG("setsid complete.\n");
+ }
+ execvp(cmd[0], cmd);
+
+ // if this point is reached, exec failed, so we should exit nonzero
+ print_error("error: unable to execute %s: %s\n", cmd[0]);
+ _exit(2);
+ }
+
+ /* parent */
+ DEBUG("Child spawned with PID %d.\n", child_pid);
+ for (;;) {
+ sigwait(&all_signals, &signum);
+ handle_signal(signum);
+ }
+}
diff --git a/src/exec/vlogger.c b/src/exec/vlogger.c
@@ -0,0 +1,193 @@
+#include "config.h"
+#include "message.h"
+#include "util.h"
+
+#include <errno.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/syslog.h>
+#include <unistd.h>
+
+static char pwd[PATH_MAX];
+
+typedef struct ident {
+ const char* name;
+ int value;
+} ident_t;
+
+ident_t prioritynames[] = {
+ { "alert", LOG_ALERT },
+ { "crit", LOG_CRIT },
+ { "debug", LOG_DEBUG },
+ { "emerg", LOG_EMERG },
+ { "err", LOG_ERR },
+ { "error", LOG_ERR },
+ { "info", LOG_INFO },
+ { "notice", LOG_NOTICE },
+ { "panic", LOG_EMERG },
+ { "warn", LOG_WARNING },
+ { "warning", LOG_WARNING },
+ { 0, -1 }
+};
+
+ident_t facilitynames[] = {
+ { "auth", LOG_AUTH },
+ { "authpriv", LOG_AUTHPRIV },
+ { "cron", LOG_CRON },
+ { "daemon", LOG_DAEMON },
+ { "ftp", LOG_FTP },
+ { "kern", LOG_KERN },
+ { "lpr", LOG_LPR },
+ { "mail", LOG_MAIL },
+ { "news", LOG_NEWS },
+ { "security", LOG_AUTH },
+ { "syslog", LOG_SYSLOG },
+ { "user", LOG_USER },
+ { "uucp", LOG_UUCP },
+ { "local0", LOG_LOCAL0 },
+ { "local1", LOG_LOCAL1 },
+ { "local2", LOG_LOCAL2 },
+ { "local3", LOG_LOCAL3 },
+ { "local4", LOG_LOCAL4 },
+ { "local5", LOG_LOCAL5 },
+ { "local6", LOG_LOCAL6 },
+ { "local7", LOG_LOCAL7 },
+ { 0, -1 }
+};
+
+static void strpriority(char* facil_str, int* facility, int* level) {
+ char* prio_str = NULL;
+ ident_t* ident;
+
+ if ((prio_str = strchr(facil_str, '.'))) {
+ *prio_str = '\0';
+ prio_str++;
+ for (ident = prioritynames; ident->name; ident++) {
+ if (streq(ident->name, prio_str))
+ *level = ident->value;
+ }
+ }
+ if (*facil_str) {
+ for (ident = facilitynames; ident->name; ident++) {
+ if (streq(ident->name, facil_str))
+ *facility = ident->value;
+ }
+ }
+}
+
+int main(int argc, char* argv[]) {
+ char buf[SV_VLOGGER_BUFFER];
+ char *p, *e, *argv0;
+ char* tag = NULL;
+ int c;
+ bool Sflag = false;
+ int logflags = 0;
+ int facility = LOG_USER;
+ int level = LOG_NOTICE;
+
+ argv0 = *argv;
+
+ if (streq(argv0, "./run")) {
+ // if running as a service, update facility and tag
+ p = getcwd(pwd, sizeof(pwd));
+ if (p != NULL && *pwd == '/') {
+ if (*(p = pwd + (strlen(pwd) - 1)) == '/')
+ *p = '\0';
+ if ((p = strrchr(pwd, '/')) && strncmp(p + 1, "log", 3) == 0 &&
+ (*p = '\0', (p = strrchr(pwd, '/'))) && (*(p + 1) != '\0')) {
+ tag = p + 1;
+ facility = LOG_DAEMON;
+ level = LOG_NOTICE;
+ }
+ }
+ } else if (streq(basename(argv0), "logger")) {
+ /* behave just like logger(1) and only use syslog */
+ Sflag = true;
+ }
+
+ while ((c = getopt(argc, argv, "f:ip:Sst:")) != -1)
+ switch (c) {
+ case 'f':
+ if (freopen(optarg, "r", stdin) == NULL) {
+ print_error("error: unable to reopen %s: %s\n", optarg);
+ return 1;
+ }
+ break;
+ case 'i':
+ logflags |= LOG_PID;
+ break;
+ case 'p':
+ strpriority(optarg, &facility, &level);
+ break;
+ case 'S':
+ Sflag = true;
+ break;
+ case 's':
+ logflags |= LOG_PERROR;
+ break;
+ case 't':
+ tag = optarg;
+ break;
+ default:
+ print_usage_exit(PROG_VLOGGER, 1);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ Sflag = true;
+
+ if (!Sflag && access("/etc/vlogger", X_OK) != -1) {
+ ident_t* ident;
+ const char *sfacility = "", *slevel = "";
+ for (ident = prioritynames; ident->name; ident++) {
+ if (ident->value == level)
+ slevel = ident->name;
+ }
+ for (ident = facilitynames; ident->name; ident++) {
+ if (ident->value == facility)
+ sfacility = ident->name;
+ }
+ execl("/etc/vlogger", argv0, tag ? tag : "", slevel, sfacility, NULL);
+ print_error("error: unable to exec /etc/vlogger: %s\n");
+ exit(1);
+ }
+
+ openlog(tag ? tag : getlogin(), logflags, facility);
+
+ if (argc > 0) {
+ size_t len;
+ p = buf;
+ *p = '\0';
+ e = buf + sizeof(buf) - 2;
+ while (*argv) {
+ len = strlen(*argv);
+ if (p + len > e && p > buf) {
+ syslog(level | facility, "%s", buf);
+ p = buf;
+ *p = '\0';
+ }
+ if (len > sizeof(buf) - 1) {
+ syslog(level | facility, "%s", *argv++);
+ } else {
+ if (p != buf) {
+ *p++ = ' ';
+ *p = '\0';
+ }
+ strncat(p, *argv++, e - p);
+ p += len;
+ }
+ }
+ if (p != buf)
+ syslog(level | facility, "%s", buf);
+ return 0;
+ }
+
+ while (fgets(buf, sizeof(buf), stdin) != NULL)
+ syslog(level | facility, "%s", buf);
+
+ return 0;
+}
diff --git a/src/exec/zzz.c b/src/exec/zzz.c
@@ -0,0 +1,120 @@
+#include "config.h"
+#include "util.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+
+static int open_write(const char* path, const char* string) {
+ int fd;
+
+ if ((fd = open(path, O_WRONLY | O_TRUNC)) == -1) {
+ print_error("cannot open %s: %s\n", path);
+ return -1;
+ }
+ if (write(fd, string, strlen(string)) == -1) {
+ print_error("error writing to %s: %s\n", path);
+ close(fd);
+ return -1;
+ }
+ return close(fd);
+}
+
+
+int main(int argc, char** argv) {
+ int opt;
+ pid_t pid;
+ struct stat st;
+ const char *new_state = "mem",
+ *new_disk = NULL;
+
+ struct option long_options[] = {
+ { "noop", no_argument, 0, 'n' },
+ { "freeze", no_argument, 0, 'S' },
+ { "suspend", no_argument, 0, 'z' },
+ { "hibernate", no_argument, 0, 'Z' },
+ { "reboot", no_argument, 0, 'R' },
+ { "hybrid", no_argument, 0, 'H' },
+ { 0 },
+ };
+
+ while ((opt = getopt_long(argc, argv, "nSzZRH", long_options, NULL)) != -1) {
+ switch (opt) {
+ case 'n':
+ new_state = NULL;
+ new_disk = NULL;
+ break;
+ case 's':
+ new_state = "suspend";
+ new_disk = NULL;
+ break;
+ case 'S':
+ new_state = "freeze";
+ new_disk = NULL;
+ break;
+ case 'z':
+ new_state = "mem";
+ new_disk = NULL;
+ break;
+ case 'Z':
+ new_state = "disk";
+ new_disk = "platform";
+ break;
+ case 'R':
+ new_state = "disk";
+ new_disk = "reboot";
+ break;
+ case 'H':
+ new_state = "disk";
+ new_disk = "suspend";
+ break;
+ default:
+ printf("zzz [-n] [-S] [-z] [-Z] [-R] [-H]\n");
+ return 1;
+ }
+ }
+
+ argc -= optind, argv += optind;
+
+
+ if (stat(SV_SUSPEND_EXEC, &st) == 0 && st.st_mode & S_IXUSR) {
+ if ((pid = fork()) == -1) {
+ print_error("failed to fork for " SV_SUSPEND_EXEC ": %s\n");
+ return 1;
+ } else if (pid == 0) { // child
+ execl(SV_SUSPEND_EXEC, SV_SUSPEND_EXEC, NULL);
+ print_error("failed to execute " SV_SUSPEND_EXEC ": %s\n");
+ _exit(1);
+ }
+
+ wait(NULL);
+ }
+
+ if (new_disk) {
+ open_write("/sys/power/disk", new_disk);
+ }
+
+ if (new_state) {
+ open_write("/sys/power/state", new_state);
+ } else {
+ sleep(5);
+ }
+
+ if (stat(SV_RESUME_EXEC, &st) == 0 && st.st_mode & S_IXUSR) {
+ if ((pid = fork()) == -1) {
+ print_error("failed to fork for " SV_RESUME_EXEC ": %s\n");
+ return 1;
+ } else if (pid == 0) { // child
+ execl(SV_RESUME_EXEC, SV_RESUME_EXEC, NULL);
+ print_error("failed to execute " SV_RESUME_EXEC ": %s\n");
+ _exit(1);
+ }
+
+ wait(NULL);
+ }
+}
diff --git a/src/register.c b/src/register.c
@@ -11,18 +11,6 @@
#include <unistd.h>
-static int fd_set_flag(int fd, int flags) {
- int rc;
-
- if ((rc = fcntl(fd, F_GETFL)) == -1)
- return -1;
-
- if (fcntl(fd, F_SETFL, rc | flags) == -1)
- return -1;
-
- return 0;
-}
-
static int init_supervise(service_t* s) {
int fd;
struct stat st;
@@ -136,6 +124,7 @@ service_t* service_register(int dir, const char* name, bool is_log_service) {
else if (fstatat(s->dir, once_path, &st, 0) != -1 && st.st_mode & S_IREAD)
s->restart_file = S_ONCE;
+ service_write(s);
return s;
}
diff --git a/src/service.c b/src/service.c
@@ -98,6 +98,9 @@ bool service_is_dependency(service_t* d) {
}
bool service_need_restart(service_t* s) {
+ if (!daemon_running)
+ return false;
+
if (s->restart_manual == S_FORCE_DOWN)
return service_is_dependency(s);
diff --git a/src/stage.c b/src/stage.c
@@ -1,29 +1,29 @@
+#include "stage.h"
+
#include "config.h"
-#include "service.h"
#include "util.h"
#include <errno.h>
#include <fcntl.h>
+#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <unistd.h>
-static const char* stage_exec[] = {
- [0] = SV_START_EXEC,
- [2] = SV_STOP_EXEC
-};
+static char* stage_exec[][4] = {
+ [0] = { SV_START_EXEC, NULL },
+ [1] = { SV_SUPERVISE_EXEC, SV_SERVICE_DIR, SV_RUNLEVEL_DEFAULT, NULL },
+ [2] = { SV_STOP_EXEC, NULL },
+};
-void service_stage(int stage) {
- int pid, ttyfd, exitstat, sig = 0;
- sigset_t ss;
- struct sigaction sigact = { 0 };
- // stage = 0 | 2
- if (stage != 0 && stage != 2)
- return;
+bool handle_stage(int stage) {
+ int pid, ttyfd, exitstat, sig = 0;
+ sigset_t ss;
+ bool cont = true;
while ((pid = fork()) == -1) {
print_error("error: unable to fork for stage1: %s\n");
@@ -45,27 +45,22 @@ void service_stage(int stage) {
sigblock_all(true);
-
- sigact.sa_handler = SIG_DFL;
- sigaction(SIGCHLD, &sigact, NULL);
- sigaction(SIGINT, &sigact, NULL);
-
- sigact.sa_handler = SIG_IGN;
- sigaction(SIGCONT, &sigact, NULL);
-
printf("enter stage %d\n", stage);
- execl(stage_exec[stage], stage_exec[stage], NULL);
+ execv(stage_exec[stage][0], stage_exec[stage]);
print_error("error: unable to exec stage %d: %s\n", stage);
_exit(1);
}
sigemptyset(&ss);
sigaddset(&ss, SIGCHLD);
+ sigaddset(&ss, SIGUSR1);
sigaddset(&ss, SIGCONT);
- sigaddset(&ss, SIGINT);
sigwait(&ss, &sig);
+ if (stage == 1 && sig != SIGCHLD)
+ kill(pid, SIGTERM);
+
if (waitpid(pid, &exitstat, 0) == -1) {
print_error("warn: waitpid failed: %s");
sleep(5);
@@ -78,9 +73,11 @@ void service_stage(int stage) {
if (WIFSIGNALED(exitstat)) {
/* this is stage 1 */
fprintf(stderr, "stage 1 failed: skip stage 2\n");
- daemon_running = false;
+ cont = false;
}
}
printf("leave stage 1\n");
}
+
+ return cont;
}
diff --git a/src/status.c b/src/status.c
@@ -11,7 +11,16 @@
#include <unistd.h>
-static void service_update_status(service_t* s) {
+void service_update_state(service_t* s, int state) {
+ if (state != -1)
+ s->state = state;
+
+ s->status_change = time(NULL);
+
+ service_write(s);
+}
+
+void service_write(service_t* s) {
int fd;
const char* stat_human;
struct service_serial stat_runit;
@@ -56,26 +65,3 @@ static void service_update_status(service_t* s) {
renameat(s->dir, "supervise/stat.new", s->dir, "supervise/stat");
renameat(s->dir, "supervise/pid.new", s->dir, "supervise/pid");
}
-
-void service_update_state(service_t* s, int state) {
- if (state != -1)
- s->state = state;
-
- s->status_change = time(NULL);
- service_update_status(s);
-
- for (int i = 0; i < services_size; i++) {
- s = &services[i];
- if (s->state == STATE_DEAD)
- continue;
- if (service_need_restart(s)) {
- if (s->state == STATE_INACTIVE) {
- service_start(s);
- }
- } else {
- if (s->state != STATE_INACTIVE) {
- service_stop(s);
- }
- }
- }
-}
diff --git a/src/supervise.c b/src/supervise.c
@@ -44,6 +44,25 @@ static void signal_child(int unused) {
service_handle_exit(s, WIFSIGNALED(status), WIFSIGNALED(status) ? WTERMSIG(status) : WEXITSTATUS(status));
}
+static void update_services(void) {
+ service_t* s;
+
+ for (int i = 0; i < services_size; i++) {
+ s = &services[i];
+ if (s->state == STATE_DEAD)
+ continue;
+ if (service_need_restart(s)) {
+ if (s->state == STATE_INACTIVE) {
+ service_start(s);
+ }
+ } else {
+ if (s->state != STATE_INACTIVE) {
+ service_stop(s);
+ }
+ }
+ }
+}
+
static void control_sockets(void) {
service_t* s;
char cmd, chr;
@@ -66,6 +85,12 @@ static void control_sockets(void) {
}
}
+void signal_interrupt(int signo) {
+ (void) signo;
+
+ daemon_running = false;
+}
+
int service_supervise(const char* service_dir_, const char* runlevel_) {
struct sigaction sigact = { 0 };
service_t* s;
@@ -74,6 +99,9 @@ int service_supervise(const char* service_dir_, const char* runlevel_) {
sigact.sa_handler = signal_child;
sigaction(SIGCHLD, &sigact, NULL);
+ sigact.sa_handler = signal_interrupt;
+ sigaction(SIGINT, &sigact, NULL);
+ sigaction(SIGTERM, &sigact, NULL);
sigact.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sigact, NULL);
@@ -95,6 +123,7 @@ int service_supervise(const char* service_dir_, const char* runlevel_) {
while (daemon_running) {
service_refresh_directory();
control_sockets();
+ update_services();
sleep(SV_CHECK_INTERVAL);
}
@@ -106,10 +135,11 @@ int service_supervise(const char* service_dir_, const char* runlevel_) {
service_stop(s);
}
- start = time(NULL);
+ running = 0;
+ start = time(NULL);
do {
sleep(1); // sleep for one second
- running = 0;
+ // running = 0;
for (int i = 0; i < services_size; i++) {
if (services[i].state != STATE_INACTIVE)
running++;
diff --git a/src/util.c b/src/util.c
@@ -137,3 +137,17 @@ char* progname(char* path) {
}
return path;
}
+
+int fd_set_flag(int fd, int flags) {
+ int rc;
+
+ if ((rc = fcntl(fd, F_GETFL)) == -1)
+ return -1;
+
+ rc |= flags;
+
+ if (fcntl(fd, F_SETFL, rc) == -1)
+ return -1;
+
+ return rc;
+}