commit 1c09f37f344b03401fe2a2541b7a75b5eae6676d
parent 07baace6078efed8cdaf2292191288d30e0855ab
Author: Friedel Schön <[email protected]>
Date: Sun, 4 Feb 2024 13:22:32 +0100
new make tools
Diffstat:
105 files changed, 3401 insertions(+), 3257 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,68 +1,21 @@
--include config.mk
+TOPDIR = .
+-include $(TOPDIR)/config.mk
-ifeq ($(BINARIES),)
-$(error 'config.mk' is missing, run ./configure)
-endif
+SUBDIRS += src docs
-VERSION := 0.3.3
+include $(TOPDIR)/mk/subdir.mk
-SRC_DIR := src
-INCLUDE_DIR := include
-BIN_DIR := bin
-MAN_DIR := man
-DOCS_DIR := docs
-ASSETS_DIR := assets
-TOOLS_DIR := tools
-TARGET_DIR := target
+install: install-static
-TARGET_OBJECT_DIR := $(TARGET_DIR)/obj
-TARGET_BIN_DIR := $(TARGET_DIR)/bin
-TARGET_MAN_DIR := $(TARGET_DIR)/man
-TARGET_DOCS_DIR := $(TARGET_DIR)/docs
-TARGET_ASSETS_DIR := $(TARGET_DOCS_DIR)/assets
+install-static:
+ $(SILENT)install -d $(EPREFIX)/fiss
+ $(SILENT)for file in resume start stop suspend; do \
+ echo "[INST] $(EPREFIX)/fiss/$$file"; \
+ install -m 755 etc/$$file $(EPREFIX)/fiss; \
+ done
-TARGET_DIRS := $(TARGET_DIR) $(TARGET_OBJECT_DIR) $(TARGET_BIN_DIR) \
- $(TARGET_DOCS_DIR) $(TARGET_MAN_DIR)
+uninstall: uninstall-static
-SOURCE_FILES := $(wildcard $(SRC_DIR)/*.c)
-EXEC_FILES := $(wildcard $(BIN_DIR)/*)
-OBJ_FILES := $(patsubst $(SRC_DIR)/%.c,$(TARGET_OBJECT_DIR)/%.o,$(SOURCE_FILES))
-BIN_FILES := $(patsubst %,$(TARGET_BIN_DIR)/%,$(BINARIES))
-INCLUDE_FILES := $(wildcard $(INCLUDE_DIR)/*.h)
-
-MAN_FILES := $(wildcard $(MAN_DIR)/*.txt)
-TEMPL_FILES := $(wildcard $(DOCS_DIR)/*.txt)
-
-ROFF_FILES := $(patsubst $(MAN_DIR)/%.txt,$(TARGET_MAN_DIR)/%,$(MAN_FILES))
-DOCS_FILES := $(patsubst $(DOCS_DIR)/%.txt,$(TARGET_DOCS_DIR)/%.html,$(TEMPL_FILES)) \
- $(patsubst $(MAN_DIR)/%.txt,$(TARGET_DOCS_DIR)/%.html,$(MAN_FILES))
-
-CFLAGS += -I$(INCLUDE_DIR) -I. -DSV_VERSION=\"$(VERSION)\" -g -std=gnu99
-LDFLAGS +=
-
-ifeq ($(VERBOSE),)
-SILENT := @
-endif
-
-# Magic targets
-.PHONY: all clean manual binary documentation
-
-.PRECIOUS: $(OBJ_FILES) $(patsubst $(BIN_DIR)/%.c,$(TARGET_OBJECT_DIR)/%.o,$(EXEC_FILES))
-
-
-# Default target
-all: compile_flags.txt binary manual documentation
-
-# Clean target
-clean:
- @echo "[ RM ] $(TARGET_DIRS)"
- $(SILENT)rm -rf $(TARGET_DIRS)
-
-binary: $(BIN_FILES)
-
-manual: $(ROFF_FILES)
-
-documentation: $(DOCS_FILES)
-
-include mk/target.mk
-include mk/install.mk
+uninstall-static:
+ @echo "[RM] $(EPREFIX)/fiss"
+ $(SILENT)rm -vfr $(EPREFIX)/fiss
+\ No newline at end of file
diff --git a/bin/chpst.c b/bin/chpst.c
@@ -1,352 +0,0 @@
-// +objects: parse.o util.o
-
-#include "parse.h"
-#include "util.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <grp.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/file.h>
-#include <sys/resource.h>
-
-
-const char* current_prog(void) {
- return "chpst";
-}
-
-void limit(int what, rlim_t l) {
- struct rlimit r;
-
- if (getrlimit(what, &r) == -1)
- fprintf(stderr, "error: unable to getrlimit\n");
-
- if (l < 0) {
- r.rlim_cur = 0;
- } else if (l > r.rlim_max)
- r.rlim_cur = r.rlim_max;
- else
- r.rlim_cur = l;
-
- if (setrlimit(what, &r) == -1)
- fprintf(stderr, "error: unable to setrlimit\n");
-}
-
-
-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 limitd = -2,
- limits = -2,
- limitl = -2,
- limita = -2,
- limito = -2,
- limitp = -2,
- limitf = -2,
- limitc = -2,
- limitr = -2,
- limitt = -2;
- long nicelevel = 0;
- bool ssid = false;
- bool closestd[3] = { false, false, false };
-
- if (streq(argv[0], "setuidgid") || streq(argv[0], "envuidgid")) {
- if (argc < 2) {
- fprintf(stderr, "%s <uid-gid> command...", argv[0]);
- return 1;
- }
- gid_len = parse_ugid(argv[1], &uid, gid);
- argv += 2, argc -= 2;
- } else if (streq(argv[0], "pgrphack")) {
- ssid = true;
- argv += 1, argc -= 1;
- } else if (streq(argv[0], "setlock")) {
- while ((opt = getopt(argc, argv, "+xXnN")) != -1) {
- switch (opt) {
- case 'n':
- lockflags = LOCK_EX | LOCK_NB;
- break;
- case 'N':
- lockflags = LOCK_EX;
- break;
- case 'x':
- case 'X':
- fprintf(stderr, "warning: '-%c' is ignored\n", optopt);
- break;
- case '?':
- fprintf(stderr, "%s [-xXnN] command...", argv[0]);
- return 1;
- }
- }
- argv += optind, argc -= optind;
- if (argc < 1) {
- fprintf(stderr, "%s [-xXnN] command...", argv[0]);
- return 1;
- }
- lock = argv[0];
- argv += 1, argc -= 1;
- } else if (streq(argv[0], "softlimit")) {
- while ((opt = getopt(argc, argv, "+a:c:d:f:l:m:o:p:r:s:t:")) != -1) {
- switch (opt) {
- case 'm':
- limits = limitl = limita = limitd = parse_long(optarg, "limit");
- break;
- case 'a':
- limita = parse_long(optarg, "limit");
- break;
- case 'd':
- limitd = parse_long(optarg, "limit");
- break;
- case 'o':
- limito = parse_long(optarg, "limit");
- break;
- case 'p':
- limitp = parse_long(optarg, "limit");
- break;
- case 'f':
- limitf = parse_long(optarg, "limit");
- break;
- case 'c':
- limitc = parse_long(optarg, "limit");
- break;
- case 'r':
- limitr = parse_long(optarg, "limit");
- break;
- case 't':
- limitt = parse_long(optarg, "limit");
- break;
- case 'l':
- limitl = parse_long(optarg, "limit");
- break;
- case 's':
- limits = parse_long(optarg, "limit");
- break;
- case '?':
- fprintf(stderr, "softlimit command...");
- return 1;
- }
- }
- argv += optind, argc -= optind;
- } else {
- if (!streq(argv[0], "chpst"))
- fprintf(stderr, "warning: program-name unsupported, asuming `chpst`\n");
-
- 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 'm':
- limits = limitl = limita = limitd = parse_long(optarg, "limit");
- break;
- case 'd':
- limitd = parse_long(optarg, "limit");
- break;
- case 'o':
- limito = parse_long(optarg, "limit");
- break;
- case 'p':
- limitp = parse_long(optarg, "limit");
- break;
- case 'f':
- limitf = parse_long(optarg, "limit");
- break;
- case 'c':
- limitc = parse_long(optarg, "limit");
- break;
- case 'r':
- limitr = parse_long(optarg, "limit");
- break;
- case 't':
- limitt = parse_long(optarg, "limit");
- break;
- case 'e':
- fprintf(stderr, "warning: '-%c' is ignored\n", optopt);
- break;
- case '?':
- fprintf(stderr, "usage\n");
- return 1;
- }
- }
- argv += optind, argc -= optind;
- }
-
- if (argc == 0) {
- fprintf(stderr, "%s: command required\n", argv[0]);
- return 1;
- }
-
- if (ssid) {
- setsid();
- }
-
- if (uid) {
- setgroups(gid_len, gid);
- setgid(gid[0]);
- setuid(uid);
- // $EUID
- }
-
- if (root) {
- if (chroot(root) == -1)
- print_errno("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_errno("unable to set nice level: %s\n");
- }
-
- if (limitd >= -1) {
-#ifdef RLIMIT_DATA
- limit(RLIMIT_DATA, limitd);
-#else
- if (verbose)
- fprintf(stderr, "system does not support RLIMIT_DATA\n");
-#endif
- }
- if (limits >= -1) {
-#ifdef RLIMIT_STACK
- limit(RLIMIT_STACK, limits);
-#else
- if (verbose)
- fprintf(stderr, "system does not support RLIMIT_STACK\n");
-#endif
- }
- if (limitl >= -1) {
-#ifdef RLIMIT_MEMLOCK
- limit(RLIMIT_MEMLOCK, limitl);
-#else
- if (verbose)
- fprintf(stderr, "system does not support RLIMIT_MEMLOCK\n");
-#endif
- }
- if (limita >= -1) {
-#ifdef RLIMIT_VMEM
- limit(RLIMIT_VMEM, limita);
-#else
-# ifdef RLIMIT_AS
- limit(RLIMIT_AS, limita);
-# else
- if (verbose)
- fprintf(stderr, "system does neither support RLIMIT_VMEM nor RLIMIT_AS\n");
-# endif
-#endif
- }
- if (limito >= -1) {
-#ifdef RLIMIT_NOFILE
- limit(RLIMIT_NOFILE, limito);
-#else
-# ifdef RLIMIT_OFILE
- limit(RLIMIT_OFILE, limito);
-# else
- if (verbose)
- fprintf(stderr, "system does neither support RLIMIT_NOFILE nor RLIMIT_OFILE\n");
-# endif
-#endif
- }
- if (limitp >= -1) {
-#ifdef RLIMIT_NPROC
- limit(RLIMIT_NPROC, limitp);
-#else
- if (verbose)
- fprintf(stderr, "system does not support RLIMIT_NPROC\n");
-#endif
- }
- if (limitf >= -1) {
-#ifdef RLIMIT_FSIZE
- limit(RLIMIT_FSIZE, limitf);
-#else
- if (verbose)
- fprintf(stderr, "system does not support RLIMIT_FSIZE\n");
-#endif
- }
- if (limitc >= -1) {
-#ifdef RLIMIT_CORE
- limit(RLIMIT_CORE, limitc);
-#else
- if (verbose)
- fprintf(stderr, "system does not support RLIMIT_CORE\n");
-#endif
- }
- if (limitr >= -1) {
-#ifdef RLIMIT_RSS
- limit(RLIMIT_RSS, limitr);
-#else
- if (verbose)
- fprintf(stderr, "system does not support RLIMIT_RSS\n");
-#endif
- }
- if (limitt >= -1) {
-#ifdef RLIMIT_CPU
- limit(RLIMIT_CPU, limitt);
-#else
- if (verbose)
- fprintf(stderr, "system does not support RLIMIT_CPU\n");
-#endif
- }
-
- if (lock) {
- if ((lockfd = open(lock, O_WRONLY | O_APPEND)) == -1)
- print_errno("unable to open lock: %s\n");
-
- if (flock(lockfd, lockflags) == -1)
- print_errno("unable to lock: %s\n");
- }
-
- if (closestd[0] && close(0) == -1)
- print_errno("unable to close stdin: %s\n");
- if (closestd[1] && close(1) == -1)
- print_errno("unable to close stdout: %s\n");
- if (closestd[2] && close(2) == -1)
- print_errno("unable to close stderr: %s\n");
-
- exec = argv[0];
- if (arg0)
- argv[0] = arg0;
-
- execvp(exec, argv);
- print_errno("cannot execute: %s\n");
-}
diff --git a/bin/finit.c b/bin/finit.c
@@ -1,141 +0,0 @@
-// +objects: message.o util.o supervise.o service.o start.o stop.o
-// +objects: register.o handle_exit.o handle_command.o
-// +objects: encode.o dependency.o status.o stage.o
-// +flags: -static
-
-#include "config.h"
-#include "message.h"
-#include "service.h"
-#include "stage.h"
-#include "util.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/reboot.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-
-const char* current_prog(void) {
- return "finit";
-}
-
-static bool do_reboot;
-
-static int handle_initctl(int argc, const char** argv) {
- int sig;
-
- if (argc != 2 || argv[1][1] != '\0' || (argv[1][0] != '0' && argv[1][0] != '6')) {
- print_usage_exit(PROG_FINIT, 1);
- }
- if (getuid() != 0) {
- fprintf(stderr, "error: can only be run as root...\n");
- return 1;
- }
- sig = argv[1][0] == '0' ? SIGTERM : SIGINT;
- if (kill(1, sig) == -1) {
- print_errno("error: unable to kill init: %s\n");
- return 1;
- }
- return 0;
-}
-
-static void signal_interrupt(int signum) {
- daemon_running = false;
- do_reboot = signum == SIGINT;
-}
-
-
-int main(int argc, const char** argv) {
- sigset_t ss;
- pid_t pid;
-
- if (getpid() != 1) {
- return handle_initctl(argc, argv);
- }
- setsid();
-
- sigblock_all(false);
-
- reclaim_console();
-
- // disable ctrl-alt-delete
- reboot(0);
-
- printf("booting...\n");
-
- daemon_running = true;
-
- // stage 1
- handle_stage(0);
-
-
- // stage 2
- if (daemon_running) { // stage1 succeed
- struct sigaction sigact = { 0 };
-
- sigblock_all(true);
-
- sigact.sa_handler = signal_interrupt;
- sigaction(SIGTERM, &sigact, NULL);
- sigaction(SIGINT, &sigact, NULL);
-
- service_supervise(SV_SERVICE_DIR, SV_BOOT_SERVICE, false);
- sigblock_all(false);
- }
-
- // stage 3
- handle_stage(2);
-
-#ifdef RB_AUTOBOOT
- /* fallthrough stage 3 */
- printf("sending KILL signal to all processes...\n");
- kill(-1, SIGKILL);
-
- if ((pid = fork()) <= 0) {
- if (do_reboot) {
- printf("system reboot\n");
- sync();
- reboot(RB_AUTOBOOT);
- } else {
-# if defined(RB_POWER_OFF)
- printf("system power off\n");
- sync();
- reboot(RB_POWER_OFF);
- sleep(2);
-# elif defined(RB_HALT_SYSTEM)
- printf("system halt\n");
- sync();
- reboot(RB_HALT_SYSTEM);
-# elif define(RB_HALT)
- printf("system halt\n");
- sync();
- reboot(RB_HALT);
-# else
- printf("system reboot\n");
- sync();
- reboot(RB_AUTOBOOT);
-# endif
- }
- if (pid == 0)
- _exit(0);
- } else {
- sigemptyset(&ss);
- sigaddset(&ss, SIGCHLD);
- sigprocmask(SIG_UNBLOCK, &ss, NULL);
-
- while (waitpid(pid, NULL, 0) != -1) {}
- }
-#endif
-
- sigfillset(&ss);
- for (;;)
- sigsuspend(&ss);
-
- /* not reached */
- printf("exit.\n");
- return 0;
-}
diff --git a/bin/fsvc.c b/bin/fsvc.c
@@ -1,327 +0,0 @@
-// +objects: message.o util.o signame.o
-
-#include "config.h"
-#include "message.h"
-#include "service.h"
-#include "signame.h"
-#include "util.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <getopt.h>
-#include <limits.h>
-#include <stdbool.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-
-const char* current_prog(void) {
- return "fsvc";
-}
-
-static const char* const command_names[][2] = {
- { "up", "u" }, // starts the services, pin as started
- { "down", "d" }, // stops the service, pin as stopped
- { "once", "o" }, // same as xup
- { "term", "t" }, // same as down
- { "kill", "k" }, // sends kill, pin as stopped
- { "pause", "p" }, // pauses the service
- { "cont", "c" }, // resumes the service
- { "reset", "r" }, // resets the service
- { "alarm", "a" }, // sends alarm
- { "hup", "h" }, // sends hup
- { "int", "i" }, // sends interrupt
- { "quit", "q" }, // sends quit
- { "1", "1" }, // sends usr1
- { "2", "2" }, // sends usr2
- { "usr1", "1" }, // sends usr1
- { "usr2", "2" }, // sends usr2
- { "exit", "x" }, // does nothing
- { "restart", "!du" }, // restarts the service, don't modify pin
- { "start", "!u" }, // start the service, pin as started, print status
- { "stop", "!d" }, // stop the service, pin as stopped, print status
- { "status", "!" }, // print status
- { "check", "!" }, // print status
- { "enable", "!.e" }, // enable service
- { "disable", "!.d" }, // disable service
- { 0, 0 }
-};
-
-static const struct option long_options[] = {
- { "version", no_argument, NULL, 'V' },
- { "wait", no_argument, NULL, 'w' },
- { 0 }
-};
-
-struct service_decode {
- int state;
- pid_t pid;
- time_t state_change;
- bool restart;
- bool once;
- bool is_depends;
- bool wants_up;
- int last_exit;
- int return_code;
- uint8_t fail_count;
- bool paused;
- bool is_terminating;
-};
-
-static void decode(struct service_decode* s, const struct service_serial* buffer) {
- uint64_t tai = ((uint64_t) buffer->status_change[0] << 56) |
- ((uint64_t) buffer->status_change[1] << 48) |
- ((uint64_t) buffer->status_change[2] << 40) |
- ((uint64_t) buffer->status_change[3] << 32) |
- ((uint64_t) buffer->status_change[4] << 24) |
- ((uint64_t) buffer->status_change[5] << 16) |
- ((uint64_t) buffer->status_change[6] << 8) |
- ((uint64_t) buffer->status_change[7] << 0);
-
- s->state_change = tai - 4611686018427387914ULL;
-
- s->state = buffer->state;
- s->return_code = buffer->return_code;
- s->fail_count = buffer->fail_count;
- s->is_terminating = (buffer->flags >> 4) & 0x01;
- s->once = (buffer->flags >> 3) & 0x01;
- s->restart = (buffer->flags >> 2) & 0x01;
- s->last_exit = (buffer->flags >> 0) & 0x03;
-
- s->pid = (buffer->pid[0] << 0) |
- (buffer->pid[1] << 8) |
- (buffer->pid[2] << 16) |
- (buffer->pid[3] << 24);
-
- s->paused = buffer->paused;
- s->wants_up = buffer->restart == 'u';
-
- s->is_depends = s->wants_up != (s->once || s->restart);
-}
-
-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) {
- // no custom commands defined
-
- (void) dir, (void) command;
-
- return -1;
-}
-
-static int send_command(int dir, const char* command) {
- 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) == -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;
- struct service_decode 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);
-
- decode(&s, &buffer);
-
- timeval = time(NULL) - s.state_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_ERROR:
- printf("dead (error)");
- break;
- }
-
- if (s.paused)
- printf(" & paused");
-
- printf(" since %lu%s", timeval, timeunit);
-
- if (s.once == S_ONCE)
- printf(", started once");
-
- if (s.restart)
- printf(", should restart");
-
- if (s.is_depends)
- 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(", crashed 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, fd,
- timeout = SV_STATUS_WAIT;
- time_t mod, start;
-
- const char* command = NULL;
- const char* service;
-
- 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 (const char** ident = (void*) command_names; ident[0] != NULL; ident++) {
- if (streq(ident[0], argv[0])) {
- command = ident[1];
- 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++) {
- service = progname(argv[i]);
-
- if ((dir = open(argv[i], O_DIRECTORY)) == -1) {
- fprintf(stderr, "error: %s: cannot open directory: %s\n", argv[i], strerror(errno));
- continue;
- }
-
- if ((fd = openat(dir, "supervise/ok", O_WRONLY | O_NONBLOCK)) == -1) {
- fprintf(stderr, "error: %s: cannot open supervise/control: %s\n", argv[i], strerror(errno));
- continue;
- }
- close(fd);
-
- if ((mod = get_mtime(dir)) == -1) {
- fprintf(stderr, "error: %s: cannot get modify-time\n", argv[i]);
- continue;
- }
-
- if (command[0] != '\0') {
- if (send_command(dir, command) == -1) {
- fprintf(stderr, "error: %s: unable to send command\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: ", service);
-
- if (status(dir) == -1)
- printf("unable to access supervise/status\n");
- }
- }
-}
diff --git a/bin/fsvs.c b/bin/fsvs.c
@@ -1,72 +0,0 @@
-// +objects: message.o util.o supervise.o service.o start.o stop.o
-// +objects: register.o handle_exit.o handle_command.o
-// +objects: encode.o parse.o dependency.o status.o
-
-#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>
-
-
-const char* current_prog(void) {
- return "fsvs";
-}
-
-static const struct option long_options[] = {
- { "version", no_argument, 0, 'V' },
- { "once", no_argument, 0, 'o' },
- { 0 }
-};
-
-static void signal_interrupt(int signum) {
- (void) signum;
-
- daemon_running = false;
-}
-
-int main(int argc, char** argv) {
- int c;
- bool once = false;
- while ((c = getopt_long(argc, argv, ":Vo", long_options, NULL)) > 0) {
- switch (c) {
- case 'V':
- print_version_exit();
- break;
- case 'o':
- once = true;
- 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_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);
- }
-
- struct sigaction sa = { 0 };
- sa.sa_handler = signal_interrupt;
- sigaction(SIGINT, &sa, NULL);
- sigaction(SIGTERM, &sa, NULL);
-
- return service_supervise(argv[0], argv[1], once);
-}
diff --git a/bin/halt.c b/bin/halt.c
@@ -1,85 +0,0 @@
-// +objects: wtmp.o util.o
-
-#include "util.h"
-#include "wtmp.h"
-
-#include <errno.h>
-#include <stdbool.h>
-#include <string.h>
-#include <sys/reboot.h>
-#include <unistd.h>
-
-
-const char* current_prog(void) {
- return "halt";
-}
-
-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_errno("reboot failed: %s\n");
- }
-
- return 0;
-}
diff --git a/bin/modules-load.c b/bin/modules-load.c
@@ -1,145 +0,0 @@
-// +objects: util.o
-
-#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
-
-
-const char* current_prog(void) {
- return "modules-load";
-}
-
-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_errno("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_errno("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/");
-
- if (modules_size == 0)
- return 0;
-
- 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_errno("cannot exec modprobe: %s");
- return 1;
-}
diff --git a/bin/shutdown.sh b/bin/shutdown.sh
@@ -1,69 +0,0 @@
-#!/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/bin/sigremap.c b/bin/sigremap.c
@@ -1,284 +0,0 @@
-/* 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.
- */
-
-// +objects: message.o signame.o
-
-
-#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>
-
-
-const char* current_prog(void) {
- return "sigremap";
-}
-
-#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_errno("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_errno("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_errno("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/bin/vlogger.c b/bin/vlogger.c
@@ -1,200 +0,0 @@
-// +objects: message.o
-
-#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>
-
-
-const char* current_prog(void) {
- return "vlogger";
-}
-
-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_errno("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_errno("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/bin/zzz.c b/bin/zzz.c
@@ -1,136 +0,0 @@
-#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>
-
-
-const char* current_prog(void) {
- return "zzz";
-}
-
-static int open_write(const char* path, const char* string) {
- int fd;
-
- if ((fd = open(path, O_WRONLY | O_TRUNC)) == -1) {
- print_errno("cannot open %s: %s\n", path);
- return -1;
- }
- if (write(fd, string, strlen(string)) == -1) {
- print_errno("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 = NULL,
- *new_disk = NULL;
-
- if (streq(argv[0], "zzz")) {
- new_state = "mem";
- new_disk = NULL;
- } else if (streq(argv[0], "ZZZ")) {
- new_state = "disk";
- new_disk = "platform";
- } else {
- fprintf(stderr, "error: program-name `%s` invalid\n", argv[0]);
- return 1;
- }
-
- 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_errno("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_errno("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_errno("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_errno("failed to execute " SV_RESUME_EXEC ": %s\n");
- _exit(1);
- }
-
- wait(NULL);
- }
-}
diff --git a/config.h b/config.h
@@ -0,0 +1,57 @@
+#pragma once
+
+/// seconds to wait for a service before it gets killed
+#define SV_STOP_TIMEOUT 5
+
+// maximal characters a service-dir can have
+#define SV_NAME_MAX 128
+
+// maximal amount a service may fail
+#define SV_FAIL_MAX 32
+
+// maximal amount of services that can be registered
+#define SV_SERVICE_MAX 128
+
+// time to wait to accept new connection
+#define SV_CHECK_INTERVAL 3
+
+// maximal size of a param in ./params
+#define SV_PARAM_FILE_LINE_MAX 1024
+
+// maximal size of a param in ./env
+#define SV_ENV_FILE_LINE_MAX 1024
+
+// max dependencies in the dep-tree
+#define SV_DEPENDENCY_MAX 128
+
+// max arguments a service can have
+#define SV_ARGUMENTS_MAX 16
+
+// maximal count of environment variables in ./env
+#define SV_ENV_MAX 16
+
+#define SV_USER_BUFFER 256
+
+#define SV_USER_GROUP_MAX 32
+
+#define SV_VLOGGER_BUFFER 1024
+
+#define SV_STATUS_WAIT 5
+
+/// service to start at boot
+#define SV_BOOT_SERVICE "boot"
+
+/// log directory
+#define SV_LOG_DIR "/var/log/fiss"
+/// rescue shell
+#define SV_RESCUE_SHELL "/bin/bash"
+/// service directory
+#define SV_SERVICE_DIR "/etc/service.d"
+/// start executable
+#define SV_START_EXEC "/usr/share/fiss/start"
+/// stop executable
+#define SV_STOP_EXEC "/usr/share/fiss/stop"
+/// suspend executable
+#define SV_SUSPEND_EXEC "/usr/share/fiss/suspend"
+/// resume executable
+#define SV_RESUME_EXEC "/usr/share/fiss/resume"
diff --git a/config.mk b/config.mk
@@ -0,0 +1,13 @@
+.SUFFIXES:
+
+VERSION = v0.4.0
+IN_REPLACE := 's/%VERSION%/$(VERSION)/g'
+SILENT = @
+
+CC := gcc
+CFLAGS := -g -Wall -Wextra -Wpedantic -Wno-gnu-zero-variadic-macro-arguments -I$(TOPDIR) -DSV_VERSION=\"$(VERSION)\" -g -std=gnu99
+LDFLAGS := -fPIE
+
+SED := sed
+PYTHON := python3
+AWK := awk
diff --git a/docs/Makefile b/docs/Makefile
@@ -0,0 +1,6 @@
+TOPDIR=..
+-include $(TOPDIR)/config.mk
+
+PAGES=index.html
+
+include $(TOPDIR)/mk/prog.mk
+\ No newline at end of file
diff --git a/share/fiss/crypt.awk b/etc/crypt.awk
diff --git a/share/fiss/resume b/etc/resume
diff --git a/share/fiss/start b/etc/start
diff --git a/share/fiss/stop b/etc/stop
diff --git a/share/fiss/suspend b/etc/suspend
diff --git a/share/fiss/utils b/etc/utils
diff --git a/include/message.h b/include/message.h
@@ -1,19 +0,0 @@
-#pragma once
-
-#define FISS_VERSION_STRING "fiss version v" SV_VERSION ""
-
-typedef enum prog {
- PROG_FINIT,
- PROG_FSVC,
- PROG_FSVS,
- PROG_HALT,
- PROG_POWEROFF,
- PROG_REBOOT,
- PROG_SEEDRNG,
- PROG_SIGREMAP,
- PROG_VLOGGER,
- PROG_ZZZ
-} prog_t;
-
-void print_usage_exit(prog_t prog, int status) __attribute__((noreturn));
-void print_version_exit(void) __attribute__((noreturn));
diff --git a/include/parse.h b/include/parse.h
@@ -1,10 +0,0 @@
-#pragma once
-
-#include "service.h"
-
-#include <unistd.h>
-
-
-void parse_env_file(char** env);
-void parse_param_file(struct service* s, char* args[]);
-int parse_ugid(char* str, uid_t* uid, gid_t* gids);
diff --git a/include/service.h b/include/service.h
@@ -1,112 +0,0 @@
-#pragma once
-
-#include "config.h"
-#include "util.h"
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <time.h>
-
-
-enum service_command {
- X_UP = 'u', // starts the services, pin as started
- X_DOWN = 'd', // stops the service, pin as stopped
- X_ONCE = 'o', // starts the service, pin as once
- X_TERM = 't', // same as down
- X_KILL = 'k', // sends kill, pin as stopped
- X_PAUSE = 'p', // pauses the service
- X_CONT = 'c', // resumes the service
- X_RESET = 'r', // resets the service
- X_ALARM = 'a', // sends alarm
- X_HUP = 'h', // sends hup
- X_INT = 'i', // sends interrupt
- X_QUIT = 'q', // sends quit
- X_USR1 = '1', // sends usr1
- X_USR2 = '2', // sends usr2
- X_EXIT = 'x', // does nothing
-};
-
-enum service_state {
- STATE_INACTIVE, // not started
- STATE_SETUP, // ./setup running
- STATE_STARTING, // ./start running
- STATE_ACTIVE_FOREGROUND, // ./run running
- STATE_ACTIVE_BACKGROUND, // ./start finished, ./stop not called yet
- STATE_ACTIVE_DUMMY, // dependencies started
- STATE_STOPPING, // ./stop running
- STATE_FINISHING, // ./finish running
- STATE_DONE, // ./stop finished
- STATE_ERROR, // something went wrong
-};
-
-enum service_exit {
- EXIT_NONE, // never exited
- EXIT_NORMAL, // exited
- EXIT_SIGNALED, // crashed
-};
-
-enum service_restart {
- S_DOWN, // service should not be started
- S_ONCE, // service should
- S_RESTART, // service should be started
-};
-
-struct service_serial {
- uint8_t status_change[8];
- uint8_t state;
- uint8_t return_code;
- uint8_t fail_count;
- uint8_t flags;
- uint8_t pid[4];
- uint8_t paused;
- uint8_t restart;
- uint8_t force_down;
- uint8_t state_runit;
-};
-
-struct service {
- char name[SV_NAME_MAX]; // name of service
- enum service_state state; // current state
- pid_t pid; // pid of run
- int dir; // dirfd
- int control; // fd to supervise/control
- time_t state_change; // last status change
- int restart; // should restart on exit
- enum service_exit last_exit; // stopped signaled or exited
- int return_code; // return code or signal
- uint8_t fail_count; // current fail cound
- bool is_log_service; // is a log service
- bool paused; // is paused
- time_t stop_timeout; // stop start-time
- pipe_t log_pipe; // pipe for logging
- struct service* log_service; // has a log_server otherwise NULL
-};
-
-extern struct service services[];
-extern int services_size;
-extern int service_dir;
-extern int null_fd;
-extern bool daemon_running;
-extern struct service* depends[][2];
-extern int depends_size;
-extern const char* service_dir_path;
-
-
-void service_encode(struct service* s, struct service_serial* buffer);
-struct service* service_get(const char* name);
-void service_handle_command(struct service* s, char command);
-void service_handle_exit(struct service* s, bool signaled, int return_code);
-void service_kill(struct service* s, int signal);
-bool service_need_restart(struct service* s);
-int service_refresh_directory(void);
-struct service* service_register(int dir, const char* name, bool is_log_service);
-void service_run(struct service* s);
-int service_send_command(char command, char extra, const char* service, struct service* response, int response_max);
-void service_start(struct service* s);
-const char* service_status_name(struct service* s);
-void service_stop(struct service* s);
-int service_supervise(const char* service_dir_, const char* service, bool once);
-void service_update_dependency(struct service* s);
-bool service_is_dependency(struct service* s);
-void service_update_state(struct service* s, int state);
-void service_write(struct service* s);
diff --git a/include/util.h b/include/util.h
@@ -1,26 +0,0 @@
-#pragma once
-
-#include <stdio.h>
-
-#define streq(a, b) (!strcmp((a), (b)))
-
-#define print_errno(msg, ...) (fprintf(stderr, "%s: " msg, current_prog(), ##__VA_ARGS__, strerror(errno)))
-#define print_error(msg, ...) (fprintf(stderr, "%s: " msg, current_prog(), ##__VA_ARGS__))
-
-typedef struct {
- int read;
- int write;
-} pipe_t;
-
-const char* current_prog(void); // has to be defined per program
-
-ssize_t dgetline(int fd, char* line, size_t line_buffer);
-ssize_t readstr(int fd, char* str);
-ssize_t writestr(int fd, const char* str);
-unsigned int stat_mode(const char* format, ...);
-int fork_dup_cd_exec(int dir, const char* path, int fd0, int fd1, int fd2);
-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/mk/extract-flags.sh b/mk/extract-flags.sh
@@ -1,7 +0,0 @@
-#!/bin/bash
-
-[ -n "$2" ] && for obj in $(grep -E '^//\s+\+objects:' $1 | sed -E 's/\/\/\s+\+objects://'); do
- echo -n "$2/$obj "
-done
-
-grep -E '^//\s+\+flags:' $1 | sed -E "s/\/\/\s+\+flags://"
diff --git a/mk/install.mk b/mk/install.mk
@@ -1,48 +0,0 @@
-.PHONY: install install-binary install-documentation install-binary install-share install-etc
-
-install: install-binary \
- install-documentation \
- install-manual \
- install-share \
- install-etc
-
-install-binary: $(BIN_FILES)
-ifneq ($(INSTALL_SBIN),)
- @install -vd $(INSTALL_PREFIX)/$(INSTALL_SBIN)
- @for file in $(BIN_FILES); do \
- install -v -m 755 $$file $(INSTALL_PREFIX)/$(INSTALL_SBIN); \
- done
-endif
-
-install-documentation: $(DOCS_FILES)
-ifneq ($(INSTALL_DOCS),)
- @install -vd $(INSTALL_PREFIX)/$(INSTALL_DOCS)
- @for file in $(DOCS_FILES); do \
- install -v -m 644 $$file $(INSTALL_PREFIX)/$(INSTALL_DOCS); \
- done
-endif
-
-install-manual: $(ROFF_FILES)
-ifneq ($(INSTALL_MAN8),)
- @install -vd $(INSTALL_PREFIX)/$(INSTALL_MAN8)
- @for file in $(ROFF_FILES); do \
- install -v -m 644 $$file $(INSTALL_PREFIX)/$(INSTALL_MAN8); \
- done
-endif
-
-install-share:
-ifneq ($(INSTALL_SHARE),)
- @install -vd $(INSTALL_PREFIX)/$(INSTALL_SHARE)/fiss
- @for file in share/fiss/*; do \
- install -v -m 644 $$file $(INSTALL_PREFIX)/$(INSTALL_SHARE)/fiss; \
- done
-endif
-
-install-etc:
-ifneq ($(INSTALL_ETC),)
- @install -vd $(INSTALL_PREFIX)/$(INSTALL_ETC)/service.d
- @install -vd $(INSTALL_PREFIX)/$(INSTALL_ETC)/start.d
- @install -vd $(INSTALL_PREFIX)/$(INSTALL_ETC)/stop.d
- @install -vd $(INSTALL_PREFIX)/$(INSTALL_ETC)/zzz.d/resume
- @install -vd $(INSTALL_PREFIX)/$(INSTALL_ETC)/zzz.d/suspend
-endif
diff --git a/mk/prog.mk b/mk/prog.mk
@@ -0,0 +1,90 @@
+-include $(TOPDIR)/config.mk
+
+.PHONY: all binaries pages manuals clean
+.PHONY: install install-binaries install-manuals install-pages
+.PHONY: uninstall uninstall-binaries uninstall-manuals uninstall-pages
+
+all: binaries pages manuals
+
+binaries: $(BINS)
+
+manuals: $(MANS)
+
+pages: $(PAGES)
+
+clean:
+ @echo "[ RM ] $(OBJS) $(BINS:=.o) $(BINS) $(MANS) $(PAGES)"
+ $(SILENT)-rm -f $(OBJS) $(BINS:=.o) $(BINS) $(MANS) $(PAGES)
+
+install: install-binaries install-manuals install-pages
+
+install-binaries: binaries
+ $(SILENT)install -d $(PREFIX)/bin
+ $(SILENT)for file in $(BINS); do \
+ echo "[INST] $(PREFIX)/bin/$$file"; \
+ install -m 755 $$file $(PREFIX)/bin; \
+ done
+
+install-manuals: manuals
+ $(SILENT)for file in $(MANS); do \
+ filename=$$(basename "$$file"); \
+ section="$${filename##*.}"; \
+ install -d $(PREFIX)/share/man/man$$section; \
+ echo "[INST] $(PREFIX)/share/man/man$$section/$$file"; \
+ install -m 755 $$file $(PREFIX)/share/man/man$$section/; \
+ done
+
+install-pages: pages
+ $(SILENT)install -d $(PREFIX)/share/doc/fiss;
+ $(SILENT)for file in $(PAGES); do \
+ echo "[INST] $(PREFIX)/share/doc/fiss/$$file"; \
+ install -m 755 $$file $(PREFIX)/share/doc/fiss/; \
+ done
+
+uninstall: uninstall-binaries uninstall-manuals uninstall-pages
+
+uninstall-binaries:
+ $(SILENT)for file in $(BINS); do \
+ echo "[ RM ] $(PREFIX)/bin/$$file"; \
+ rm -f $(PREFIX)/bin/$$file; \
+ done
+
+uninstall-manuals:
+ $(SILENT)for file in $(MANS); do \
+ filename=$$(basename "$$file"); \
+ section="$${filename##*.}"; \
+ echo "[ RM ] $(PREFIX)/share/man/man$$section/$$file"; \
+ rm -f $(PREFIX)/share/man/man$$section/$$file; \
+ done
+
+uninstall-pages:
+ @echo "[ RM ] $(PREFIX)/share/doc/fiss";
+ $(SILENT)rm -fr $(PREFIX)/share/doc/fiss;
+
+# Object rules
+%.o: %.c | $(HEADERS)
+ @echo "[ CC ] $< -> $@"
+ $(SILENT)$(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+# Executables
+%: %.o $(OBJS)
+ @echo "[ LD ] $@"
+ $(SILENT)$(CC) -o $@ $^ $(LDFLAGS)
+
+%: %.sh
+ @echo "[COPY] $< -> $@"
+ $(SILENT)cp $< $@
+ $(SILENT)chmod +x $@
+
+%: %.lnk
+ @echo "[LINK] $< -> $@"
+ $(SILENT)ln -sf $(shell cat $<) $@
+
+# Documentation and Manual
+%.html: %.txt
+ @echo "[MDOC] $< -> $@"
+ $(SILENT)$(SED) $(IN_REPLACE) $< | $(PYTHON) $(TOPDIR)/tools/make-docs.py > $@
+
+%: %.txt
+ @echo "[MMAN] $< -> $@"
+ $(SILENT)$(SED) $(IN_REPLACE) $< | $(PYTHON) $(TOPDIR)/tools/make-man.py | $(AWK) '/./ { print }' > $@
+\ No newline at end of file
diff --git a/mk/subdir.mk b/mk/subdir.mk
@@ -0,0 +1,6 @@
+% ::
+ @for dir in $(SUBDIRS); do \
+ $(MAKE) -C $$dir $@ || exit 1; \
+ done
+
+.DEFAULT_GOAL = all
diff --git a/mk/target.mk b/mk/target.mk
@@ -1,52 +0,0 @@
-# Directory rules
-$(TARGET_DIRS):
- @echo "[MDIR] $@"
- $(SILENT)mkdir -p $@
-
-$(TARGET_ASSETS_DIR): $(ASSETS_DIR) | $(TARGET_DOCS_DIR)
- @echo "[COPY] $< -> $@"
- $(SILENT)mkdir -p $@
- $(SILENT)cp -r $</* $@
-
-
-# Object rules
-$(TARGET_OBJECT_DIR)/%.o: $(SRC_DIR)/%.c $(INCLUDE_FILES) | $(TARGET_OBJECT_DIR)
- @echo "[ CC ] $< -> $@"
- $(SILENT)$(CC) -o $@ $< -c $(CFLAGS) $(shell mk/extract-flags.sh $< $(TARGET_OBJECT_DIR))
-
-# Object rules
-$(TARGET_OBJECT_DIR)/%.o: $(BIN_DIR)/%.c $(INCLUDE_FILES) | $(TARGET_OBJECT_DIR)
- @echo "[ CC ] $< -> $@"
- $(SILENT)$(CC) -o $@ $< -c $(CFLAGS) $(shell mk/extract-flags.sh $<)
-
-# Executables
-$(TARGET_BIN_DIR)/%: $(TARGET_OBJECT_DIR)/%.o $(OBJ_FILES) | $(TARGET_BIN_DIR)
- @echo "[ LD ] $< -> $@"
- $(SILENT)$(CC) -o $@ $< $(shell mk/extract-flags.sh $(patsubst $(TARGET_BIN_DIR)/%,$(BIN_DIR)/%.c,$@) $(TARGET_OBJECT_DIR)) $(LDFLAGS)
-
-$(TARGET_BIN_DIR)/%: $(BIN_DIR)/%.sh | $(TARGET_BIN_DIR)
- @echo "[COPY] $< -> $@"
- $(SILENT)cp $< $@
- $(SILENT)chmod +x $@
-
-$(TARGET_BIN_DIR)/%: $(BIN_DIR)/%.lnk | $(TARGET_BIN_DIR)
- @echo "[LINK] $< -> $@"
- $(SILENT)ln -sf $(shell cat $<) $@
-
-# Documentation and Manual
-$(TARGET_DOCS_DIR)/%.html: $(DOCS_DIR)/%.txt $(TARGET_ASSETS_DIR) $(MAKE_DOCS) | $(TARGET_DOCS_DIR)
- @echo "[MDOC] $< -> $@"
- $(SILENT)$(SED) 's/%VERSION%/$(VERSION)/' $< | $(PYTHON) $(MAKE_DOCS) > $@
-
-$(TARGET_DOCS_DIR)/%.html: $(MAN_DIR)/%.txt $(TARGET_ASSETS_DIR) $(MAKE_DOCS) | $(TARGET_DOCS_DIR)
- @echo "[MDOC] $< -> $@"
- $(SILENT)$(SED) 's/%VERSION%/$(VERSION)/' $< | $(PYTHON) $(MAKE_DOCS) > $@
-
-$(TARGET_MAN_DIR)/%: $(MAN_DIR)/%.txt $(MAKE_MAN) | $(TARGET_MAN_DIR)
- @echo "[MMAN] $< -> $@"
- $(SILENT)$(SED) 's/%VERSION%/$(VERSION)/' $< | $(PYTHON) $(MAKE_MAN) | $(AWK) '/./ { print }' > $@
-
-# Debug
-compile_flags.txt: Makefile
- @echo "[ECHO] $@"
- $(SILENT)echo $(CFLAGS) | tr " " "\n" > compile_flags.txt
-\ No newline at end of file
diff --git a/src/Makefile b/src/Makefile
@@ -0,0 +1,6 @@
+TOPDIR = ..
+-include $(TOPDIR)/config.mk
+
+SUBDIRS += chpst finit fsvc halt modules-load seedrng vlogger zzz
+
+include $(TOPDIR)/mk/subdir.mk
diff --git a/src/chpst/Makefile b/src/chpst/Makefile
@@ -0,0 +1,11 @@
+TOPDIR=../..
+-include $(TOPDIR)/config.mk
+
+OBJS = parse.o ../common/util.o
+BINS = chpst envuidgid pgrphack setlock setuidgid softlimit
+INTERM = chpst.8.txt
+MANS = chpst.8
+PAGES = chpst.8.html
+HEADERS = parse.h ../common/util.h
+
+include $(TOPDIR)/mk/prog.mk
+\ No newline at end of file
diff --git a/man/chpst.8.txt b/src/chpst/chpst.8.txt
diff --git a/src/chpst/chpst.c b/src/chpst/chpst.c
@@ -0,0 +1,355 @@
+// +objects: parse.o util.o
+
+#include "../common/util.h"
+#include "parse.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/resource.h>
+
+#define SHIFT(argc, argv, by) ((argc) -= (by), (argv) += (by))
+
+
+const char* current_prog(void) {
+ return "chpst";
+}
+
+void limit(int what, long l) {
+ struct rlimit r;
+
+ if (getrlimit(what, &r) == -1)
+ fprintf(stderr, "error: unable to getrlimit\n");
+
+ if (l < 0) {
+ r.rlim_cur = 0;
+ } else if ((unsigned long) l > r.rlim_max)
+ r.rlim_cur = r.rlim_max;
+ else
+ r.rlim_cur = l;
+
+ if (setrlimit(what, &r) == -1)
+ fprintf(stderr, "error: unable to setrlimit\n");
+}
+
+
+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 limitd = -2,
+ limits = -2,
+ limitl = -2,
+ limita = -2,
+ limito = -2,
+ limitp = -2,
+ limitf = -2,
+ limitc = -2,
+ limitr = -2,
+ limitt = -2;
+ long nicelevel = 0;
+ bool ssid = false;
+ bool closestd[3] = { false, false, false };
+
+ if (streq(argv[0], "setuidgid") || streq(argv[0], "envuidgid")) {
+ if (argc < 2) {
+ fprintf(stderr, "%s <uid-gid> command...", argv[0]);
+ return 1;
+ }
+ gid_len = parse_ugid(argv[1], &uid, gid);
+ SHIFT(argc, argv, 2);
+ } else if (streq(argv[0], "pgrphack")) {
+ ssid = true;
+ SHIFT(argc, argv, 1);
+ } else if (streq(argv[0], "setlock")) {
+ while ((opt = getopt(argc, argv, "+xXnN")) != -1) {
+ switch (opt) {
+ case 'n':
+ lockflags = LOCK_EX | LOCK_NB;
+ break;
+ case 'N':
+ lockflags = LOCK_EX;
+ break;
+ case 'x':
+ case 'X':
+ fprintf(stderr, "warning: '-%c' is ignored\n", optopt);
+ break;
+ case '?':
+ fprintf(stderr, "%s [-xXnN] command...", argv[0]);
+ return 1;
+ }
+ }
+ SHIFT(argc, argv, optind);
+ if (argc < 1) {
+ fprintf(stderr, "%s [-xXnN] command...", argv[0]);
+ return 1;
+ }
+ lock = argv[0];
+ SHIFT(argc, argv, 1);
+ } else if (streq(argv[0], "softlimit")) {
+ while ((opt = getopt(argc, argv, "+a:c:d:f:l:m:o:p:r:s:t:")) != -1) {
+ switch (opt) {
+ case 'm':
+ limits = limitl = limita = limitd = parse_long(optarg, "limit");
+ break;
+ case 'a':
+ limita = parse_long(optarg, "limit");
+ break;
+ case 'd':
+ limitd = parse_long(optarg, "limit");
+ break;
+ case 'o':
+ limito = parse_long(optarg, "limit");
+ break;
+ case 'p':
+ limitp = parse_long(optarg, "limit");
+ break;
+ case 'f':
+ limitf = parse_long(optarg, "limit");
+ break;
+ case 'c':
+ limitc = parse_long(optarg, "limit");
+ break;
+ case 'r':
+ limitr = parse_long(optarg, "limit");
+ break;
+ case 't':
+ limitt = parse_long(optarg, "limit");
+ break;
+ case 'l':
+ limitl = parse_long(optarg, "limit");
+ break;
+ case 's':
+ limits = parse_long(optarg, "limit");
+ break;
+ case '?':
+ fprintf(stderr, "softlimit command...");
+ return 1;
+ }
+ }
+ SHIFT(argc, argv, optind);
+ } else {
+ if (!streq(argv[0], "chpst"))
+ fprintf(stderr, "warning: program-name unsupported, asuming `chpst`\n");
+
+ 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 'm':
+ limits = limitl = limita = limitd = parse_long(optarg, "limit");
+ break;
+ case 'd':
+ limitd = parse_long(optarg, "limit");
+ break;
+ case 'o':
+ limito = parse_long(optarg, "limit");
+ break;
+ case 'p':
+ limitp = parse_long(optarg, "limit");
+ break;
+ case 'f':
+ limitf = parse_long(optarg, "limit");
+ break;
+ case 'c':
+ limitc = parse_long(optarg, "limit");
+ break;
+ case 'r':
+ limitr = parse_long(optarg, "limit");
+ break;
+ case 't':
+ limitt = parse_long(optarg, "limit");
+ break;
+ case 'e':
+ fprintf(stderr, "warning: '-%c' is ignored\n", optopt);
+ break;
+ case '?':
+ fprintf(stderr, "usage\n");
+ return 1;
+ }
+ }
+ SHIFT(argc, argv, optind);
+ }
+
+ if (argc == 0) {
+ fprintf(stderr, "%s: command required\n", argv[0]);
+ return 1;
+ }
+
+ if (ssid) {
+ setsid();
+ }
+
+ if (uid) {
+ setgroups(gid_len, gid);
+ setgid(gid[0]);
+ setuid(uid);
+ // $EUID
+ }
+
+ if (root) {
+ if (chroot(root) == -1)
+ print_errno("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_errno("unable to set nice level: %s\n");
+ }
+
+ if (limitd >= -1) {
+#ifdef RLIMIT_DATA
+ limit(RLIMIT_DATA, limitd);
+#else
+ if (verbose)
+ fprintf(stderr, "system does not support RLIMIT_DATA\n");
+#endif
+ }
+ if (limits >= -1) {
+#ifdef RLIMIT_STACK
+ limit(RLIMIT_STACK, limits);
+#else
+ if (verbose)
+ fprintf(stderr, "system does not support RLIMIT_STACK\n");
+#endif
+ }
+ if (limitl >= -1) {
+#ifdef RLIMIT_MEMLOCK
+ limit(RLIMIT_MEMLOCK, limitl);
+#else
+ if (verbose)
+ fprintf(stderr, "system does not support RLIMIT_MEMLOCK\n");
+#endif
+ }
+ if (limita >= -1) {
+#ifdef RLIMIT_VMEM
+ limit(RLIMIT_VMEM, limita);
+#else
+# ifdef RLIMIT_AS
+ limit(RLIMIT_AS, limita);
+# else
+ if (verbose)
+ fprintf(stderr, "system does neither support RLIMIT_VMEM nor RLIMIT_AS\n");
+# endif
+#endif
+ }
+ if (limito >= -1) {
+#ifdef RLIMIT_NOFILE
+ limit(RLIMIT_NOFILE, limito);
+#else
+# ifdef RLIMIT_OFILE
+ limit(RLIMIT_OFILE, limito);
+# else
+ if (verbose)
+ fprintf(stderr, "system does neither support RLIMIT_NOFILE nor RLIMIT_OFILE\n");
+# endif
+#endif
+ }
+ if (limitp >= -1) {
+#ifdef RLIMIT_NPROC
+ limit(RLIMIT_NPROC, limitp);
+#else
+ if (verbose)
+ fprintf(stderr, "system does not support RLIMIT_NPROC\n");
+#endif
+ }
+ if (limitf >= -1) {
+#ifdef RLIMIT_FSIZE
+ limit(RLIMIT_FSIZE, limitf);
+#else
+ if (verbose)
+ fprintf(stderr, "system does not support RLIMIT_FSIZE\n");
+#endif
+ }
+ if (limitc >= -1) {
+#ifdef RLIMIT_CORE
+ limit(RLIMIT_CORE, limitc);
+#else
+ if (verbose)
+ fprintf(stderr, "system does not support RLIMIT_CORE\n");
+#endif
+ }
+ if (limitr >= -1) {
+#ifdef RLIMIT_RSS
+ limit(RLIMIT_RSS, limitr);
+#else
+ if (verbose)
+ fprintf(stderr, "system does not support RLIMIT_RSS\n");
+#endif
+ }
+ if (limitt >= -1) {
+#ifdef RLIMIT_CPU
+ limit(RLIMIT_CPU, limitt);
+#else
+ if (verbose)
+ fprintf(stderr, "system does not support RLIMIT_CPU\n");
+#endif
+ }
+
+ if (lock) {
+ if ((lockfd = open(lock, O_WRONLY | O_APPEND)) == -1)
+ print_errno("unable to open lock: %s\n");
+
+ if (flock(lockfd, lockflags) == -1)
+ print_errno("unable to lock: %s\n");
+ }
+
+ if (closestd[0] && close(0) == -1)
+ print_errno("unable to close stdin: %s\n");
+ if (closestd[1] && close(1) == -1)
+ print_errno("unable to close stdout: %s\n");
+ if (closestd[2] && close(2) == -1)
+ print_errno("unable to close stderr: %s\n");
+
+ exec = argv[0];
+ if (arg0)
+ argv[0] = arg0;
+
+ execvp(exec, argv);
+ print_errno("cannot execute: %s\n");
+}
diff --git a/bin/envuidgid.lnk b/src/chpst/envuidgid.lnk
diff --git a/src/parse.c b/src/chpst/parse.c
diff --git a/src/chpst/parse.h b/src/chpst/parse.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <unistd.h>
+
+
+int parse_ugid(char* str, uid_t* uid, gid_t* gids);
diff --git a/bin/pgrphack.lnk b/src/chpst/pgrphack.lnk
diff --git a/bin/setlock.lnk b/src/chpst/setlock.lnk
diff --git a/bin/setuidgid.lnk b/src/chpst/setuidgid.lnk
diff --git a/bin/softlimit.lnk b/src/chpst/softlimit.lnk
diff --git a/src/util.c b/src/common/util.c
diff --git a/src/common/util.h b/src/common/util.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <stdio.h>
+#include <time.h>
+
+#define streq(a, b) (!strcmp((a), (b)))
+
+#define print_errno(msg, ...) (fprintf(stderr, "%s: " msg, current_prog(), ##__VA_ARGS__, strerror(errno)))
+#define print_error(msg, ...) (fprintf(stderr, "%s: " msg, current_prog(), ##__VA_ARGS__))
+
+typedef struct {
+ int read;
+ int write;
+} pipe_t;
+
+const char* current_prog(void); // has to be defined per program
+
+ssize_t dgetline(int fd, char* line, size_t line_buffer);
+ssize_t readstr(int fd, char* str);
+ssize_t writestr(int fd, const char* str);
+unsigned int stat_mode(const char* format, ...);
+int fork_dup_cd_exec(int dir, const char* path, int fd0, int fd1, int fd2);
+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/dependency.c b/src/dependency.c
@@ -1,47 +0,0 @@
-#include "config.h"
-#include "service.h"
-#include "util.h"
-
-#include <fcntl.h>
-#include <limits.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-
-void service_add_dependency(struct service* s, struct service* d) {
- if (s == d)
- return;
-
- depends[depends_size][0] = s;
- depends[depends_size][1] = d;
- depends_size++;
-}
-
-void service_update_dependency(struct service* s) {
- struct service* dep;
- int depends_file;
- char line[SV_NAME_MAX];
-
- if (s->log_service) { // aka keep first entry (the log service) if a log service is used
- service_add_dependency(s, s->log_service);
- }
-
- if ((depends_file = openat(s->dir, "depends", O_RDONLY)) == -1)
- return;
-
- while (dgetline(depends_file, line, sizeof(line)) > 0) {
- if (streq(s->name, line)) {
- fprintf(stderr, "warning: %s depends on itself\n", s->name);
- continue;
- }
-
- if ((dep = service_get(line)) == NULL) {
- fprintf(stderr, "warning: %s depends on %s: dependency not found\n", s->name, line);
- continue;
- }
- service_add_dependency(s, dep);
- }
-
- close(depends_file);
-}
diff --git a/src/finit/Makefile b/src/finit/Makefile
@@ -0,0 +1,15 @@
+TOPDIR = ../..
+-include $(TOPDIR)/config.mk
+
+BINS += fsvs finit
+OBJS += message.o supervise.o service.o start.o \
+ stop.o register.o handle_exit.o handle_command.o \
+ encode.o dependency.o status.o stage.o ../common/util.o
+
+HEADERS += ../common/util.h message.h service.h stage.h
+MANS += finit.8 fsvs.8
+PAGES += finit.8.html fsvs.8.html
+
+finit: LDFLAGS += -static
+
+include $(TOPDIR)/mk/prog.mk
+\ No newline at end of file
diff --git a/src/finit/dependency.c b/src/finit/dependency.c
@@ -0,0 +1,60 @@
+#include "../common/util.h"
+#include "config.h"
+#include "service.h"
+
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+
+static bool circular_dependency(struct service* s, struct service* d) {
+ if (s == d)
+ return true;
+
+ for (int i = 0; i < s->parents_size; i++) {
+ if (circular_dependency(s->parents[i], d))
+ return true;
+ }
+
+ return false;
+}
+
+void service_add_dependency(struct service* s, struct service* d) {
+ if (circular_dependency(s, d)) {
+ print_error("warning: detected circular dependency while adding %s to %s\n", d->name, s->name);
+ return;
+ }
+
+ s->children[s->children_size++] = d;
+ d->parents[s->parents_size++] = s;
+}
+
+void service_update_dependency(struct service* s) {
+ struct service* dep;
+ int depends_file;
+ char line[SV_NAME_MAX];
+
+ if (s->log_service) { // aka keep first entry (the log service) if a log service is used
+ service_add_dependency(s, s->log_service);
+ }
+
+ if ((depends_file = openat(s->dir, "depends", O_RDONLY)) == -1)
+ return;
+
+ while (dgetline(depends_file, line, sizeof(line)) > 0) {
+ if (streq(s->name, line)) {
+ fprintf(stderr, "warning: %s depends on itself\n", s->name);
+ continue;
+ }
+
+ if ((dep = service_get(line)) == NULL) {
+ fprintf(stderr, "warning: %s depends on %s: dependency not found\n", s->name, line);
+ continue;
+ }
+ service_add_dependency(s, dep);
+ }
+
+ close(depends_file);
+}
diff --git a/src/encode.c b/src/finit/encode.c
diff --git a/man/finit.8.txt b/src/finit/finit.8.txt
diff --git a/src/finit/finit.c b/src/finit/finit.c
@@ -0,0 +1,141 @@
+// +objects:
+// +objects: message.o util.o supervise.o service.o start.o stop.o register.o handle_exit.o handle_command.o encode.o dependency.o status.o stage.o
+// +objects: encode.o dependency.o status.o stage.o
+// +flags: -static
+
+#include "../common/util.h"
+#include "config.h"
+#include "message.h"
+#include "service.h"
+#include "stage.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/reboot.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+
+const char* current_prog(void) {
+ return "finit";
+}
+
+static bool do_reboot;
+
+static int handle_initctl(int argc, const char** argv) {
+ int sig;
+
+ if (argc != 2 || argv[1][1] != '\0' || (argv[1][0] != '0' && argv[1][0] != '6')) {
+ print_usage_exit(PROG_FINIT, 1);
+ }
+ if (getuid() != 0) {
+ fprintf(stderr, "error: can only be run as root...\n");
+ return 1;
+ }
+ sig = argv[1][0] == '0' ? SIGTERM : SIGINT;
+ if (kill(1, sig) == -1) {
+ print_errno("error: unable to kill init: %s\n");
+ return 1;
+ }
+ return 0;
+}
+
+static void signal_interrupt(int signum) {
+ daemon_running = false;
+ do_reboot = signum == SIGINT;
+}
+
+
+int main(int argc, const char** argv) {
+ sigset_t ss;
+ pid_t pid;
+
+ if (getpid() != 1) {
+ return handle_initctl(argc, argv);
+ }
+ setsid();
+
+ sigblock_all(false);
+
+ reclaim_console();
+
+ // disable ctrl-alt-delete
+ reboot(0);
+
+ printf("booting...\n");
+
+ daemon_running = true;
+
+ // stage 1
+ handle_stage(0);
+
+
+ // stage 2
+ if (daemon_running) { // stage1 succeed
+ struct sigaction sigact = { 0 };
+
+ sigblock_all(true);
+
+ sigact.sa_handler = signal_interrupt;
+ sigaction(SIGTERM, &sigact, NULL);
+ sigaction(SIGINT, &sigact, NULL);
+
+ service_supervise(SV_SERVICE_DIR, SV_BOOT_SERVICE, false);
+ sigblock_all(false);
+ }
+
+ // stage 3
+ handle_stage(2);
+
+#ifdef RB_AUTOBOOT
+ /* fallthrough stage 3 */
+ printf("sending KILL signal to all processes...\n");
+ kill(-1, SIGKILL);
+
+ if ((pid = fork()) <= 0) {
+ if (do_reboot) {
+ printf("system reboot\n");
+ sync();
+ reboot(RB_AUTOBOOT);
+ } else {
+# if defined(RB_POWER_OFF)
+ printf("system power off\n");
+ sync();
+ reboot(RB_POWER_OFF);
+ sleep(2);
+# elif defined(RB_HALT_SYSTEM)
+ printf("system halt\n");
+ sync();
+ reboot(RB_HALT_SYSTEM);
+# elif define(RB_HALT)
+ printf("system halt\n");
+ sync();
+ reboot(RB_HALT);
+# else
+ printf("system reboot\n");
+ sync();
+ reboot(RB_AUTOBOOT);
+# endif
+ }
+ if (pid == 0)
+ _exit(0);
+ } else {
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGCHLD);
+ sigprocmask(SIG_UNBLOCK, &ss, NULL);
+
+ while (waitpid(pid, NULL, 0) != -1) {}
+ }
+#endif
+
+ sigfillset(&ss);
+ for (;;)
+ sigsuspend(&ss);
+
+ /* not reached */
+ printf("exit.\n");
+ return 0;
+}
diff --git a/man/fsvs.8.txt b/src/finit/fsvs.8.txt
diff --git a/src/finit/fsvs.c b/src/finit/fsvs.c
@@ -0,0 +1,72 @@
+// +objects: message.o util.o supervise.o service.o start.o stop.o
+// +objects: register.o handle_exit.o handle_command.o
+// +objects: encode.o parse.o dependency.o status.o
+
+#include "../common/util.h"
+#include "config.h"
+#include "message.h"
+#include "service.h"
+
+#include <getopt.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+
+const char* current_prog(void) {
+ return "fsvs";
+}
+
+static const struct option long_options[] = {
+ { "version", no_argument, 0, 'V' },
+ { "once", no_argument, 0, 'o' },
+ { 0 }
+};
+
+static void signal_interrupt(int signum) {
+ (void) signum;
+
+ daemon_running = false;
+}
+
+int main(int argc, char** argv) {
+ int c;
+ bool once = false;
+ while ((c = getopt_long(argc, argv, ":Vo", long_options, NULL)) > 0) {
+ switch (c) {
+ case 'V':
+ print_version_exit();
+ break;
+ case 'o':
+ once = true;
+ 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_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);
+ }
+
+ struct sigaction sa = { 0 };
+ sa.sa_handler = signal_interrupt;
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ return service_supervise(argv[0], argv[1], once);
+}
diff --git a/src/handle_command.c b/src/finit/handle_command.c
diff --git a/src/finit/handle_exit.c b/src/finit/handle_exit.c
@@ -0,0 +1,104 @@
+#include "service.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+static void do_finish(struct service* s) {
+ struct stat st;
+
+ if (fstatat(s->dir, "finish", &st, 0) != -1 && st.st_mode & S_IXUSR) {
+ if ((s->pid = fork_dup_cd_exec(s->dir, "./finish", null_fd, null_fd, null_fd)) == -1) {
+ print_errno("error: cannot execute ./finish: %s\n");
+ service_update_state(s, STATE_INACTIVE);
+ } else {
+ service_update_state(s, STATE_FINISHING);
+ }
+ } else if (s->fail_count == SV_FAIL_MAX) {
+ service_update_state(s, STATE_ERROR);
+ printf("%s died\n", s->name);
+ } else {
+ service_update_state(s, s->restart == S_ONCE ? STATE_DONE : STATE_INACTIVE);
+ }
+}
+
+
+void service_handle_exit(struct service* s, bool signaled, int return_code) {
+ struct stat st;
+
+ s->pid = 0;
+ s->stop_timeout = 0;
+
+ if (s->restart == S_ONCE)
+ s->restart = S_DOWN;
+
+ switch (s->state) {
+ case STATE_SETUP:
+ service_run(s);
+ break;
+ case STATE_ACTIVE_FOREGROUND:
+ if (signaled) {
+ s->last_exit = EXIT_SIGNALED;
+ s->return_code = return_code;
+ s->fail_count++;
+
+ printf("%s killed thought signal %d\n", s->name, s->return_code);
+ } else {
+ s->last_exit = EXIT_NORMAL;
+ s->return_code = return_code;
+ if (s->return_code > 0)
+ s->fail_count++;
+ else
+ s->fail_count = 0;
+
+ printf("%s exited with code %d\n", s->name, s->return_code);
+ }
+
+ do_finish(s);
+
+ break;
+ case STATE_ACTIVE_DUMMY:
+ case STATE_ACTIVE_BACKGROUND:
+ case STATE_STOPPING:
+ do_finish(s);
+ break;
+
+ case STATE_FINISHING:
+ if (s->fail_count == SV_FAIL_MAX) {
+ service_update_state(s, STATE_ERROR);
+ printf("%s died\n", s->name);
+ } else {
+ service_update_state(s, s->restart == S_ONCE ? STATE_DONE : STATE_INACTIVE);
+ }
+ break;
+ case STATE_STARTING:
+ if (!signaled && return_code == 0) {
+ if (fstatat(s->dir, "stop", &st, 0) != -1 && st.st_mode & S_IXUSR) {
+ service_update_state(s, STATE_ACTIVE_BACKGROUND);
+ } else {
+ do_finish(s);
+ }
+ } else if (!signaled) {
+ s->last_exit = EXIT_NORMAL;
+ s->return_code = return_code;
+
+ do_finish(s);
+ } else { // signaled
+ s->last_exit = EXIT_SIGNALED;
+ s->return_code = return_code;
+
+ do_finish(s);
+ }
+ break;
+
+ case STATE_ERROR:
+ case STATE_INACTIVE:
+ case STATE_DONE:
+ printf("unexpected error: %s died but it's inactive\n", s->name);
+ }
+}
diff --git a/bin/init.lnk b/src/finit/init.lnk
diff --git a/src/finit/message.c b/src/finit/message.c
@@ -0,0 +1,52 @@
+#include "message.h"
+
+#include "config.h"
+
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static const char* prog_usage[] = {
+ [PROG_FINIT] = "init <0|6>",
+ [PROG_FSVC] = "fsvc <command> [-v --verbose] [-V --version] [-r --runlevel <level>] [-s --service-dir <path>]\n"
+ " fsvc start [-p --pin] <service>\n"
+ " fsvc stop [-p --pin] <service>\n"
+ " fsvc enable [-o --once] <service>\n"
+ " fsvc disable [-o --once] <service>\n"
+ " fsvc kill <service> <signal|signum>\n"
+ " fsvc status [-c --check] <service>\n"
+ " fsvc pause <service>\n"
+ " fsvc resume <service>\n"
+ " fsvc switch [-f --reset] <runlevel>",
+ [PROG_FSVS] = "fsvs [-V --version] [-v --verbose] [-f --force] <service-dir> <runlevel>",
+ [PROG_HALT] = "halt [-n] [-f] [-d] [-w] [-B]",
+ [PROG_POWEROFF] = "poweroff [-n] [-f] [-d] [-w] [-B]",
+ [PROG_REBOOT] = "reboot [-n] [-f] [-d] [-w] [-B]",
+ [PROG_SEEDRNG] = "seedrng",
+ [PROG_SIGREMAP] = "sigremap [-s --single] [-v --verbose] [-V --version] <old-signal=new-signal...> <command> [args...]",
+ [PROG_VLOGGER] = "vlogger [-isS] [-f file] [-p pri] [-t tag] [message ...]",
+ [PROG_ZZZ] = "zzz [-n --noop] [-S --freeze] [-z --suspend] [-Z --hibernate] [-R --reboot] [-H --hybrid]"
+};
+
+static const char* prog_manual[] = {
+ [PROG_FINIT] = "finit 8",
+ [PROG_FSVC] = "fsvc 8",
+ [PROG_FSVS] = "fsvs 8",
+ [PROG_HALT] = "halt 8",
+ [PROG_POWEROFF] = "poweroff 8",
+ [PROG_REBOOT] = "reboot 8",
+ [PROG_SEEDRNG] = "seedrng 8",
+ [PROG_SIGREMAP] = "sigremap 8",
+ [PROG_VLOGGER] = "vlogger 1",
+ [PROG_ZZZ] = "zzz 8"
+};
+
+void print_usage_exit(enum prog prog, int status) {
+ fprintf(status ? stderr : stdout, "Usage: %s\n\nCheck manual '%s' for more information.\n", prog_usage[prog], prog_manual[prog]);
+ exit(status);
+}
+
+void print_version_exit(void) {
+ printf(SV_VERSION);
+ exit(0);
+}
diff --git a/src/finit/message.h b/src/finit/message.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#define FISS_VERSION_STRING "fiss version v" SV_VERSION ""
+
+enum prog {
+ PROG_FINIT,
+ PROG_FSVC,
+ PROG_FSVS,
+ PROG_HALT,
+ PROG_POWEROFF,
+ PROG_REBOOT,
+ PROG_SEEDRNG,
+ PROG_SIGREMAP,
+ PROG_VLOGGER,
+ PROG_ZZZ
+};
+
+void print_usage_exit(enum prog prog, int status) __attribute__((noreturn));
+void print_version_exit(void) __attribute__((noreturn));
diff --git a/src/finit/register.c b/src/finit/register.c
@@ -0,0 +1,96 @@
+#include "../common/util.h"
+#include "config.h"
+#include "service.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+static int init_supervise(struct service* s) {
+ int fd;
+ struct stat st;
+
+ if (fstatat(s->dir, "supervise", &st, 0) == -1 && mkdirat(s->dir, "supervise", 0755) == -1) {
+ return -1;
+ }
+
+ if (fstatat(s->dir, "supervise/ok", &st, 0) == -1 && mkfifoat(s->dir, "supervise/ok", 0666) == -1) {
+ print_errno("cannot create fifo at supervise/ok: %s\n");
+ return -1;
+ }
+
+ if (fstatat(s->dir, "supervise/control", &st, 0) == -1 && mkfifoat(s->dir, "supervise/control", 0644) == -1) {
+ print_errno("cannot create fifo at supervise/control: %s\n");
+ return -1;
+ }
+
+ if (openat(s->dir, "supervise/ok", O_RDONLY | O_NONBLOCK) == -1) {
+ print_errno("cannot open supervise/ok: %s\n");
+ return -1;
+ }
+
+ if ((s->control = openat(s->dir, "supervise/control", O_RDONLY | O_NONBLOCK)) == -1) {
+ print_errno("cannot open supervise/ok: %s\n");
+ return -1;
+ }
+
+ if ((fd = openat(s->dir, "supervise/lock", O_CREAT | O_WRONLY, 0644)) == -1) {
+ print_errno("cannot create supervise/lock: %s\n");
+ return -1;
+ }
+ close(fd);
+
+ return 0;
+}
+
+struct service* service_register(int dir, const char* name, bool is_log_service) {
+ struct service* s;
+ struct stat st;
+
+ if ((s = service_get(name)) == NULL) {
+ s = &services[services_size++];
+ s->state = STATE_INACTIVE;
+ s->restart = S_DOWN;
+ s->last_exit = EXIT_NONE;
+ s->return_code = 0;
+ s->fail_count = 0;
+ s->log_service = NULL;
+ s->paused = false;
+ s->log_pipe.read = 0;
+ s->log_pipe.write = 0;
+ s->is_log_service = is_log_service;
+ s->stop_timeout = 0;
+
+ if ((s->dir = openat(dir, name, O_DIRECTORY)) == -1) {
+ print_errno("error: cannot open '%s': %s\n", name);
+ services_size--;
+ return NULL;
+ }
+
+ if (init_supervise(s) == -1) {
+ services_size--;
+ return NULL;
+ }
+
+ strncpy(s->name, name, sizeof(s->name));
+
+ service_update_state(s, -1);
+ }
+
+ if (s->is_log_service) {
+ if (s->log_pipe.read == 0 || s->log_pipe.write == 0)
+ pipe((int*) &s->log_pipe);
+
+ } else if (!s->log_service && fstatat(s->dir, "log", &st, 0) != -1 && S_ISDIR(st.st_mode)) {
+ s->log_service = service_register(s->dir, "log", true);
+ }
+
+ service_write(s);
+
+ return s;
+}
diff --git a/src/finit/service.c b/src/finit/service.c
@@ -0,0 +1,86 @@
+#include "service.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+
+struct service services[SV_SERVICE_MAX];
+int services_size = 0;
+char runlevel[SV_NAME_MAX];
+int service_dir;
+const char* service_dir_path;
+int null_fd;
+bool daemon_running;
+
+struct service* service_get(const char* name) {
+ for (int i = 0; i < services_size; i++) {
+ if (streq(services[i].name, name))
+ return &services[i];
+ }
+ return NULL;
+}
+
+int service_refresh_directory(void) {
+ DIR* dp;
+ struct dirent* ep;
+ struct stat st;
+ struct service* s;
+
+ if ((dp = opendir(service_dir_path)) == NULL) {
+ print_errno("error: cannot open service directory: %s\n");
+ return -1;
+ }
+
+ for (int i = 0; i < services_size; i++) {
+ s = &services[i];
+ if (fstat(s->dir, &st) == -1 || !S_ISDIR(st.st_mode)) {
+ service_stop(s);
+ close(s->dir);
+ close(s->control);
+ }
+ }
+
+ while ((ep = readdir(dp)) != NULL) {
+ if (ep->d_name[0] == '.')
+ continue;
+
+ if (fstatat(service_dir, ep->d_name, &st, 0) == -1 || !S_ISDIR(st.st_mode))
+ continue;
+
+ service_register(service_dir, ep->d_name, false);
+ }
+
+ closedir(dp);
+
+ for (int i = 0; i < services_size; i++) {
+ services[i].children_size = 0;
+ services[i].parents_size = 0;
+ }
+
+ for (int i = 0; i < services_size; i++)
+ service_update_dependency(&services[i]);
+
+ return 0;
+}
+
+
+bool service_need_restart(struct service* s) {
+ if (!daemon_running)
+ return false;
+
+ if (s->restart == S_RESTART)
+ return true;
+
+ for (int i = 0; i < s->parents_size; i++) {
+ if (service_need_restart(s->parents[i]))
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/finit/service.h b/src/finit/service.h
@@ -0,0 +1,114 @@
+#pragma once
+
+#include "../common/util.h"
+#include "config.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <time.h>
+
+
+enum service_command {
+ X_UP = 'u', // starts the services, pin as started
+ X_DOWN = 'd', // stops the service, pin as stopped
+ X_ONCE = 'o', // starts the service, pin as once
+ X_TERM = 't', // same as down
+ X_KILL = 'k', // sends kill, pin as stopped
+ X_PAUSE = 'p', // pauses the service
+ X_CONT = 'c', // resumes the service
+ X_RESET = 'r', // resets the service
+ X_ALARM = 'a', // sends alarm
+ X_HUP = 'h', // sends hup
+ X_INT = 'i', // sends interrupt
+ X_QUIT = 'q', // sends quit
+ X_USR1 = '1', // sends usr1
+ X_USR2 = '2', // sends usr2
+ X_EXIT = 'x', // does nothing
+};
+
+enum service_state {
+ STATE_INACTIVE, // not started
+ STATE_SETUP, // ./setup running
+ STATE_STARTING, // ./start running
+ STATE_ACTIVE_FOREGROUND, // ./run running
+ STATE_ACTIVE_BACKGROUND, // ./start finished, ./stop not called yet
+ STATE_ACTIVE_DUMMY, // dependencies started
+ STATE_STOPPING, // ./stop running
+ STATE_FINISHING, // ./finish running
+ STATE_DONE, // ./stop finished
+ STATE_ERROR, // something went wrong
+};
+
+enum service_exit {
+ EXIT_NONE, // never exited
+ EXIT_NORMAL, // exited
+ EXIT_SIGNALED, // crashed
+};
+
+enum service_restart {
+ S_DOWN, // service should not be started
+ S_ONCE, // service should
+ S_RESTART, // service should be started
+};
+
+struct service_serial {
+ uint8_t status_change[8];
+ uint8_t state;
+ uint8_t return_code;
+ uint8_t fail_count;
+ uint8_t flags;
+ uint8_t pid[4];
+ uint8_t paused;
+ uint8_t restart;
+ uint8_t force_down;
+ uint8_t state_runit;
+};
+
+struct service {
+ char name[SV_NAME_MAX]; // name of service
+ enum service_state state; // current state
+ pid_t pid; // pid of run
+ int dir; // dirfd
+ int control; // fd to supervise/control
+ time_t state_change; // last status change
+ enum service_restart restart; // should restart on exit
+ enum service_exit last_exit; // stopped signaled or exited
+ int return_code; // return code or signal
+ uint8_t fail_count; // current fail cound
+ bool is_log_service; // is a log service
+ bool paused; // is paused
+ time_t stop_timeout; // stop start-time
+ pipe_t log_pipe; // pipe for logging
+ struct service* log_service; // has a log_server otherwise NULL
+ int parents_size; // count of service depending on
+ struct service* parents[SV_DEPENDENCY_MAX]; // service depending on
+ int children_size; // count of dependencies
+ struct service* children[SV_DEPENDENCY_MAX]; // dependencies
+};
+
+extern struct service services[];
+extern int services_size;
+extern int null_fd;
+extern bool daemon_running;
+extern const char* service_dir_path;
+extern int service_dir;
+
+
+void service_encode(struct service* s, struct service_serial* buffer);
+struct service* service_get(const char* name);
+void service_handle_command(struct service* s, char command);
+void service_handle_exit(struct service* s, bool signaled, int return_code);
+void service_kill(struct service* s, int signal);
+bool service_need_restart(struct service* s);
+int service_refresh_directory(void);
+struct service* service_register(int dir, const char* name, bool is_log_service);
+void service_run(struct service* s);
+int service_send_command(char command, char extra, const char* service, struct service* response, int response_max);
+void service_start(struct service* s);
+const char* service_status_name(struct service* s);
+void service_stop(struct service* s);
+int service_supervise(const char* service_dir_, const char* service, bool once);
+void service_update_dependency(struct service* s);
+bool service_is_dependency(struct service* s);
+void service_update_state(struct service* s, int state);
+void service_write(struct service* s);
diff --git a/src/finit/stage.c b/src/finit/stage.c
@@ -0,0 +1,82 @@
+#include "stage.h"
+
+#include "../common/util.h"
+#include "config.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 char* stage_exec[][4] = {
+ [0] = { SV_START_EXEC, NULL },
+ [2] = { SV_STOP_EXEC, NULL },
+};
+
+
+bool handle_stage(int stage) {
+ int pid, ttyfd, exitstat, sig = 0;
+ sigset_t ss;
+ bool cont = true;
+
+ while ((pid = fork()) == -1) {
+ print_errno("error: unable to fork for stage1: %s\n");
+ sleep(5);
+ }
+ if (pid == 0) {
+ /* child */
+
+ if (stage == 0) {
+ /* stage 1 gets full control of console */
+ if ((ttyfd = open("/dev/console", O_RDWR)) == -1) {
+ print_errno("error: unable to open /dev/console: %s\n");
+ } else {
+ ioctl(ttyfd, TIOCSCTTY, NULL); // make the controlling process
+ dup2(ttyfd, 0);
+ if (ttyfd > 2) close(ttyfd);
+ }
+ }
+
+ sigblock_all(true);
+
+ printf("enter stage %d\n", stage);
+ execv(stage_exec[stage][0], stage_exec[stage]);
+ print_errno("error: unable to exec stage %d: %s\n", stage);
+ _exit(1);
+ }
+
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGCHLD);
+ sigaddset(&ss, SIGUSR1);
+ sigaddset(&ss, SIGCONT);
+
+ sigwait(&ss, &sig);
+
+ if (stage == 1 && sig != SIGCHLD)
+ kill(pid, SIGTERM);
+
+ if (waitpid(pid, &exitstat, 0) == -1) {
+ print_errno("warn: waitpid failed: %s");
+ sleep(5);
+ }
+
+ reclaim_console();
+
+ if (stage == 0) {
+ if (!WIFEXITED(exitstat) || WEXITSTATUS(exitstat) != 0) {
+ if (WIFSIGNALED(exitstat)) {
+ /* this is stage 1 */
+ fprintf(stderr, "stage 1 failed: skip stage 2\n");
+ cont = false;
+ }
+ }
+ printf("leave stage 1\n");
+ }
+
+ return cont;
+}
diff --git a/include/stage.h b/src/finit/stage.h
diff --git a/src/finit/start.c b/src/finit/start.c
@@ -0,0 +1,112 @@
+#include "service.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+static void set_pipes(struct service* s) {
+ if (s->is_log_service) {
+ close(s->log_pipe.write);
+ dup2(s->log_pipe.read, STDIN_FILENO);
+ close(s->log_pipe.read);
+ dup2(null_fd, STDOUT_FILENO);
+ dup2(null_fd, STDERR_FILENO);
+ } else if (s->log_service) { // aka has_log_service
+ close(s->log_service->log_pipe.read);
+ dup2(s->log_service->log_pipe.write, STDOUT_FILENO);
+ dup2(s->log_service->log_pipe.write, STDERR_FILENO);
+ close(s->log_service->log_pipe.write);
+ dup2(null_fd, STDIN_FILENO);
+ } else if (stat_mode("log") & S_IWRITE) { // is not
+ int log_fd;
+ if ((log_fd = open("log", O_WRONLY | O_TRUNC)) == -1)
+ log_fd = null_fd;
+
+ dup2(null_fd, STDIN_FILENO);
+ dup2(log_fd, STDOUT_FILENO);
+ dup2(log_fd, STDERR_FILENO);
+ } else if (S_ISREG(stat_mode("nolog"))) {
+ dup2(null_fd, STDIN_FILENO);
+ dup2(null_fd, STDOUT_FILENO);
+ dup2(null_fd, STDERR_FILENO);
+ } else {
+ char service_log[PATH_MAX];
+ int log_fd;
+
+ snprintf(service_log, PATH_MAX, "%s/%s.log", SV_LOG_DIR, s->name);
+
+ if ((log_fd = open(service_log, O_CREAT | O_WRONLY | O_TRUNC, 0644)) == -1)
+ log_fd = null_fd;
+
+ dup2(null_fd, STDIN_FILENO);
+ dup2(log_fd, STDOUT_FILENO);
+ dup2(log_fd, STDERR_FILENO);
+ }
+}
+
+void service_run(struct service* s) {
+ struct stat st;
+
+ if (fstatat(s->dir, "run", &st, 0) != -1 && st.st_mode & S_IXUSR) {
+ service_update_state(s, STATE_ACTIVE_FOREGROUND);
+ } else if (fstatat(s->dir, "start", &st, 0) != -1 && st.st_mode & S_IXUSR) {
+ service_update_state(s, STATE_STARTING);
+ } else if (fstatat(s->dir, "depends", &st, 0) != -1 && st.st_mode & S_IREAD) {
+ service_update_state(s, STATE_ACTIVE_DUMMY);
+ } else {
+ // fprintf(stderr, "warn: %s: `run`, `start` or `depends` not found\n", s->name);
+ service_update_state(s, STATE_INACTIVE);
+ }
+
+ if (s->state != STATE_ACTIVE_DUMMY) {
+ if ((s->pid = fork()) == -1) {
+ print_errno("error: cannot fork process: %s\n");
+ exit(1);
+ } else if (s->pid == 0) { // child
+ if (setsid() == -1)
+ print_errno("error: cannot setsid: %s\n");
+
+ fchdir(s->dir);
+ set_pipes(s);
+
+ if (s->state == STATE_STARTING) {
+ execl("./start", "./start", NULL);
+ } else {
+ execl("./run", "./run", NULL);
+ }
+ print_errno("error: cannot execute service: %s\n");
+ _exit(1);
+ }
+ }
+}
+
+void service_start(struct service* s) {
+ struct stat st;
+
+ if (!daemon_running || s->state != STATE_INACTIVE)
+ return;
+
+ printf("starting %s\n", s->name);
+ for (int i = 0; i < s->children_size; i++) {
+ service_start(s->children[i]);
+ }
+
+ if (fstatat(s->dir, "setup", &st, 0) != -1 && st.st_mode & S_IXUSR) {
+ if ((s->pid = fork_dup_cd_exec(s->dir, "./setup", null_fd, null_fd, null_fd)) == -1) {
+ print_errno("error: cannot execute ./setup: %s\n");
+ service_update_state(s, STATE_INACTIVE);
+ } else {
+ service_update_state(s, STATE_SETUP);
+ }
+ } else {
+ service_run(s);
+ }
+ printf("started %s \n", s->name);
+}
diff --git a/src/finit/status.c b/src/finit/status.c
@@ -0,0 +1,94 @@
+#include "../common/util.h"
+#include "service.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+void service_update_state(struct service* s, int state) {
+ if (state != -1)
+ s->state = state;
+
+ s->state_change = time(NULL);
+
+ service_write(s);
+}
+
+void service_write(struct service* s) {
+ int fd;
+ const char* stat_human;
+ struct service_serial stat_runit;
+
+ if ((fd = openat(s->dir, "supervise/status.new", O_CREAT | O_WRONLY | O_TRUNC, 0644)) == -1) {
+ print_errno("cannot open supervise/status: %s\n");
+ return;
+ }
+
+ service_encode(s, &stat_runit);
+
+ if (write(fd, &stat_runit, sizeof(stat_runit)) == -1) {
+ print_errno("cannot write to supervise/status: %s\n");
+ return;
+ }
+
+ close(fd);
+
+ if ((fd = openat(s->dir, "supervise/stat.new", O_CREAT | O_WRONLY | O_TRUNC, 0644)) == -1) {
+ print_errno("cannot create supervise/stat: %s\n");
+ return;
+ }
+
+ stat_human = service_status_name(s);
+ if (write(fd, stat_human, strlen(stat_human)) == -1) {
+ print_errno("cannot write to supervise/stat: %s\n");
+ return;
+ }
+
+ close(fd);
+
+ if ((fd = openat(s->dir, "supervise/pid.new", O_CREAT | O_WRONLY | O_TRUNC, 0644)) == -1) {
+ print_errno("cannot create supervise/stat: %s\n");
+ return;
+ }
+
+ dprintf(fd, "%d", s->pid);
+
+ close(fd);
+
+ renameat(s->dir, "supervise/status.new", s->dir, "supervise/status");
+ renameat(s->dir, "supervise/stat.new", s->dir, "supervise/stat");
+ renameat(s->dir, "supervise/pid.new", s->dir, "supervise/pid");
+}
+
+const char* service_status_name(struct service* s) {
+ switch (s->state) {
+ case STATE_SETUP:
+ return "setup";
+ case STATE_STARTING:
+ return "starting";
+ case STATE_ACTIVE_FOREGROUND:
+ return "run";
+ case STATE_ACTIVE_BACKGROUND:
+ return "run-background";
+ case STATE_ACTIVE_DUMMY:
+ return "run-dummy";
+ case STATE_FINISHING:
+ return "finishing";
+ case STATE_STOPPING:
+ return "stopping";
+ case STATE_INACTIVE:
+ return "down";
+ case STATE_DONE:
+ return "done";
+ case STATE_ERROR:
+ return "dead (error)";
+ default:
+ return NULL;
+ }
+}
diff --git a/src/finit/stop.c b/src/finit/stop.c
@@ -0,0 +1,48 @@
+#include "../common/util.h"
+#include "service.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+
+void service_stop(struct service* s) {
+ switch (s->state) {
+ case STATE_ACTIVE_DUMMY:
+ service_handle_exit(s, false, 0);
+ break;
+ case STATE_ACTIVE_BACKGROUND:
+ if ((s->pid = fork_dup_cd_exec(s->dir, "./stop", null_fd, null_fd, null_fd)) == -1) {
+ print_errno("error: cannot execute ./stop: %s\n");
+ service_update_state(s, STATE_INACTIVE);
+ } else {
+ service_update_state(s, STATE_STOPPING);
+ }
+ break;
+ case STATE_ACTIVE_FOREGROUND:
+ case STATE_SETUP:
+ case STATE_STARTING:
+ case STATE_STOPPING:
+ case STATE_FINISHING:
+ s->stop_timeout = time(NULL);
+ kill(s->pid, SIGTERM);
+ service_update_state(s, -1);
+ break;
+ case STATE_DONE:
+ s->state = STATE_INACTIVE;
+ case STATE_INACTIVE:
+ case STATE_ERROR:
+ break;
+ }
+}
+
+void service_kill(struct service* s, int signal) {
+ if (!s->pid)
+ return;
+
+ if (s->state == STATE_ACTIVE_FOREGROUND)
+ kill(s->pid, signal);
+}
diff --git a/src/finit/supervise.c b/src/finit/supervise.c
@@ -0,0 +1,170 @@
+#include "../common/util.h"
+#include "config.h"
+#include "service.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <setjmp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+
+static void signal_child(int unused) {
+ (void) unused;
+
+ int status;
+ pid_t died_pid;
+ struct service* s = NULL;
+
+ if ((died_pid = wait(&status)) == -1) {
+ print_errno("error: cannot wait for process: %s\n");
+ return;
+ }
+
+ if (!WIFEXITED(status) && !WIFSIGNALED(status))
+ return;
+
+ for (int i = 0; i < services_size; i++) {
+ if (services[i].pid == died_pid) {
+ s = &services[i];
+ break;
+ }
+ }
+ if (s == NULL)
+ return;
+
+ service_handle_exit(s, WIFSIGNALED(status), WIFSIGNALED(status) ? WTERMSIG(status) : WEXITSTATUS(status));
+}
+
+static void update_services(void) {
+ struct service* s;
+
+ for (int i = 0; i < services_size; i++) {
+ s = &services[i];
+ if (s->state == STATE_INACTIVE || s->state == STATE_ERROR)
+ s->stop_timeout = 0;
+
+ if (s->state == STATE_ERROR)
+ continue;
+
+ if (s->stop_timeout != 0) {
+ if (time(NULL) - s->stop_timeout >= SV_STOP_TIMEOUT) {
+ printf(":: service '%s' doesn't terminate, killing...\n", s->name);
+ service_kill(s, SIGKILL);
+ s->stop_timeout = 0;
+ }
+ } else if (s->state == STATE_INACTIVE && service_need_restart(s)) {
+ service_start(s);
+ }
+ }
+}
+
+static void control_sockets(void) {
+ struct service* s;
+ char cmd;
+
+ for (int i = 0; i < services_size; i++) {
+ s = &services[i];
+ while (read(s->control, &cmd, 1) == 1) {
+ printf("handling '%c' from %s\n", cmd, s->name);
+ service_handle_command(s, cmd);
+ }
+ }
+}
+
+void stop_dummies(void) {
+ for (int i = 0; i < services_size; i++) {
+ if (services[i].state != STATE_ACTIVE_DUMMY || services[i].restart == S_RESTART)
+ continue;
+
+ for (int j = 0; j < services[i].children_size; j++) {
+ struct service* dep = services[i].children[j];
+ if (dep->state != STATE_INACTIVE && dep->state != STATE_ERROR)
+ goto dont_stop;
+ }
+
+ service_stop(&services[i]);
+
+ dont_stop:;
+ }
+}
+
+int service_supervise(const char* service_dir_, const char* service, bool once) {
+ struct sigaction sigact = { 0 };
+ struct service* s;
+
+ daemon_running = true;
+
+ sigact.sa_handler = signal_child;
+ sigaction(SIGCHLD, &sigact, NULL);
+ sigact.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &sigact, NULL);
+
+ service_dir_path = service_dir_;
+ if ((service_dir = open(service_dir_, O_DIRECTORY)) == -1) {
+ print_errno("error: cannot open directory %s: %s\n", service_dir_);
+ return 1;
+ }
+
+ if ((null_fd = open("/dev/null", O_RDWR)) == -1) {
+ print_errno("error: cannot open /dev/null: %s\n");
+ null_fd = 1;
+ }
+
+ printf(":: starting services\n");
+
+ service_refresh_directory();
+
+ if ((s = service_get(service)) == NULL) {
+ fprintf(stderr, "error: cannot start '%s': not found\n", service);
+ goto cleanup;
+ }
+
+ s->restart = once ? S_ONCE : S_RESTART;
+ service_start(s);
+
+
+ bool cont;
+ // accept connections and handle requests
+ do {
+ if (!daemon_running) {
+ for (int i = 0; i < services_size; i++) {
+ s = &services[i];
+ service_stop(s);
+ }
+ }
+
+ service_refresh_directory();
+ stop_dummies();
+ control_sockets();
+ update_services();
+
+ sleep(SV_CHECK_INTERVAL);
+
+ cont = false;
+ for (int i = 0; i < services_size; i++) {
+ if (services[i].state != STATE_INACTIVE && services[i].state != STATE_ERROR)
+ cont = true;
+ }
+ } while (cont);
+
+ printf(":: terminating\n");
+
+ printf(":: all services stopped\n");
+
+cleanup:
+
+ close(service_dir);
+ close(null_fd);
+
+ signal(SIGPIPE, SIG_DFL);
+ signal(SIGCHLD, SIG_DFL);
+ return 0;
+}
diff --git a/src/fsvc/Makefile b/src/fsvc/Makefile
@@ -0,0 +1,11 @@
+TOPDIR=../..
+-include $(TOPDIR)/config.mk
+
+OBJS += ../finit/message.o ../common/util.o signame.o
+BINS = fsvc
+INTERM = fsvc.8.txt
+MANS = fsvc.8
+PAGES = fsvc.8.html
+HEADERS = ../common/util.h ../finit/message.h ../finit/service.h signame.h
+
+include $(TOPDIR)/mk/prog.mk
diff --git a/man/fsvc.8.txt b/src/fsvc/fsvc.8.txt
diff --git a/src/fsvc/fsvc.c b/src/fsvc/fsvc.c
@@ -0,0 +1,327 @@
+// +objects: message.o util.o signame.o
+
+#include "../common/util.h"
+#include "../finit/message.h"
+#include "../finit/service.h"
+#include "config.h"
+#include "signame.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+const char* current_prog(void) {
+ return "fsvc";
+}
+
+static const char* const command_names[][2] = {
+ { "up", "u" }, // starts the services, pin as started
+ { "down", "d" }, // stops the service, pin as stopped
+ { "once", "o" }, // same as xup
+ { "term", "t" }, // same as down
+ { "kill", "k" }, // sends kill, pin as stopped
+ { "pause", "p" }, // pauses the service
+ { "cont", "c" }, // resumes the service
+ { "reset", "r" }, // resets the service
+ { "alarm", "a" }, // sends alarm
+ { "hup", "h" }, // sends hup
+ { "int", "i" }, // sends interrupt
+ { "quit", "q" }, // sends quit
+ { "1", "1" }, // sends usr1
+ { "2", "2" }, // sends usr2
+ { "usr1", "1" }, // sends usr1
+ { "usr2", "2" }, // sends usr2
+ { "exit", "x" }, // does nothing
+ { "restart", "!du" }, // restarts the service, don't modify pin
+ { "start", "!u" }, // start the service, pin as started, print status
+ { "stop", "!d" }, // stop the service, pin as stopped, print status
+ { "status", "!" }, // print status
+ { "check", "!" }, // print status
+ { "enable", "!.e" }, // enable service
+ { "disable", "!.d" }, // disable service
+ { 0, 0 }
+};
+
+static const struct option long_options[] = {
+ { "version", no_argument, NULL, 'V' },
+ { "wait", no_argument, NULL, 'w' },
+ { 0 }
+};
+
+struct service_decode {
+ int state;
+ pid_t pid;
+ time_t state_change;
+ bool restart;
+ bool once;
+ bool is_depends;
+ bool wants_up;
+ int last_exit;
+ int return_code;
+ uint8_t fail_count;
+ bool paused;
+ bool is_terminating;
+};
+
+static void decode(struct service_decode* s, const struct service_serial* buffer) {
+ uint64_t tai = ((uint64_t) buffer->status_change[0] << 56) |
+ ((uint64_t) buffer->status_change[1] << 48) |
+ ((uint64_t) buffer->status_change[2] << 40) |
+ ((uint64_t) buffer->status_change[3] << 32) |
+ ((uint64_t) buffer->status_change[4] << 24) |
+ ((uint64_t) buffer->status_change[5] << 16) |
+ ((uint64_t) buffer->status_change[6] << 8) |
+ ((uint64_t) buffer->status_change[7] << 0);
+
+ s->state_change = tai - 4611686018427387914ULL;
+
+ s->state = buffer->state;
+ s->return_code = buffer->return_code;
+ s->fail_count = buffer->fail_count;
+ s->is_terminating = (buffer->flags >> 4) & 0x01;
+ s->once = (buffer->flags >> 3) & 0x01;
+ s->restart = (buffer->flags >> 2) & 0x01;
+ s->last_exit = (buffer->flags >> 0) & 0x03;
+
+ s->pid = (buffer->pid[0] << 0) |
+ (buffer->pid[1] << 8) |
+ (buffer->pid[2] << 16) |
+ (buffer->pid[3] << 24);
+
+ s->paused = buffer->paused;
+ s->wants_up = buffer->restart == 'u';
+
+ s->is_depends = s->wants_up != (s->once || s->restart);
+}
+
+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) {
+ // no custom commands defined
+
+ (void) dir, (void) command;
+
+ return -1;
+}
+
+static int send_command(int dir, const char* command) {
+ 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) == -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;
+ struct service_decode 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);
+
+ decode(&s, &buffer);
+
+ timeval = time(NULL) - s.state_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_ERROR:
+ printf("dead (error)");
+ break;
+ }
+
+ if (s.paused)
+ printf(" & paused");
+
+ printf(" since %lu%s", timeval, timeunit);
+
+ if (s.once == S_ONCE)
+ printf(", started once");
+
+ if (s.restart)
+ printf(", should restart");
+
+ if (s.is_depends)
+ 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(", crashed 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, fd,
+ timeout = SV_STATUS_WAIT;
+ time_t mod, start;
+
+ const char* command = NULL;
+ const char* service;
+
+ 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 (const char** ident = (void*) command_names; ident[0] != NULL; ident++) {
+ if (streq(ident[0], argv[0])) {
+ command = ident[1];
+ 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++) {
+ service = progname(argv[i]);
+
+ if ((dir = open(argv[i], O_DIRECTORY)) == -1) {
+ fprintf(stderr, "error: %s: cannot open directory: %s\n", argv[i], strerror(errno));
+ continue;
+ }
+
+ if ((fd = openat(dir, "supervise/ok", O_WRONLY | O_NONBLOCK)) == -1) {
+ fprintf(stderr, "error: %s: cannot open supervise/control: %s\n", argv[i], strerror(errno));
+ continue;
+ }
+ close(fd);
+
+ if ((mod = get_mtime(dir)) == -1) {
+ fprintf(stderr, "error: %s: cannot get modify-time\n", argv[i]);
+ continue;
+ }
+
+ if (command[0] != '\0') {
+ if (send_command(dir, command) == -1) {
+ fprintf(stderr, "error: %s: unable to send command\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: ", service);
+
+ if (status(dir) == -1)
+ printf("unable to access supervise/status\n");
+ }
+ }
+}
diff --git a/src/fsvc/signame.c b/src/fsvc/signame.c
@@ -0,0 +1,203 @@
+#include "signame.h"
+
+#include "../common/util.h"
+
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#define SIGNUM_NAME(name) \
+ { SIG##name, #name }
+
+struct signal_name {
+ int num;
+ const char* name;
+};
+
+static struct signal_name signal_names[] = {
+/* Signals required by POSIX 1003.1-2001 base, listed in
+ traditional numeric order where possible. */
+#ifdef SIGHUP
+ SIGNUM_NAME(HUP),
+#endif
+#ifdef SIGINT
+ SIGNUM_NAME(INT),
+#endif
+#ifdef SIGQUIT
+ SIGNUM_NAME(QUIT),
+#endif
+#ifdef SIGILL
+ SIGNUM_NAME(ILL),
+#endif
+#ifdef SIGTRAP
+ SIGNUM_NAME(TRAP),
+#endif
+#ifdef SIGABRT
+ SIGNUM_NAME(ABRT),
+#endif
+#ifdef SIGFPE
+ SIGNUM_NAME(FPE),
+#endif
+#ifdef SIGKILL
+ SIGNUM_NAME(KILL),
+#endif
+#ifdef SIGSEGV
+ SIGNUM_NAME(SEGV),
+#endif
+/* On Haiku, SIGSEGV == SIGBUS, but we prefer SIGSEGV to match
+ strsignal.c output, so SIGBUS must be listed second. */
+#ifdef SIGBUS
+ SIGNUM_NAME(BUS),
+#endif
+#ifdef SIGPIPE
+ SIGNUM_NAME(PIPE),
+#endif
+#ifdef SIGALRM
+ SIGNUM_NAME(ALRM),
+#endif
+#ifdef SIGTERM
+ SIGNUM_NAME(TERM),
+#endif
+#ifdef SIGUSR1
+ SIGNUM_NAME(USR1),
+#endif
+#ifdef SIGUSR2
+ SIGNUM_NAME(USR2),
+#endif
+#ifdef SIGCHLD
+ SIGNUM_NAME(CHLD),
+#endif
+#ifdef SIGURG
+ SIGNUM_NAME(URG),
+#endif
+#ifdef SIGSTOP
+ SIGNUM_NAME(STOP),
+#endif
+#ifdef SIGTSTP
+ SIGNUM_NAME(TSTP),
+#endif
+#ifdef SIGCONT
+ SIGNUM_NAME(CONT),
+#endif
+#ifdef SIGTTIN
+ SIGNUM_NAME(TTIN),
+#endif
+#ifdef SIGTTOU
+ SIGNUM_NAME(TTOU),
+#endif
+
+/* Signals required by POSIX 1003.1-2001 with the XSI extension. */
+#ifdef SIGSYS
+ SIGNUM_NAME(SYS),
+#endif
+#ifdef SIGPOLL
+ SIGNUM_NAME(POLL),
+#endif
+#ifdef SIGVTALRM
+ SIGNUM_NAME(VTALRM),
+#endif
+#ifdef SIGPROF
+ SIGNUM_NAME(PROF),
+#endif
+#ifdef SIGXCPU
+ SIGNUM_NAME(XCPU),
+#endif
+#ifdef SIGXFSZ
+ SIGNUM_NAME(XFSZ),
+#endif
+
+/* Unix Version 7. */
+#ifdef SIGIOT
+ SIGNUM_NAME(IOT), /* Older name for ABRT. */
+#endif
+#ifdef SIGEMT
+ SIGNUM_NAME(EMT),
+#endif
+
+/* USG Unix. */
+#ifdef SIGPHONE
+ SIGNUM_NAME(PHONE),
+#endif
+#ifdef SIGWIND
+ SIGNUM_NAME(WIND),
+#endif
+
+/* Unix System V. */
+#ifdef SIGCLD
+ SIGNUM_NAME(CLD),
+#endif
+#ifdef SIGPWR
+ SIGNUM_NAME(PWR),
+#endif
+
+/* GNU/Linux 2.2 and Solaris 8. */
+#ifdef SIGCANCEL
+ SIGNUM_NAME(CANCEL),
+#endif
+#ifdef SIGLWP
+ SIGNUM_NAME(LWP),
+#endif
+#ifdef SIGWAITING
+ SIGNUM_NAME(WAITING),
+#endif
+#ifdef SIGFREEZE
+ SIGNUM_NAME(FREEZE),
+#endif
+#ifdef SIGTHAW
+ SIGNUM_NAME(THAW),
+#endif
+#ifdef SIGLOST
+ SIGNUM_NAME(LOST),
+#endif
+#ifdef SIGWINCH
+ SIGNUM_NAME(WINCH),
+#endif
+
+/* GNU/Linux 2.2. */
+#ifdef SIGINFO
+ SIGNUM_NAME(INFO),
+#endif
+#ifdef SIGIO
+ SIGNUM_NAME(IO),
+#endif
+#ifdef SIGSTKFLT
+ SIGNUM_NAME(STKFLT),
+#endif
+
+/* OpenBSD. */
+#ifdef SIGTHR
+ SIGNUM_NAME(THR),
+#endif
+
+ { 0, NULL },
+};
+
+int signame(char const* name) {
+ char* endptr;
+ int signum;
+
+ if ((signum = strtol(name, &endptr, 10)) && endptr == strchr(name, '\0'))
+ return signum;
+
+ // startswith SIG, remove that so -SIGKILL == -KILL
+ if (strncmp(name, "SIG", 3) == 0) {
+ name += 3;
+ }
+
+ // search for name
+ for (struct signal_name* sigpair = signal_names; sigpair->num != 0; sigpair++)
+ if (streq(sigpair->name, name))
+ return sigpair->num;
+
+ return -1;
+}
+
+const char* sigabbr(int signal) {
+ // search for name
+ for (struct signal_name* sigpair = signal_names; sigpair->num != 0; sigpair++)
+ if (sigpair->num == signal)
+ return sigpair->name;
+
+ return "UNKNOWN";
+}
diff --git a/include/signame.h b/src/fsvc/signame.h
diff --git a/src/halt/Makefile b/src/halt/Makefile
@@ -0,0 +1,11 @@
+TOPDIR=../..
+-include $(TOPDIR)/config.mk
+
+OBJS += wtmp.o ../common/util.o
+BINS = halt poweroff reboot shutdown
+INTERM = halt.8 shutdown.8.txt
+MANS = halt.8 shutdown.8
+PAGES = halt.8.html shutdown.8.html
+HEADERS = ../common/util.h wtmp.h
+
+include $(TOPDIR)/mk/prog.mk
diff --git a/man/halt.8.txt b/src/halt/halt.8.txt
diff --git a/src/halt/halt.c b/src/halt/halt.c
@@ -0,0 +1,83 @@
+#include "../common/util.h"
+#include "wtmp.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/reboot.h>
+#include <unistd.h>
+
+
+const char* current_prog(void) {
+ return "halt";
+}
+
+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_errno("reboot failed: %s\n");
+ }
+
+ return 0;
+}
diff --git a/bin/poweroff.lnk b/src/halt/poweroff.lnk
diff --git a/bin/reboot.lnk b/src/halt/reboot.lnk
diff --git a/man/shutdown.8.txt b/src/halt/shutdown.8.txt
diff --git a/src/halt/shutdown.sh b/src/halt/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/wtmp.c b/src/halt/wtmp.c
diff --git a/include/wtmp.h b/src/halt/wtmp.h
diff --git a/src/handle_exit.c b/src/handle_exit.c
@@ -1,105 +0,0 @@
-#include "parse.h"
-#include "service.h"
-
-#include <errno.h>
-#include <limits.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-
-static void do_finish(struct service* s) {
- struct stat st;
-
- if (fstatat(s->dir, "finish", &st, 0) != -1 && st.st_mode & S_IXUSR) {
- if ((s->pid = fork_dup_cd_exec(s->dir, "./finish", null_fd, null_fd, null_fd)) == -1) {
- print_errno("error: cannot execute ./finish: %s\n");
- service_update_state(s, STATE_INACTIVE);
- } else {
- service_update_state(s, STATE_FINISHING);
- }
- } else if (s->fail_count == SV_FAIL_MAX) {
- service_update_state(s, STATE_ERROR);
- printf("%s died\n", s->name);
- } else {
- service_update_state(s, s->restart == S_ONCE ? STATE_DONE : STATE_INACTIVE);
- }
-}
-
-
-void service_handle_exit(struct service* s, bool signaled, int return_code) {
- struct stat st;
-
- s->pid = 0;
- s->stop_timeout = 0;
-
- if (s->restart == S_ONCE)
- s->restart = S_DOWN;
-
- switch (s->state) {
- case STATE_SETUP:
- service_run(s);
- break;
- case STATE_ACTIVE_FOREGROUND:
- if (signaled) {
- s->last_exit = EXIT_SIGNALED;
- s->return_code = return_code;
- s->fail_count++;
-
- printf("%s killed thought signal %d\n", s->name, s->return_code);
- } else {
- s->last_exit = EXIT_NORMAL;
- s->return_code = return_code;
- if (s->return_code > 0)
- s->fail_count++;
- else
- s->fail_count = 0;
-
- printf("%s exited with code %d\n", s->name, s->return_code);
- }
-
- do_finish(s);
-
- break;
- case STATE_ACTIVE_DUMMY:
- case STATE_ACTIVE_BACKGROUND:
- case STATE_STOPPING:
- do_finish(s);
- break;
-
- case STATE_FINISHING:
- if (s->fail_count == SV_FAIL_MAX) {
- service_update_state(s, STATE_ERROR);
- printf("%s died\n", s->name);
- } else {
- service_update_state(s, s->restart == S_ONCE ? STATE_DONE : STATE_INACTIVE);
- }
- break;
- case STATE_STARTING:
- if (!signaled && return_code == 0) {
- if (fstatat(s->dir, "stop", &st, 0) != -1 && st.st_mode & S_IXUSR) {
- service_update_state(s, STATE_ACTIVE_BACKGROUND);
- } else {
- do_finish(s);
- }
- } else if (!signaled) {
- s->last_exit = EXIT_NORMAL;
- s->return_code = return_code;
-
- do_finish(s);
- } else { // signaled
- s->last_exit = EXIT_SIGNALED;
- s->return_code = return_code;
-
- do_finish(s);
- }
- break;
-
- case STATE_ERROR:
- case STATE_INACTIVE:
- case STATE_DONE:
- printf("unexpected error: %s died but it's inactive\n", s->name);
- }
-}
diff --git a/src/message.c b/src/message.c
@@ -1,52 +0,0 @@
-#include "message.h"
-
-#include "config.h"
-
-#include <libgen.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-static const char* prog_usage[] = {
- [PROG_FINIT] = "init <0|6>",
- [PROG_FSVC] = "fsvc <command> [-v --verbose] [-V --version] [-r --runlevel <level>] [-s --service-dir <path>]\n"
- " fsvc start [-p --pin] <service>\n"
- " fsvc stop [-p --pin] <service>\n"
- " fsvc enable [-o --once] <service>\n"
- " fsvc disable [-o --once] <service>\n"
- " fsvc kill <service> <signal|signum>\n"
- " fsvc status [-c --check] <service>\n"
- " fsvc pause <service>\n"
- " fsvc resume <service>\n"
- " fsvc switch [-f --reset] <runlevel>",
- [PROG_FSVS] = "fsvs [-V --version] [-v --verbose] [-f --force] <service-dir> <runlevel>",
- [PROG_HALT] = "halt [-n] [-f] [-d] [-w] [-B]",
- [PROG_POWEROFF] = "poweroff [-n] [-f] [-d] [-w] [-B]",
- [PROG_REBOOT] = "reboot [-n] [-f] [-d] [-w] [-B]",
- [PROG_SEEDRNG] = "seedrng",
- [PROG_SIGREMAP] = "sigremap [-s --single] [-v --verbose] [-V --version] <old-signal=new-signal...> <command> [args...]",
- [PROG_VLOGGER] = "vlogger [-isS] [-f file] [-p pri] [-t tag] [message ...]",
- [PROG_ZZZ] = "zzz [-n --noop] [-S --freeze] [-z --suspend] [-Z --hibernate] [-R --reboot] [-H --hybrid]"
-};
-
-static const char* prog_manual[] = {
- [PROG_FINIT] = "finit 8",
- [PROG_FSVC] = "fsvc 8",
- [PROG_FSVS] = "fsvs 8",
- [PROG_HALT] = "halt 8",
- [PROG_POWEROFF] = "poweroff 8",
- [PROG_REBOOT] = "reboot 8",
- [PROG_SEEDRNG] = "seedrng 8",
- [PROG_SIGREMAP] = "sigremap 8",
- [PROG_VLOGGER] = "vlogger 1",
- [PROG_ZZZ] = "zzz 8"
-};
-
-void print_usage_exit(prog_t prog, int status) {
- fprintf(status ? stderr : stdout, "Usage: %s\n\nCheck manual '%s' for more information.\n", prog_usage[prog], prog_manual[prog]);
- exit(status);
-}
-
-void print_version_exit(void) {
- printf(SV_VERSION);
- exit(0);
-}
diff --git a/src/modules-load/Makefile b/src/modules-load/Makefile
@@ -0,0 +1,11 @@
+TOPDIR=../..
+-include $(TOPDIR)/config.mk
+
+OBJS = ../common/util.o
+BINS = modules-load
+INTERM = modules-load.8.txt
+MANS = modules-load.8
+PAGES = modules-load.8.html
+HEADERS = ../common/util.h
+
+include $(TOPDIR)/mk/prog.mk
+\ No newline at end of file
diff --git a/man/modules-load.8.txt b/src/modules-load/modules-load.8.txt
diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c
@@ -0,0 +1,145 @@
+// +objects: util.o
+
+#include "../common/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
+
+
+const char* current_prog(void) {
+ return "modules-load";
+}
+
+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_errno("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_errno("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/");
+
+ if (modules_size == 0)
+ return 0;
+
+ 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_errno("cannot exec modprobe: %s");
+ return 1;
+}
diff --git a/src/register.c b/src/register.c
@@ -1,98 +0,0 @@
-#include "config.h"
-#include "service.h"
-#include "util.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-
-static int init_supervise(struct service* s) {
- int fd;
- struct stat st;
-
- if (fstatat(s->dir, "supervise", &st, 0) == -1 && mkdirat(s->dir, "supervise", 0755) == -1) {
- return -1;
- }
-
- if (fstatat(s->dir, "supervise/ok", &st, 0) == -1 && mkfifoat(s->dir, "supervise/ok", 0666) == -1) {
- print_errno("cannot create fifo at supervise/ok: %s\n");
- return -1;
- }
-
- if (fstatat(s->dir, "supervise/control", &st, 0) == -1 && mkfifoat(s->dir, "supervise/control", 0644) == -1) {
- print_errno("cannot create fifo at supervise/control: %s\n");
- return -1;
- }
-
- if (openat(s->dir, "supervise/ok", O_RDONLY | O_NONBLOCK) == -1) {
- print_errno("cannot open supervise/ok: %s\n");
- return -1;
- }
-
- if ((s->control = openat(s->dir, "supervise/control", O_RDONLY | O_NONBLOCK)) == -1) {
- print_errno("cannot open supervise/ok: %s\n");
- return -1;
- }
-
- if ((fd = openat(s->dir, "supervise/lock", O_CREAT | O_WRONLY, 0644)) == -1) {
- print_errno("cannot create supervise/lock: %s\n");
- return -1;
- }
- close(fd);
-
- return 0;
-}
-
-struct service* service_register(int dir, const char* name, bool is_log_service) {
- struct service* s;
- struct stat st;
-
- if ((s = service_get(name)) == NULL) {
- s = &services[services_size++];
- s->state = STATE_INACTIVE;
- s->restart = S_DOWN;
- s->last_exit = EXIT_NONE;
- s->return_code = 0;
- s->fail_count = 0;
- s->log_service = NULL;
- s->paused = false;
- s->log_pipe.read = 0;
- s->log_pipe.write = 0;
- s->is_log_service = is_log_service;
- s->stop_timeout = 0;
-
- if ((s->dir = openat(dir, name, O_DIRECTORY)) == -1) {
- print_errno("error: cannot open '%s': %s\n", name);
- services_size--;
- return NULL;
- }
-
- if (init_supervise(s) == -1) {
- services_size--;
- return NULL;
- }
-
- strncpy(s->name, name, sizeof(s->name));
-
- service_update_state(s, -1);
- }
-
- if (s->is_log_service) {
- if (s->log_pipe.read == 0 || s->log_pipe.write == 0)
- pipe((int*) &s->log_pipe);
-
- } else if (!s->log_service && fstatat(s->dir, "log", &st, 0) != -1 && S_ISDIR(st.st_mode)) {
-
- if (!s->log_service)
- s->log_service = service_register(s->dir, "log", true);
- }
-
- service_write(s);
-
- return s;
-}
diff --git a/src/seedrng/Makefile b/src/seedrng/Makefile
@@ -0,0 +1,11 @@
+TOPDIR=../..
+-include $(TOPDIR)/config.mk
+
+OBJS =
+BINS = seedrng
+INTERM =
+MANS =
+PAGES =
+HEADERS =
+
+include $(TOPDIR)/mk/prog.mk
+\ No newline at end of file
diff --git a/bin/seedrng.c b/src/seedrng/seedrng.c
diff --git a/src/service.c b/src/service.c
@@ -1,91 +0,0 @@
-#include "service.h"
-
-#include <dirent.h>
-#include <errno.h>
-#include <limits.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-
-struct service services[SV_SERVICE_MAX];
-int services_size = 0;
-char runlevel[SV_NAME_MAX];
-int service_dir;
-const char* service_dir_path;
-int control_socket;
-int null_fd;
-struct service* depends[SV_DEPENDENCY_MAX][2];
-int depends_size;
-bool daemon_running;
-
-struct service* service_get(const char* name) {
- for (int i = 0; i < services_size; i++) {
- if (streq(services[i].name, name))
- return &services[i];
- }
- return NULL;
-}
-
-int service_refresh_directory(void) {
- DIR* dp;
- struct dirent* ep;
- struct stat st;
- struct service* s;
-
- if ((dp = opendir(service_dir_path)) == NULL) {
- print_errno("error: cannot open service directory: %s\n");
- return -1;
- }
-
- for (int i = 0; i < services_size; i++) {
- s = &services[i];
- if (fstat(s->dir, &st) == -1 || !S_ISDIR(st.st_mode)) {
- service_stop(s);
- close(s->dir);
- close(s->control);
- }
- }
-
- while ((ep = readdir(dp)) != NULL) {
- if (ep->d_name[0] == '.')
- continue;
-
- if (fstatat(service_dir, ep->d_name, &st, 0) == -1 || !S_ISDIR(st.st_mode))
- continue;
-
- service_register(service_dir, ep->d_name, false);
- }
-
- closedir(dp);
-
- depends_size = 0;
- for (int i = 0; i < services_size; i++)
- service_update_dependency(&services[i]);
-
- return 0;
-}
-
-
-bool service_need_restart(struct service* s) {
- struct service* d;
-
- if (!daemon_running)
- return false;
-
- if (s->restart == S_RESTART)
- return true;
-
- for (int i = 0; i < depends_size; i++) {
- if (depends[i][1] != s)
- continue;
-
- d = depends[i][0];
- if (service_need_restart(d))
- return true;
- }
-
- return false;
-}
diff --git a/src/signame.c b/src/signame.c
@@ -1,203 +0,0 @@
-#include "signame.h"
-
-#include "util.h"
-
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-
-
-#define SIGNUM_NAME(name) \
- { SIG##name, #name }
-
-typedef struct signal_name {
- int num;
- const char* name;
-} signal_name_t;
-
-static signal_name_t signal_names[] = {
-/* Signals required by POSIX 1003.1-2001 base, listed in
- traditional numeric order where possible. */
-#ifdef SIGHUP
- SIGNUM_NAME(HUP),
-#endif
-#ifdef SIGINT
- SIGNUM_NAME(INT),
-#endif
-#ifdef SIGQUIT
- SIGNUM_NAME(QUIT),
-#endif
-#ifdef SIGILL
- SIGNUM_NAME(ILL),
-#endif
-#ifdef SIGTRAP
- SIGNUM_NAME(TRAP),
-#endif
-#ifdef SIGABRT
- SIGNUM_NAME(ABRT),
-#endif
-#ifdef SIGFPE
- SIGNUM_NAME(FPE),
-#endif
-#ifdef SIGKILL
- SIGNUM_NAME(KILL),
-#endif
-#ifdef SIGSEGV
- SIGNUM_NAME(SEGV),
-#endif
-/* On Haiku, SIGSEGV == SIGBUS, but we prefer SIGSEGV to match
- strsignal.c output, so SIGBUS must be listed second. */
-#ifdef SIGBUS
- SIGNUM_NAME(BUS),
-#endif
-#ifdef SIGPIPE
- SIGNUM_NAME(PIPE),
-#endif
-#ifdef SIGALRM
- SIGNUM_NAME(ALRM),
-#endif
-#ifdef SIGTERM
- SIGNUM_NAME(TERM),
-#endif
-#ifdef SIGUSR1
- SIGNUM_NAME(USR1),
-#endif
-#ifdef SIGUSR2
- SIGNUM_NAME(USR2),
-#endif
-#ifdef SIGCHLD
- SIGNUM_NAME(CHLD),
-#endif
-#ifdef SIGURG
- SIGNUM_NAME(URG),
-#endif
-#ifdef SIGSTOP
- SIGNUM_NAME(STOP),
-#endif
-#ifdef SIGTSTP
- SIGNUM_NAME(TSTP),
-#endif
-#ifdef SIGCONT
- SIGNUM_NAME(CONT),
-#endif
-#ifdef SIGTTIN
- SIGNUM_NAME(TTIN),
-#endif
-#ifdef SIGTTOU
- SIGNUM_NAME(TTOU),
-#endif
-
-/* Signals required by POSIX 1003.1-2001 with the XSI extension. */
-#ifdef SIGSYS
- SIGNUM_NAME(SYS),
-#endif
-#ifdef SIGPOLL
- SIGNUM_NAME(POLL),
-#endif
-#ifdef SIGVTALRM
- SIGNUM_NAME(VTALRM),
-#endif
-#ifdef SIGPROF
- SIGNUM_NAME(PROF),
-#endif
-#ifdef SIGXCPU
- SIGNUM_NAME(XCPU),
-#endif
-#ifdef SIGXFSZ
- SIGNUM_NAME(XFSZ),
-#endif
-
-/* Unix Version 7. */
-#ifdef SIGIOT
- SIGNUM_NAME(IOT), /* Older name for ABRT. */
-#endif
-#ifdef SIGEMT
- SIGNUM_NAME(EMT),
-#endif
-
-/* USG Unix. */
-#ifdef SIGPHONE
- SIGNUM_NAME(PHONE),
-#endif
-#ifdef SIGWIND
- SIGNUM_NAME(WIND),
-#endif
-
-/* Unix System V. */
-#ifdef SIGCLD
- SIGNUM_NAME(CLD),
-#endif
-#ifdef SIGPWR
- SIGNUM_NAME(PWR),
-#endif
-
-/* GNU/Linux 2.2 and Solaris 8. */
-#ifdef SIGCANCEL
- SIGNUM_NAME(CANCEL),
-#endif
-#ifdef SIGLWP
- SIGNUM_NAME(LWP),
-#endif
-#ifdef SIGWAITING
- SIGNUM_NAME(WAITING),
-#endif
-#ifdef SIGFREEZE
- SIGNUM_NAME(FREEZE),
-#endif
-#ifdef SIGTHAW
- SIGNUM_NAME(THAW),
-#endif
-#ifdef SIGLOST
- SIGNUM_NAME(LOST),
-#endif
-#ifdef SIGWINCH
- SIGNUM_NAME(WINCH),
-#endif
-
-/* GNU/Linux 2.2. */
-#ifdef SIGINFO
- SIGNUM_NAME(INFO),
-#endif
-#ifdef SIGIO
- SIGNUM_NAME(IO),
-#endif
-#ifdef SIGSTKFLT
- SIGNUM_NAME(STKFLT),
-#endif
-
-/* OpenBSD. */
-#ifdef SIGTHR
- SIGNUM_NAME(THR),
-#endif
-
- { 0, NULL },
-};
-
-int signame(char const* name) {
- char* endptr;
- int signum;
-
- if ((signum = strtol(name, &endptr, 10)) && endptr == strchr(name, '\0'))
- return signum;
-
- // startswith SIG, remove that so -SIGKILL == -KILL
- if (strncmp(name, "SIG", 3) == 0) {
- name += 3;
- }
-
- // search for name
- for (signal_name_t* sigpair = signal_names; sigpair->num != 0; sigpair++)
- if (streq(sigpair->name, name))
- return sigpair->num;
-
- return -1;
-}
-
-const char* sigabbr(int signal) {
- // search for name
- for (signal_name_t* sigpair = signal_names; sigpair->num != 0; sigpair++)
- if (sigpair->num == signal)
- return sigpair->name;
-
- return "UNKNOWN";
-}
diff --git a/src/sigremap/Makefile b/src/sigremap/Makefile
@@ -0,0 +1,11 @@
+TOPDIR=../..
+-include $(TOPDIR)/config.mk
+
+OBJS =
+BINS = zzz ZZZ
+INTERM = zzz.8.txt
+MANS = zzz.8
+PAGES = zzz.8.html
+HEADERS =
+
+include $(TOPDIR)/mk/prog.mk
+\ No newline at end of file
diff --git a/man/sigremap.8.txt b/src/sigremap/sigremap.8.txt
diff --git a/src/sigremap/sigremap.c b/src/sigremap/sigremap.c
@@ -0,0 +1,284 @@
+/* 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.
+ */
+
+// +objects: message.o signame.o
+
+
+#include "../common/util.h"
+#include "message.h"
+#include "signame.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>
+
+
+const char* current_prog(void) {
+ return "sigremap";
+}
+
+#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_errno("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_errno("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_errno("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/stage.c b/src/stage.c
@@ -1,82 +0,0 @@
-#include "stage.h"
-
-#include "config.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 char* stage_exec[][4] = {
- [0] = { SV_START_EXEC, NULL },
- [2] = { SV_STOP_EXEC, NULL },
-};
-
-
-bool handle_stage(int stage) {
- int pid, ttyfd, exitstat, sig = 0;
- sigset_t ss;
- bool cont = true;
-
- while ((pid = fork()) == -1) {
- print_errno("error: unable to fork for stage1: %s\n");
- sleep(5);
- }
- if (pid == 0) {
- /* child */
-
- if (stage == 0) {
- /* stage 1 gets full control of console */
- if ((ttyfd = open("/dev/console", O_RDWR)) == -1) {
- print_errno("error: unable to open /dev/console: %s\n");
- } else {
- ioctl(ttyfd, TIOCSCTTY, NULL); // make the controlling process
- dup2(ttyfd, 0);
- if (ttyfd > 2) close(ttyfd);
- }
- }
-
- sigblock_all(true);
-
- printf("enter stage %d\n", stage);
- execv(stage_exec[stage][0], stage_exec[stage]);
- print_errno("error: unable to exec stage %d: %s\n", stage);
- _exit(1);
- }
-
- sigemptyset(&ss);
- sigaddset(&ss, SIGCHLD);
- sigaddset(&ss, SIGUSR1);
- sigaddset(&ss, SIGCONT);
-
- sigwait(&ss, &sig);
-
- if (stage == 1 && sig != SIGCHLD)
- kill(pid, SIGTERM);
-
- if (waitpid(pid, &exitstat, 0) == -1) {
- print_errno("warn: waitpid failed: %s");
- sleep(5);
- }
-
- reclaim_console();
-
- if (stage == 0) {
- if (!WIFEXITED(exitstat) || WEXITSTATUS(exitstat) != 0) {
- if (WIFSIGNALED(exitstat)) {
- /* this is stage 1 */
- fprintf(stderr, "stage 1 failed: skip stage 2\n");
- cont = false;
- }
- }
- printf("leave stage 1\n");
- }
-
- return cont;
-}
diff --git a/src/start.c b/src/start.c
@@ -1,114 +0,0 @@
-#include "parse.h"
-#include "service.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <grp.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-
-static void set_pipes(struct service* s) {
- if (s->is_log_service) {
- close(s->log_pipe.write);
- dup2(s->log_pipe.read, STDIN_FILENO);
- close(s->log_pipe.read);
- dup2(null_fd, STDOUT_FILENO);
- dup2(null_fd, STDERR_FILENO);
- } else if (s->log_service) { // aka has_log_service
- close(s->log_service->log_pipe.read);
- dup2(s->log_service->log_pipe.write, STDOUT_FILENO);
- dup2(s->log_service->log_pipe.write, STDERR_FILENO);
- close(s->log_service->log_pipe.write);
- dup2(null_fd, STDIN_FILENO);
- } else if (stat_mode("log") & S_IWRITE) { // is not
- int log_fd;
- if ((log_fd = open("log", O_WRONLY | O_TRUNC)) == -1)
- log_fd = null_fd;
-
- dup2(null_fd, STDIN_FILENO);
- dup2(log_fd, STDOUT_FILENO);
- dup2(log_fd, STDERR_FILENO);
- } else if (S_ISREG(stat_mode("nolog"))) {
- dup2(null_fd, STDIN_FILENO);
- dup2(null_fd, STDOUT_FILENO);
- dup2(null_fd, STDERR_FILENO);
- } else {
- char service_log[PATH_MAX];
- int log_fd;
-
- snprintf(service_log, PATH_MAX, "%s/%s.log", SV_LOG_DIR, s->name);
-
- if ((log_fd = open(service_log, O_CREAT | O_WRONLY | O_TRUNC, 0644)) == -1)
- log_fd = null_fd;
-
- dup2(null_fd, STDIN_FILENO);
- dup2(log_fd, STDOUT_FILENO);
- dup2(log_fd, STDERR_FILENO);
- }
-}
-
-void service_run(struct service* s) {
- struct stat st;
-
- if (fstatat(s->dir, "run", &st, 0) != -1 && st.st_mode & S_IXUSR) {
- service_update_state(s, STATE_ACTIVE_FOREGROUND);
- } else if (fstatat(s->dir, "start", &st, 0) != -1 && st.st_mode & S_IXUSR) {
- service_update_state(s, STATE_STARTING);
- } else if (fstatat(s->dir, "depends", &st, 0) != -1 && st.st_mode & S_IREAD) {
- service_update_state(s, STATE_ACTIVE_DUMMY);
- } else {
- // fprintf(stderr, "warn: %s: `run`, `start` or `depends` not found\n", s->name);
- service_update_state(s, STATE_INACTIVE);
- }
-
- if (s->state != STATE_ACTIVE_DUMMY) {
- if ((s->pid = fork()) == -1) {
- print_errno("error: cannot fork process: %s\n");
- exit(1);
- } else if (s->pid == 0) { // child
- if (setsid() == -1)
- print_errno("error: cannot setsid: %s\n");
-
- fchdir(s->dir);
- set_pipes(s);
-
- if (s->state == STATE_STARTING) {
- execl("./start", "./start", NULL);
- } else {
- execl("./run", "./run", NULL);
- }
- print_errno("error: cannot execute service: %s\n");
- _exit(1);
- }
- }
-}
-
-void service_start(struct service* s) {
- struct stat st;
-
- if (!daemon_running || s->state != STATE_INACTIVE)
- return;
-
- printf("starting %s\n", s->name);
- for (int i = 0; i < depends_size; i++) {
- if (depends[i][0] == s)
- service_start(depends[i][1]);
- }
-
- if (fstatat(s->dir, "setup", &st, 0) != -1 && st.st_mode & S_IXUSR) {
- if ((s->pid = fork_dup_cd_exec(s->dir, "./setup", null_fd, null_fd, null_fd)) == -1) {
- print_errno("error: cannot execute ./setup: %s\n");
- service_update_state(s, STATE_INACTIVE);
- } else {
- service_update_state(s, STATE_SETUP);
- }
- } else {
- service_run(s);
- }
- printf("started %s \n", s->name);
-}
diff --git a/src/status.c b/src/status.c
@@ -1,94 +0,0 @@
-#include "service.h"
-#include "util.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/file.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-
-void service_update_state(struct service* s, int state) {
- if (state != -1)
- s->state = state;
-
- s->state_change = time(NULL);
-
- service_write(s);
-}
-
-void service_write(struct service* s) {
- int fd;
- const char* stat_human;
- struct service_serial stat_runit;
-
- if ((fd = openat(s->dir, "supervise/status.new", O_CREAT | O_WRONLY | O_TRUNC, 0644)) == -1) {
- print_errno("cannot open supervise/status: %s\n");
- return;
- }
-
- service_encode(s, &stat_runit);
-
- if (write(fd, &stat_runit, sizeof(stat_runit)) == -1) {
- print_errno("cannot write to supervise/status: %s\n");
- return;
- }
-
- close(fd);
-
- if ((fd = openat(s->dir, "supervise/stat.new", O_CREAT | O_WRONLY | O_TRUNC, 0644)) == -1) {
- print_errno("cannot create supervise/stat: %s\n");
- return;
- }
-
- stat_human = service_status_name(s);
- if (write(fd, stat_human, strlen(stat_human)) == -1) {
- print_errno("cannot write to supervise/stat: %s\n");
- return;
- }
-
- close(fd);
-
- if ((fd = openat(s->dir, "supervise/pid.new", O_CREAT | O_WRONLY | O_TRUNC, 0644)) == -1) {
- print_errno("cannot create supervise/stat: %s\n");
- return;
- }
-
- dprintf(fd, "%d", s->pid);
-
- close(fd);
-
- renameat(s->dir, "supervise/status.new", s->dir, "supervise/status");
- renameat(s->dir, "supervise/stat.new", s->dir, "supervise/stat");
- renameat(s->dir, "supervise/pid.new", s->dir, "supervise/pid");
-}
-
-const char* service_status_name(struct service* s) {
- switch (s->state) {
- case STATE_SETUP:
- return "setup";
- case STATE_STARTING:
- return "starting";
- case STATE_ACTIVE_FOREGROUND:
- return "run";
- case STATE_ACTIVE_BACKGROUND:
- return "run-background";
- case STATE_ACTIVE_DUMMY:
- return "run-dummy";
- case STATE_FINISHING:
- return "finishing";
- case STATE_STOPPING:
- return "stopping";
- case STATE_INACTIVE:
- return "down";
- case STATE_DONE:
- return "done";
- case STATE_ERROR:
- return "dead (error)";
- default:
- return NULL;
- }
-}
diff --git a/src/stop.c b/src/stop.c
@@ -1,48 +0,0 @@
-#include "service.h"
-#include "util.h"
-
-#include <errno.h>
-#include <limits.h>
-#include <signal.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-
-void service_stop(struct service* s) {
- switch (s->state) {
- case STATE_ACTIVE_DUMMY:
- service_handle_exit(s, false, 0);
- break;
- case STATE_ACTIVE_BACKGROUND:
- if ((s->pid = fork_dup_cd_exec(s->dir, "./stop", null_fd, null_fd, null_fd)) == -1) {
- print_errno("error: cannot execute ./stop: %s\n");
- service_update_state(s, STATE_INACTIVE);
- } else {
- service_update_state(s, STATE_STOPPING);
- }
- break;
- case STATE_ACTIVE_FOREGROUND:
- case STATE_SETUP:
- case STATE_STARTING:
- case STATE_STOPPING:
- case STATE_FINISHING:
- s->stop_timeout = time(NULL);
- kill(s->pid, SIGTERM);
- service_update_state(s, -1);
- break;
- case STATE_DONE:
- s->state = STATE_INACTIVE;
- case STATE_INACTIVE:
- case STATE_ERROR:
- break;
- }
-}
-
-void service_kill(struct service* s, int signal) {
- if (!s->pid)
- return;
-
- if (s->state == STATE_ACTIVE_FOREGROUND)
- kill(s->pid, signal);
-}
diff --git a/src/supervise.c b/src/supervise.c
@@ -1,173 +0,0 @@
-#include "config.h"
-#include "service.h"
-#include "util.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/un.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-
-static void signal_child(int unused) {
- (void) unused;
-
- int status;
- pid_t died_pid;
- struct service* s = NULL;
-
- if ((died_pid = wait(&status)) == -1) {
- print_errno("error: cannot wait for process: %s\n");
- return;
- }
-
- if (!WIFEXITED(status) && !WIFSIGNALED(status))
- return;
-
- for (int i = 0; i < services_size; i++) {
- if (services[i].pid == died_pid) {
- s = &services[i];
- break;
- }
- }
- if (s == NULL)
- return;
-
- service_handle_exit(s, WIFSIGNALED(status), WIFSIGNALED(status) ? WTERMSIG(status) : WEXITSTATUS(status));
-}
-
-static void update_services(void) {
- struct service* s;
-
- for (int i = 0; i < services_size; i++) {
- s = &services[i];
- if (s->state == STATE_INACTIVE || s->state == STATE_ERROR)
- s->stop_timeout = 0;
-
- if (s->state == STATE_ERROR)
- continue;
-
- if (s->stop_timeout != 0) {
- if (time(NULL) - s->stop_timeout >= SV_STOP_TIMEOUT) {
- printf(":: service '%s' doesn't terminate, killing...\n", s->name);
- service_kill(s, SIGKILL);
- s->stop_timeout = 0;
- }
- } else if (s->state == STATE_INACTIVE && service_need_restart(s)) {
- service_start(s);
- }
- }
-}
-
-static void control_sockets(void) {
- struct service* s;
- char cmd;
-
- for (int i = 0; i < services_size; i++) {
- s = &services[i];
- while (read(s->control, &cmd, 1) == 1) {
- printf("handling '%c' from %s\n", cmd, s->name);
- service_handle_command(s, cmd);
- }
- }
-}
-
-void stop_dummies(void) {
- bool cont;
- for (int i = 0; i < services_size; i++) {
- if (services[i].state != STATE_ACTIVE_DUMMY || services[i].restart == S_RESTART)
- continue;
-
- cont = false;
- for (int i = 0; i < depends_size; i++) {
- if (depends[i][0] != &services[i])
- continue;
-
- if (depends[i][1]->state != STATE_INACTIVE || depends[i][1]->state != STATE_ERROR) {
- cont = true;
- }
- }
- if (!cont) {
- service_stop(&services[i]);
- }
- }
-}
-
-int service_supervise(const char* service_dir_, const char* service, bool once) {
- struct sigaction sigact = { 0 };
- struct service* s;
-
- daemon_running = true;
-
- sigact.sa_handler = signal_child;
- sigaction(SIGCHLD, &sigact, NULL);
- sigact.sa_handler = SIG_IGN;
- sigaction(SIGPIPE, &sigact, NULL);
-
- service_dir_path = service_dir_;
- if ((service_dir = open(service_dir_, O_DIRECTORY)) == -1) {
- print_errno("error: cannot open directory %s: %s\n", service_dir_);
- return 1;
- }
-
- if ((null_fd = open("/dev/null", O_RDWR)) == -1) {
- print_errno("error: cannot open /dev/null: %s\n");
- null_fd = 1;
- }
-
- printf(":: starting services\n");
-
- service_refresh_directory();
-
- if ((s = service_get(service)) == NULL) {
- fprintf(stderr, "error: cannot start '%s': not found\n", service);
- goto cleanup;
- }
-
- s->restart = once ? S_ONCE : S_RESTART;
- service_start(s);
-
-
- bool cont;
- // accept connections and handle requests
- do {
- if (!daemon_running) {
- for (int i = 0; i < services_size; i++) {
- s = &services[i];
- service_stop(s);
- }
- }
-
- service_refresh_directory();
- stop_dummies();
- control_sockets();
- update_services();
-
- sleep(SV_CHECK_INTERVAL);
-
- cont = false;
- for (int i = 0; i < services_size; i++) {
- if (services[i].state != STATE_INACTIVE && services[i].state != STATE_ERROR)
- cont = true;
- }
- } while (cont);
-
- printf(":: terminating\n");
-
- printf(":: all services stopped\n");
-
-cleanup:
-
- close(service_dir);
- close(null_fd);
-
- signal(SIGPIPE, SIG_DFL);
- signal(SIGCHLD, SIG_DFL);
- return 0;
-}
diff --git a/src/vlogger/Makefile b/src/vlogger/Makefile
@@ -0,0 +1,11 @@
+TOPDIR=../..
+-include $(TOPDIR)/config.mk
+
+OBJS = ../finit/message.o
+BINS = vlogger
+INTERM = vlogger.8.txt
+MANS = vlogger.8
+PAGES = vlogger.8.html
+HEADERS = ../finit/message.h
+
+include $(TOPDIR)/mk/prog.mk
+\ No newline at end of file
diff --git a/man/vlogger.8.txt b/src/vlogger/vlogger.8.txt
diff --git a/src/vlogger/vlogger.c b/src/vlogger/vlogger.c
@@ -0,0 +1,200 @@
+// +objects: message.o
+
+#include "../common/util.h"
+#include "../finit/message.h"
+#include "config.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>
+
+
+const char* current_prog(void) {
+ return "vlogger";
+}
+
+static char pwd[PATH_MAX];
+
+struct ident {
+ const char* name;
+ int value;
+};
+
+struct ident 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 }
+};
+
+struct ident 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;
+ struct ident* 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_errno("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) {
+ struct ident* 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_errno("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/zzz/Makefile b/src/zzz/Makefile
@@ -0,0 +1,11 @@
+TOPDIR=../..
+-include $(TOPDIR)/config.mk
+
+OBJS =
+BINS = zzz ZZZ
+INTERM = zzz.8.txt
+MANS = zzz.8
+PAGES = zzz.8.html
+HEADERS =
+
+include $(TOPDIR)/mk/prog.mk
+\ No newline at end of file
diff --git a/bin/ZZZ.lnk b/src/zzz/ZZZ.lnk
diff --git a/man/zzz.8.txt b/src/zzz/zzz.8.txt
diff --git a/src/zzz/zzz.c b/src/zzz/zzz.c
@@ -0,0 +1,136 @@
+#include "../common/util.h"
+#include "config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+
+const char* current_prog(void) {
+ return "zzz";
+}
+
+static int open_write(const char* path, const char* string) {
+ int fd;
+
+ if ((fd = open(path, O_WRONLY | O_TRUNC)) == -1) {
+ print_errno("cannot open %s: %s\n", path);
+ return -1;
+ }
+ if (write(fd, string, strlen(string)) == -1) {
+ print_errno("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 = NULL,
+ *new_disk = NULL;
+
+ if (streq(argv[0], "zzz")) {
+ new_state = "mem";
+ new_disk = NULL;
+ } else if (streq(argv[0], "ZZZ")) {
+ new_state = "disk";
+ new_disk = "platform";
+ } else {
+ fprintf(stderr, "error: program-name `%s` invalid\n", argv[0]);
+ return 1;
+ }
+
+ 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_errno("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_errno("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_errno("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_errno("failed to execute " SV_RESUME_EXEC ": %s\n");
+ _exit(1);
+ }
+
+ wait(NULL);
+ }
+}
diff --git a/tools/make-docs.py b/tools/make-docs.py
@@ -1,6 +1,10 @@
import sys
import re
+infile = sys.stdin
+if len(sys.argv) >= 2:
+ infile = open(sys.argv[1])
+
WIDTH = 80
HEADER_CHAR = '='
TITLE_CHAR = '-'
@@ -40,7 +44,7 @@ in_list = None
print(PREFIX)
-for line in sys.stdin:
+for line in infile:
line = line.strip()
# is control
diff --git a/tools/make-man.py b/tools/make-man.py
@@ -1,6 +1,10 @@
import sys
import re
+infile = sys.stdin
+if len(sys.argv) >= 2:
+ infile = open(sys.argv[1])
+
def inline_convert(text):
text = re.sub(r'\*(.+?)\*', r'\\fB\\fC\1\\fR', text)
text = re.sub(r'_(.+?)_', r'\\fI\1\\fR', text)
@@ -10,7 +14,7 @@ def inline_convert(text):
in_list = False
-for line in sys.stdin:
+for line in infile:
line = line.strip()
# is control