commit ac95deae520297b3aaafee07b14d42b6c9bf837c
Author: Friedel Schon <[email protected]>
Date: Mon, 10 Apr 2023 10:36:50 +0200
first commit
Diffstat:
126 files changed, 4799 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -0,0 +1,72 @@
+# Directories
+SRC_DIR := src
+BUILD_DIR := build
+INCLUDE_DIR := include
+BIN_DIR := bin
+EXEC_DIR := $(SRC_DIR)/exec
+SCRIPT_DIR := $(SRC_DIR)/script
+
+# Compiler Options
+CC := gcc
+CCFLAGS := -I$(INCLUDE_DIR) -Wall -Wextra -g
+LFLAGS :=
+
+# Executable-specific flags
+finit_FLAGS := -static
+
+# File lists
+SOURCE_FILES := $(wildcard $(SRC_DIR)/*.c)
+EXEC_FILES := $(wildcard $(EXEC_DIR)/*.c)
+OBJ_FILES := $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SOURCE_FILES))
+SCRIPT_FILES := $(wildcard $(SCRIPT_DIR)/*)
+BIN_FILES := $(patsubst $(EXEC_DIR)/%.c,$(BIN_DIR)/%,$(EXEC_FILES)) \
+ $(patsubst $(SCRIPT_DIR)/%.sh,$(BIN_DIR)/%,$(SCRIPT_FILES)) \
+ $(patsubst $(SCRIPT_DIR)/%.lnk,$(BIN_DIR)/%,$(SCRIPT_FILES))
+INCLUDE_FILES := $(wildcard $(INCLUDE_DIR)/*.h)
+
+# Magic targets
+.PHONY: all clean
+
+.PRECIOUS: $(OBJ_FILES)
+
+
+# Default target
+all: compile_flags.txt $(BIN_FILES)
+
+# Clean target
+clean:
+ rm -rf $(BIN_DIR) $(BUILD_DIR)
+
+# Directory rules
+$(BIN_DIR) $(BUILD_DIR):
+ mkdir -p $@
+
+# Object rules
+$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c $(INCLUDE_FILES) | $(BUILD_DIR)
+ $(CC) -o $@ -c $(CCFLAGS) $<
+
+# Executables
+$(BIN_DIR)/%: $(EXEC_DIR)/%.c $(INCLUDE_FILES) $(OBJ_FILES) | $(BIN_DIR)
+ $(CC) -o $@ $(CCFLAGS) $< $(OBJ_FILES) $($(notdir $@)_FLAGS) $(LFLAGS)
+
+$(BIN_DIR)/%: $(SCRIPT_DIR)/%.lnk | $(BIN_DIR)
+ ln -s $(shell cat $<) $@
+
+$(BIN_DIR)/%: $(SCRIPT_DIR)/%.sh | $(BIN_DIR)
+ cp $< $@
+ chmod +x $@
+
+# Debug
+compile_flags.txt:
+ echo $(CCFLAGS) | tr " " "\n" > compile_flags.txt
+
+# debug
+
+.PHONY: emulator
+
+emulator: $(BIN_FILES)
+ cp -v bin/* rootfs/sbin/
+ cd rootfs && find | cpio -oH newc -R root:root | zstd > ../build/rootfs.cpio
+
+ qemu-system-x86_64 -accel kvm -kernel ~/linux-void/vmlinuz-6.1.21_1 -initrd build/rootfs.cpio -m 4096 -append 'console=ttyS0 edd=off quiet' -serial stdio
+# qemu-system-x86_64 -accel kvm -kernel /boot/vmlinuz-6.1.21_1 -initrd build/rootfs.cpio -m 4096 -append 'console=ttyS0 edd=off quiet' -serial stdio
+\ No newline at end of file
diff --git a/docs/command.txt b/docs/command.txt
@@ -0,0 +1,15 @@
+start (u) [0] <service>: start if not running
+start (u) [1] <service>: start if not running and pin as up
+start (u) [2] <service>: only pin as restart
+stop (d) [0] <service>: stop if running
+stop (d) [1] <service>: stop if running and pin as down
+stop (d) [2] <service>: only pin as down
+send (k) [s] <service>: send signal $s to service
+pause (p) [*] <service>: pause service (send SIGSTOP)
+resume (c) [*] <service>: unpause service (send SIGCONT)
+revive (v) [*] <service>: revive died service
+update (g) [*] <service>: force update info // todo
+exit (x) [*]: stop all services and kill the fsvs instance
+refresh (y) [*]: refresh the service directory
+status (a) [*] <service>: get status of the service
+status (a) [*]: get status of all services
diff --git a/docs/control.txt b/docs/control.txt
@@ -0,0 +1,14 @@
+d -> down
+u -> up
+x -> exit
+t -> sig term
+k -> sig kill
+p -> sig pause
+c -> sig cont
+o -> once
+a -> sig alarm
+h -> sig hup
+i -> sig int
+q -> sig quit
+1 -> sig usr1
+2 -> sig usr2
diff --git a/docs/files.txt b/docs/files.txt
diff --git a/docs/readme.txt b/docs/readme.txt
@@ -0,0 +1,127 @@
+FRIEDEL'S INITIALIZATION and SERVICE SUPERVISION
+================================================================================
+
+--- Components -----------------------------------------------------------------
+
+/sbin/finit - initialisation
+- the init-executable, it handles the startup and initialisation.
+ 1. execute /etc/fiss/start
+ 2. execute /bin/fsvs at /etc/fiss/services until stopped
+ 3. execute /etc/fiss/stop
+ 4. halt
+
+/sbin/fsvs - service superviser
+
+/sbin/fsvc - service controller
+
+
+--- Configuration --------------------------------------------------------------
+
+in enum.h are some definitions (#define ...) to configure built-in variables:
+
+SV_SERVICE_DIR_ENV (default: "SERVICE_DIR")
+- environment variable where the current service-dir is stored:
+
+SV_RUNLEVEL_ENV (default: "SERVICE_RUNLEVEL")
+- environment variable where the current runlevel is stored
+
+SV_STOP_TIMEOUT (default: 5)
+- seconds to wait for a service before it gets killed
+
+SV_NAME_MAX (default: 1024)
+- maximal characters a service-dir can have
+
+SV_DEPENDS_MAX (default: 16)
+- maximal dependencies a service can have
+
+SV_FAIL_MAX (default: 127)
+- maximal amount a service may fail (max. 255 as it's stored in uint8)
+
+SV_SERVICE_MAX (default: 128)
+- maximal amount of services that can be registered
+
+SV_SUPERVISE_DIR (default: undefined)
+- path to supervision directory (undefined if supervision in service-dir)
+
+SV_DAEMONTOOLS (default: undefined)
+- define to enable daemontools' supervice-dir
+
+--- Installation ---------------------------------------------------------------
+
+$ make
+# make install
+
+
+--- Service Directory ----------------------------------------------------------
+
+every service needs to be a directory in
+ {/etc/fiss/service -> FISS_PATH}, with either or both: run and depends
+
+run - the service, if it dies it will be restarted
+
+depends - services which needs to be running before this
+ service will be started, one service per line
+
+finish - if run dies, this script will be called,
+ the return-code will be ignored, 'run will be restarted after
+ after 'finish exits
+
+zombie - service cannot die, also if failed more than FAIL_LIMIT
+ service' fail_count may overflow and reset to zero
+
+params - params to run
+
+autostart-{...} - start automatically when init is running
+autostart-{...}-once - start automatically when init is running but once
+
+unique - doesn't spawn if it's running already by an other session
+
+socket - path to socket-file where stdin and stdout/stderr will be redirected
+ (cannot exist with log/)
+
+log/ - logging service
+
+a service is considered started, if it's running {5sec -> FISS_FAIL_TIMEOUT}
+if it dies FISS_FAIL_MAX times with an exit code or signal it will be considered as dead
+
+/run/services/{runlevel}-{service}/:
+
+control - fifo to control service
+
+
+--- Supervice Directory --------------------------------------------------------
+
+control: a fifo to control the service (see control-commands)
+info: a file containing the current status (machine readable - fiss-format)
+lock: an empty file, if existing it's blocking spawning (useful if running fiss-sv multiple times)
+
+additional files if SV_DAEMONTOOLS is enabled:
+ok: an unused fifo to indicate if fiss is still running (as a fifo always needs two ends)
+pid: a file containing the pid of the running service (human readable)
+stat: a file containing the current status (human readable)
+status: a file containing the current status (machine readable - daemontools-format)
+
+
+--- Commands -------------------------------------------------------------------
+
+c -> sig cont (and unset pause-flag)
+d -> down (stop service if running and unset restart-flag)
+g -> force update info
+o -> once (start service if not running and unset restart-flag)
+p -> sig pause (and set pause-flag)
+r -> restart (stop service if running and start)
+s % -> send signal (requires extra byte $signal)
+u -> up (start service if not running and set restart-flag)
+v -> revive (start service if not running and set zombie-flag)
+w -> start without dependencies
+
+additional commands if SV_DAEMONTOOLS is enabled:
+a -> sig alarm
+h -> sig hup
+i -> sig int
+k -> sig kill
+q -> sig quit
+t -> sig term
+x -> exit (no operation)
+1 -> sig usr1
+2 -> sig usr2
+\ No newline at end of file
diff --git a/docs/service-dir.txt b/docs/service-dir.txt
@@ -0,0 +1,29 @@
+# SERVICE DIRECTORY
+
+- <service>/
+ - run -- runscript
+ - (enable) -- empty file (indicating it should start on boot or whenever fissd is started)
+ - fiss/
+ - status -- status file
+ - control -- service control (fifo)
+
+# STATUS file
+
++--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+| PID | STATUS CHANGE |FC|FL|
++--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+pid -> pid of service process (0 if not active)
+status change -> last time status changes (running/stopped/failed)
+fc (fail count) -> count of instant fails (stopping in less than 5sec)
+fl (flags) -> flags
+
+flags:
++--+--+--+--+--+--+--+--+
+|WU|SP|DE|PA| /// |
++--+--+--+--+--+--+--+--+
+
+wu (wants up) -> will be restarted
+sp (stopped) -> manually stopped (overrides $wu)
+de (dead) -> failed too much (overrides $sp)
+pa (paused) -> paused
+\ No newline at end of file
diff --git a/docs/service.txt b/docs/service.txt
@@ -0,0 +1,7 @@
+service_get()
+ on success: fill the service_struct
+ returning E_OK
+ on fail: init the service_struct with the path and zero's
+ returning E_ANUL if an argument is nulled
+ returning E_NRUN if runsvdir is not running (aka `supervise/ok` don't have an device)
+ returning E_NOENT if service not found
+\ No newline at end of file
diff --git a/docs/svstatus.txt b/docs/svstatus.txt
@@ -0,0 +1,13 @@
++--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+| TIME | ??? | PID |PA|WN|TM|ST|
++--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+
+TIME = (little endian) TAI time
+PID = (big endian)
+PA = (bool) paused
+WN = wants down: 'd'
+ wants up: 'u'
+TM = terminated
+ST = down: 0
+ up: 1
+ finish: 2
+\ No newline at end of file
diff --git a/docs/usage.txt b/docs/usage.txt
@@ -0,0 +1,25 @@
+Usage as init (pid 1):
+ fiss-sv
+Usage as regular:
+ fiss-sv <0|6>
+ fiss-sv [options] <runlevel>
+
+Options:
+ -a, --autostart .......... autostart every service (<runlevel> not required)
+ -h, --help ............... prints this and exits
+ -i, --as-init ............ execute start/stop script
+ -o, --stdout ............. print service stdout/stderr in console
+ -s, --service-dir <path> . using service-dir (default: )
+ -v, --verbose ............ print more info
+ -V, --version ............ prints current version and exits
+
+Usage:
+ fiss-ctl [options] <command> [service]
+ /etc/init.d/<service> <command>
+
+Options:
+ -h, --help ............... prints this and exits
+ -i, --as-init ............ execute start/stop script
+ -s, --service-dir <path> . using service-dir (default: )
+ -v, --verbose ............ print more info
+ -V, --version ............ prints current version and exits
diff --git a/etc/dracut/10-runit-void.conf b/etc/dracut/10-runit-void.conf
@@ -0,0 +1,3 @@
+add_dracutmodules+=" resume "
+omit_dracutmodules+=" systemd "
+i18n_vars="/etc/rc.conf:KEYMAP,FONT,FONT_MAP,FONT_UNIMAP"
diff --git a/etc/fiss/service/agetty-console/finish b/etc/fiss/service/agetty-console/finish
@@ -0,0 +1 @@
+../agetty-generic/finish
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-console/params b/etc/fiss/service/agetty-console/params
@@ -0,0 +1,5 @@
+-L
+-8
+console
+38400
+linux
diff --git a/etc/fiss/service/agetty-console/run b/etc/fiss/service/agetty-console/run
@@ -0,0 +1 @@
+../agetty-generic/run
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-generic/finish b/etc/fiss/service/agetty-generic/finish
@@ -0,0 +1,3 @@
+#!/bin/sh
+tty=${PWD##*-}
+exec utmpset -w $tty
diff --git a/etc/fiss/service/agetty-generic/run b/etc/fiss/service/agetty-generic/run
@@ -0,0 +1 @@
+/sbin/agetty
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-hvc0/finish b/etc/fiss/service/agetty-hvc0/finish
@@ -0,0 +1 @@
+../agetty-generic/finish
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-hvc0/params b/etc/fiss/service/agetty-hvc0/params
@@ -0,0 +1,4 @@
+-L
+hvc0
+9600
+vt100
diff --git a/etc/fiss/service/agetty-hvc0/run b/etc/fiss/service/agetty-hvc0/run
@@ -0,0 +1 @@
+../agetty-serial/run
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-hvsi0/finish b/etc/fiss/service/agetty-hvsi0/finish
@@ -0,0 +1 @@
+../agetty-generic/finish
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-hvsi0/params b/etc/fiss/service/agetty-hvsi0/params
@@ -0,0 +1,5 @@
+-L
+-8
+hvsi0
+19200
+vt100
diff --git a/etc/fiss/service/agetty-hvsi0/run b/etc/fiss/service/agetty-hvsi0/run
@@ -0,0 +1 @@
+../agetty-serial/run
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-serial/finish b/etc/fiss/service/agetty-serial/finish
@@ -0,0 +1 @@
+../agetty-generic/finish
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-serial/params b/etc/fiss/service/agetty-serial/params
@@ -0,0 +1,5 @@
+-L
+-8
+serial
+115200
+vt100
diff --git a/etc/fiss/service/agetty-serial/run b/etc/fiss/service/agetty-serial/run
@@ -0,0 +1 @@
+../agetty-generic/run
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-tty1/finish b/etc/fiss/service/agetty-tty1/finish
@@ -0,0 +1 @@
+../agetty-generic/finish
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-tty1/params b/etc/fiss/service/agetty-tty1/params
@@ -0,0 +1,5 @@
+-L
+-8
+tty1
+38400
+linux
diff --git a/etc/fiss/service/agetty-tty1/run b/etc/fiss/service/agetty-tty1/run
@@ -0,0 +1 @@
+../agetty-generic/run
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-tty1/up-default b/etc/fiss/service/agetty-tty1/up-default
diff --git a/etc/fiss/service/agetty-tty2/finish b/etc/fiss/service/agetty-tty2/finish
@@ -0,0 +1 @@
+../agetty-generic/finish
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-tty2/params b/etc/fiss/service/agetty-tty2/params
@@ -0,0 +1,4 @@
+--noclear
+tty2
+38400
+linux
diff --git a/etc/fiss/service/agetty-tty2/run b/etc/fiss/service/agetty-tty2/run
@@ -0,0 +1 @@
+../agetty-generic/run
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-tty2/up-default b/etc/fiss/service/agetty-tty2/up-default
diff --git a/etc/fiss/service/agetty-tty3/finish b/etc/fiss/service/agetty-tty3/finish
@@ -0,0 +1 @@
+../agetty-generic/finish
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-tty3/params b/etc/fiss/service/agetty-tty3/params
@@ -0,0 +1,4 @@
+--noclear
+tty3
+38400
+linux
diff --git a/etc/fiss/service/agetty-tty3/run b/etc/fiss/service/agetty-tty3/run
@@ -0,0 +1 @@
+../agetty-generic/run
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-tty3/up-default b/etc/fiss/service/agetty-tty3/up-default
diff --git a/etc/fiss/service/agetty-tty4/finish b/etc/fiss/service/agetty-tty4/finish
@@ -0,0 +1 @@
+../agetty-generic/finish
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-tty4/params b/etc/fiss/service/agetty-tty4/params
@@ -0,0 +1,4 @@
+--noclear
+tty4
+38400
+linux
diff --git a/etc/fiss/service/agetty-tty4/run b/etc/fiss/service/agetty-tty4/run
@@ -0,0 +1 @@
+../agetty-generic/run
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-tty4/up-default b/etc/fiss/service/agetty-tty4/up-default
diff --git a/etc/fiss/service/agetty-tty5/finish b/etc/fiss/service/agetty-tty5/finish
@@ -0,0 +1 @@
+../agetty-generic/finish
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-tty5/params b/etc/fiss/service/agetty-tty5/params
@@ -0,0 +1,4 @@
+--noclear
+tty5
+38400
+linux
diff --git a/etc/fiss/service/agetty-tty5/run b/etc/fiss/service/agetty-tty5/run
@@ -0,0 +1 @@
+../agetty-generic/run
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-tty5/up-default b/etc/fiss/service/agetty-tty5/up-default
diff --git a/etc/fiss/service/agetty-tty6/finish b/etc/fiss/service/agetty-tty6/finish
@@ -0,0 +1 @@
+../agetty-generic/finish
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-tty6/params b/etc/fiss/service/agetty-tty6/params
@@ -0,0 +1,4 @@
+--noclear
+tty6
+38400
+linux
diff --git a/etc/fiss/service/agetty-tty6/run b/etc/fiss/service/agetty-tty6/run
@@ -0,0 +1 @@
+../agetty-generic/run
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-tty6/up-default b/etc/fiss/service/agetty-tty6/up-default
diff --git a/etc/fiss/service/agetty-ttyAMA0/finish b/etc/fiss/service/agetty-ttyAMA0/finish
@@ -0,0 +1 @@
+../agetty-generic/finish
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-ttyAMA0/params b/etc/fiss/service/agetty-ttyAMA0/params
@@ -0,0 +1,5 @@
+-L
+-8
+ttyAMA0
+115200
+vt100
diff --git a/etc/fiss/service/agetty-ttyAMA0/run b/etc/fiss/service/agetty-ttyAMA0/run
@@ -0,0 +1 @@
+../agetty-serial/run
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-ttyS0/finish b/etc/fiss/service/agetty-ttyS0/finish
@@ -0,0 +1 @@
+../agetty-generic/finish
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-ttyS0/params b/etc/fiss/service/agetty-ttyS0/params
@@ -0,0 +1,5 @@
+-L
+-8
+ttyS0
+115200
+vt100
diff --git a/etc/fiss/service/agetty-ttyS0/run b/etc/fiss/service/agetty-ttyS0/run
@@ -0,0 +1 @@
+../agetty-serial/run
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-ttyUSB0/finish b/etc/fiss/service/agetty-ttyUSB0/finish
@@ -0,0 +1 @@
+../agetty-generic/finish
+\ No newline at end of file
diff --git a/etc/fiss/service/agetty-ttyUSB0/params b/etc/fiss/service/agetty-ttyUSB0/params
@@ -0,0 +1,5 @@
+-L
+-8
+ttyUSB0
+115200
+vt100
diff --git a/etc/fiss/service/agetty-ttyUSB0/run b/etc/fiss/service/agetty-ttyUSB0/run
@@ -0,0 +1 @@
+../agetty-serial/run
+\ No newline at end of file
diff --git a/etc/fiss/service/sulogin/run b/etc/fiss/service/sulogin/run
@@ -0,0 +1,5 @@
+#!/bin/sh
+[ -r conf ] && . ./conf
+read -r tty < /sys/class/tty/console/active
+tty=/dev/${tty##* }
+exec setsid sulogin ${OPTS:=-p} < $tty >$tty 2>&1
diff --git a/etc/fiss/start b/etc/fiss/start
@@ -0,0 +1,30 @@
+#!/bin/sh
+# vim: set ts=4 sw=4 et:
+
+PATH=/usr/bin:/usr/sbin
+
+. /usr/share/fiss/utils
+
+msg "Welcome to Void (fiss version)!"
+
+[ -r /etc/rc.conf ] && . /etc/rc.conf
+
+# Start core services: one-time system tasks.
+detect_virt
+for f in /etc/fiss/start.d/*.sh; do
+ [ -r $f ] && . $f
+done
+
+dmesg >/var/log/dmesg.log
+if [ $(sysctl -n kernel.dmesg_restrict 2>/dev/null) -eq 1 ]; then
+ chmod 0600 /var/log/dmesg.log
+else
+ chmod 0644 /var/log/dmesg.log
+fi
+
+# create files for controlling runit
+mkdir -p /run/fiss/log
+
+msg "Initialization complete, running stage 2..."
+
+[ -x /etc/rc.local ] && /etc/rc.local
+\ No newline at end of file
diff --git a/etc/fiss/start.d/00-pseudofs.sh b/etc/fiss/start.d/00-pseudofs.sh
@@ -0,0 +1,50 @@
+# vim: set ts=4 sw=4 et:
+
+msg "Mounting pseudo-filesystems..."
+mountpoint -q /proc || mount -o nosuid,noexec,nodev -t proc proc /proc
+mountpoint -q /sys || mount -o nosuid,noexec,nodev -t sysfs sys /sys
+mountpoint -q /run || mount -o mode=0755,nosuid,nodev -t tmpfs run /run
+mountpoint -q /dev || mount -o mode=0755,nosuid -t devtmpfs dev /dev
+mkdir -p -m0755 /run/fiss /run/lvm /run/user /run/lock /run/log /dev/pts /dev/shm
+mountpoint -q /dev/pts || mount -o mode=0620,gid=5,nosuid,noexec -n -t devpts devpts /dev/pts
+mountpoint -q /dev/shm || mount -o mode=1777,nosuid,nodev -n -t tmpfs shm /dev/shm
+mountpoint -q /sys/kernel/security || mount -n -t securityfs securityfs /sys/kernel/security
+
+if [ -d /sys/firmware/efi/efivars ]; then
+ mountpoint -q /sys/firmware/efi/efivars || mount -o nosuid,noexec,nodev -t efivarfs efivarfs /sys/firmware/efi/efivars
+fi
+
+if [ -z "$VIRTUALIZATION" ]; then
+ _cgroupv1=""
+ _cgroupv2=""
+
+ case "${CGROUP_MODE:-hybrid}" in
+ legacy)
+ _cgroupv1="/sys/fs/cgroup"
+ ;;
+ hybrid)
+ _cgroupv1="/sys/fs/cgroup"
+ _cgroupv2="${_cgroupv1}/unified"
+ ;;
+ unified)
+ _cgroupv2="/sys/fs/cgroup"
+ ;;
+ esac
+
+ # cgroup v1
+ if [ -n "$_cgroupv1" ]; then
+ mountpoint -q "$_cgroupv1" || mount -o mode=0755 -t tmpfs cgroup "$_cgroupv1"
+ while read -r _subsys_name _hierarchy _num_cgroups _enabled; do
+ [ "$_enabled" = "1" ] || continue
+ _controller="${_cgroupv1}/${_subsys_name}"
+ mkdir -p "$_controller"
+ mountpoint -q "$_controller" || mount -t cgroup -o "$_subsys_name" cgroup "$_controller"
+ done < /proc/cgroups
+ fi
+
+ # cgroup v2
+ if [ -n "$_cgroupv2" ]; then
+ mkdir -p "$_cgroupv2"
+ mountpoint -q "$_cgroupv2" || mount -t cgroup2 -o nsdelegate cgroup2 "$_cgroupv2"
+ fi
+fi
diff --git a/etc/fiss/start.d/01-static-devnodes.sh b/etc/fiss/start.d/01-static-devnodes.sh
@@ -0,0 +1,6 @@
+# Some kernel modules must be loaded before starting udev(7).
+# Load them by looking at the output of `kmod static-nodes`.
+
+for f in $(kmod static-nodes 2>/dev/null|awk '/Module/ {print $2}'); do
+ modprobe -bq $f 2>/dev/null
+done
diff --git a/etc/fiss/start.d/02-kmods.sh b/etc/fiss/start.d/02-kmods.sh
@@ -0,0 +1,9 @@
+# vim: set ts=4 sw=4 et:
+
+[ -n "$VIRTUALIZATION" ] && return 0
+# Do not try to load modules if kernel does not support them.
+[ ! -e /proc/modules ] && return 0
+
+msg "Loading kernel modules..."
+modules-load -v | tr '\n' ' ' | sed 's:insmod [^ ]*/::g; s:\.ko\(\.gz\)\? ::g'
+echo
diff --git a/etc/fiss/start.d/02-udev.sh b/etc/fiss/start.d/02-udev.sh
@@ -0,0 +1,19 @@
+# vim: set ts=4 sw=4 et:
+
+[ -n "$VIRTUALIZATION" ] && return 0
+
+if [ -x /usr/lib/systemd/systemd-udevd ]; then
+ _udevd=/usr/lib/systemd/systemd-udevd
+elif [ -x /sbin/udevd -o -x /bin/udevd ]; then
+ _udevd=udevd
+else
+ msg_warn "cannot find udevd!"
+fi
+
+if [ -n "${_udevd}" ]; then
+ msg "Starting udev and waiting for devices to settle..."
+ ${_udevd} --daemon
+ udevadm trigger --action=add --type=subsystems
+ udevadm trigger --action=add --type=devices
+ udevadm settle
+fi
diff --git a/etc/fiss/start.d/03-console-setup.sh b/etc/fiss/start.d/03-console-setup.sh
@@ -0,0 +1,26 @@
+# vim: set ts=4 sw=4 et:
+
+[ -n "$VIRTUALIZATION" ] && return 0
+
+TTYS=${TTYS:-12}
+if [ -n "$FONT" ]; then
+ msg "Setting up TTYs font to '${FONT}'..."
+
+ _index=0
+ while [ ${_index} -le $TTYS ]; do
+ setfont ${FONT_MAP:+-m $FONT_MAP} ${FONT_UNIMAP:+-u $FONT_UNIMAP} \
+ $FONT -C "/dev/tty${_index}"
+ _index=$((_index + 1))
+ done
+fi
+
+if [ -n "$KEYMAP" ]; then
+ msg "Setting up keymap to '${KEYMAP}'..."
+ loadkeys -q -u ${KEYMAP}
+fi
+
+if [ -n "$HARDWARECLOCK" ]; then
+ msg "Setting up RTC to '${HARDWARECLOCK}'..."
+ TZ=$TIMEZONE hwclock --systz \
+ ${HARDWARECLOCK:+--$(echo $HARDWARECLOCK |tr A-Z a-z) --noadjfile} || emergency_shell
+fi
diff --git a/etc/fiss/start.d/03-filesystems.sh b/etc/fiss/start.d/03-filesystems.sh
@@ -0,0 +1,80 @@
+# vim: set ts=4 sw=4 et:
+
+[ -n "$VIRTUALIZATION" ] && return 0
+
+msg "Remounting rootfs read-only..."
+mount -o remount,ro / || emergency_shell
+
+if [ -x /sbin/dmraid -o -x /bin/dmraid ]; then
+ msg "Activating dmraid devices..."
+ dmraid -i -ay
+fi
+
+if [ -x /bin/mdadm ]; then
+ msg "Activating software RAID arrays..."
+ mdadm -As
+fi
+
+if [ -x /bin/btrfs ]; then
+ msg "Activating btrfs devices..."
+ btrfs device scan || emergency_shell
+fi
+
+if [ -x /sbin/vgchange -o -x /bin/vgchange ]; then
+ msg "Activating LVM devices..."
+ vgchange --sysinit -a ay || emergency_shell
+fi
+
+if [ -e /etc/crypttab ]; then
+ msg "Activating encrypted devices..."
+ awk -f /usr/share/fiss/crypt.awk /etc/crypttab
+
+ if [ -x /sbin/vgchange -o -x /bin/vgchange ]; then
+ msg "Activating LVM devices for dm-crypt..."
+ vgchange --sysinit -a ay || emergency_shell
+ fi
+fi
+
+if [ -x /usr/bin/zpool -a -x /usr/bin/zfs ]; then
+ if [ -e /etc/zfs/zpool.cache ]; then
+ msg "Importing cached ZFS pools..."
+ zpool import -N -a -c /etc/zfs/zpool.cache
+ else
+ msg "Scanning for and importing ZFS pools..."
+ zpool import -N -a -o cachefile=none
+ fi
+
+ msg "Mounting ZFS file systems..."
+ zfs mount -a -l
+
+ msg "Sharing ZFS file systems..."
+ zfs share -a
+
+ # NOTE(dh): ZFS has ZVOLs, block devices on top of storage pools.
+ # In theory, it would be possible to use these as devices in
+ # dmraid, btrfs, LVM and so on. In practice it's unlikely that
+ # anybody is doing that, so we aren't supporting it for now.
+fi
+
+[ -f /fastboot ] && FASTBOOT=1
+[ -f /forcefsck ] && FORCEFSCK="-f"
+for arg in $(cat /proc/cmdline); do
+ case $arg in
+ fastboot) FASTBOOT=1;;
+ forcefsck) FORCEFSCK="-f";;
+ esac
+done
+
+if [ -z "$FASTBOOT" ]; then
+ msg "Checking filesystems:"
+ fsck -A -T -a -t noopts=_netdev $FORCEFSCK
+ if [ $? -gt 1 ]; then
+ emergency_shell
+ fi
+fi
+
+msg "Mounting rootfs read-write..."
+mount -o remount,rw / || emergency_shell
+
+msg "Mounting all non-network filesystems..."
+mount -a -t "nosysfs,nonfs,nonfs4,nosmbfs,nocifs" -O no_netdev || emergency_shell
diff --git a/etc/fiss/start.d/04-swap.sh b/etc/fiss/start.d/04-swap.sh
@@ -0,0 +1,6 @@
+# vim: set ts=4 sw=4 et:
+
+[ -n "$VIRTUALIZATION" ] && return 0
+
+msg "Initializing swap..."
+swapon -a || emergency_shell
diff --git a/etc/fiss/start.d/05-misc.sh b/etc/fiss/start.d/05-misc.sh
@@ -0,0 +1,25 @@
+# vim: set ts=4 sw=4 et:
+
+install -m0664 -o root -g utmp /dev/null /run/utmp
+halt -B # for wtmp
+
+if [ -z "$VIRTUALIZATION" ]; then
+ msg "Seeding random number generator..."
+ seedrng || true
+fi
+
+msg "Setting up loopback interface..."
+ip link set up dev lo
+
+[ -r /etc/hostname ] && read -r HOSTNAME < /etc/hostname
+if [ -n "$HOSTNAME" ]; then
+ msg "Setting up hostname to '${HOSTNAME}'..."
+ printf "%s" "$HOSTNAME" > /proc/sys/kernel/hostname
+else
+ msg_warn "Didn't setup a hostname!"
+fi
+
+if [ -n "$TIMEZONE" ]; then
+ msg "Setting up timezone to '${TIMEZONE}'..."
+ ln -sf "/usr/share/zoneinfo/$TIMEZONE" /etc/localtime
+fi
diff --git a/etc/fiss/start.d/08-sysctl.sh b/etc/fiss/start.d/08-sysctl.sh
@@ -0,0 +1,20 @@
+# vim: set ts=4 sw=4 et:
+
+if [ -x /sbin/sysctl -o -x /bin/sysctl ]; then
+ msg "Loading sysctl(8) settings..."
+ mkdir -p /run/vsysctl.d
+ for i in /run/sysctl.d/*.conf \
+ /etc/sysctl.d/*.conf \
+ /usr/local/lib/sysctl.d/*.conf \
+ /usr/lib/sysctl.d/*.conf; do
+
+ if [ -e "$i" ] && [ ! -e "/run/vsysctl.d/${i##*/}" ]; then
+ ln -s "$i" "/run/vsysctl.d/${i##*/}"
+ fi
+ done
+ for i in /run/vsysctl.d/*.conf; do
+ sysctl -p "$i"
+ done
+ rm -rf -- /run/vsysctl.d
+ sysctl -p /etc/sysctl.conf
+fi
diff --git a/etc/fiss/start.d/98-sbin-merge.sh b/etc/fiss/start.d/98-sbin-merge.sh
@@ -0,0 +1,11 @@
+if [ -d /usr/sbin -a ! -L /usr/sbin ]; then
+ for f in /usr/sbin/*; do
+ if [ -f $f -a ! -L $f ]; then
+ msg "Detected $f file, can't create /usr/sbin symlink."
+ return 0
+ fi
+ done
+ msg "Creating /usr/sbin -> /usr/bin symlink, moving existing to /usr/sbin.old"
+ mv /usr/sbin /usr/sbin.old
+ ln -sf bin /usr/sbin
+fi
diff --git a/etc/fiss/start.d/99-cleanup.sh b/etc/fiss/start.d/99-cleanup.sh
@@ -0,0 +1,10 @@
+# vim: set ts=4 sw=4 et:
+
+if [ ! -e /var/log/wtmp ]; then
+ install -m0664 -o root -g utmp /dev/null /var/log/wtmp
+fi
+if [ ! -e /var/log/btmp ]; then
+ install -m0600 -o root -g utmp /dev/null /var/log/btmp
+fi
+install -dm1777 /tmp/.X11-unix /tmp/.ICE-unix
+rm -f /etc/nologin /forcefsck /forcequotacheck /fastboot
diff --git a/etc/fiss/stop b/etc/fiss/stop
@@ -0,0 +1,16 @@
+#!/bin/sh
+# vim: set ts=4 sw=4 et:
+
+PATH=/usr/bin:/usr/sbin
+
+. /usr/share/fiss/utils
+
+detect_virt
+
+[ -r /etc/rc.conf ] && . /etc/rc.conf
+
+[ -r /etc/rc.shutdown ] && . /etc/rc.shutdown
+
+for f in /etc/fiss/stop.d/*.sh; do
+ [ -r $f ] && . $f
+done
diff --git a/etc/fiss/stop.d/20-rc-shutdown.sh b/etc/fiss/stop.d/20-rc-shutdown.sh
@@ -0,0 +1 @@
+[ -x /etc/rc.shutdown ] && /etc/rc.shutdown
diff --git a/etc/fiss/stop.d/30-seedrng.sh b/etc/fiss/stop.d/30-seedrng.sh
@@ -0,0 +1,4 @@
+if [ -z "$VIRTUALIZATION" ]; then
+ msg "Saving random number generator seed..."
+ seedrng
+fi
diff --git a/etc/fiss/stop.d/40-hwclock.sh b/etc/fiss/stop.d/40-hwclock.sh
@@ -0,0 +1,3 @@
+if [ -z "$VIRTUALIZATION" -a -n "$HARDWARECLOCK" ]; then
+ hwclock --systohc ${HARDWARECLOCK:+--$(echo $HARDWARECLOCK |tr A-Z a-z)}
+fi
diff --git a/etc/fiss/stop.d/50-wtmp.sh b/etc/fiss/stop.d/50-wtmp.sh
@@ -0,0 +1 @@
+halt -w
diff --git a/etc/fiss/stop.d/60-udev.sh b/etc/fiss/stop.d/60-udev.sh
@@ -0,0 +1,4 @@
+if [ -z "$VIRTUALIZATION" ]; then
+ msg "Stopping udev..."
+ udevadm control --exit
+fi
diff --git a/etc/fiss/stop.d/70-pkill.sh b/etc/fiss/stop.d/70-pkill.sh
@@ -0,0 +1,5 @@
+msg "Sending TERM signal to processes..."
+pkill --inverse -s0,1 -TERM
+sleep 1
+msg "Sending KILL signal to processes..."
+pkill --inverse -s0,1 -KILL
diff --git a/etc/fiss/stop.d/80-filesystems.sh b/etc/fiss/stop.d/80-filesystems.sh
@@ -0,0 +1,14 @@
+if [ -z "$VIRTUALIZATION" ]; then
+ msg "Unmounting filesystems, disabling swap..."
+ swapoff -a
+ umount -r -a -t nosysfs,noproc,nodevtmpfs,notmpfs
+ msg "Remounting rootfs read-only..."
+ mount -o remount,ro /
+fi
+
+sync
+
+if [ -z "$VIRTUALIZATION" ]; then
+ deactivate_vgs
+ deactivate_crypt
+fi
diff --git a/etc/fiss/stop.d/90-kexec.sh b/etc/fiss/stop.d/90-kexec.sh
@@ -0,0 +1,7 @@
+if [ -z "$VIRTUALIZATION" ]; then
+ if [ -e /run/fiss/reboot ] && command -v kexec >/dev/null; then
+ msg "Triggering kexec..."
+ kexec -e 2>/dev/null
+ # not reached when kexec was successful.
+ fi
+fi
diff --git a/etc/rc.conf b/etc/rc.conf
@@ -0,0 +1,47 @@
+# /etc/rc.conf - system configuration for void
+
+# Set the host name.
+#
+# NOTE: it's preferred to declare the hostname in /etc/hostname instead:
+# - echo myhost > /etc/hostname
+#
+#HOSTNAME="void-live"
+
+# Set RTC to UTC or localtime.
+#HARDWARECLOCK="UTC"
+
+# Set timezone, availables timezones can be found at /usr/share/zoneinfo.
+#
+# NOTE: it's preferred to set the timezone in /etc/localtime instead:
+# - ln -sf /usr/share/zoneinfo/<timezone> /etc/localtime
+# Setting the timezone here requires a reboot to apply any changes/fixes
+# and read-write access to the filesystem.
+#
+#TIMEZONE="Europe/Madrid"
+
+# Keymap to load, see loadkeys(8).
+#KEYMAP="es"
+
+# Console font to load, see setfont(8).
+#FONT="lat9w-16"
+
+# Console map to load, see setfont(8).
+#FONT_MAP=
+
+# Font unimap to load, see setfont(8).
+#FONT_UNIMAP=
+
+# Amount of ttys which should be setup.
+#TTYS=
+
+# Set the mode for cgroup mounts.
+# hybrid: mount cgroup v1 under /sys/fs/cgroup and
+# cgroup v2 under /sys/fs/cgroup/unified
+# legacy: mount cgroup v1 /sys/fs/cgroup
+# unified: mount cgroup v2 under /sys/fs/cgroup
+#CGROUP_MODE=hybrid
+
+# Set this to true only if you do not want seed files to actually credit the
+# RNG, for example if you plan to replicate this file system image and do not
+# have the wherewithal to first delete the contents of /var/lib/seedrng.
+#SEEDRNG_SKIP_CREDIT=false
diff --git a/etc/rc.local b/etc/rc.local
@@ -0,0 +1,5 @@
+#!/bin/sh
+# Default rc.local for void; add your custom commands here.
+#
+# This is run by runit in stage 2 before the services are executed
+# (see /etc/runit/2).
diff --git a/etc/rc.shutdown b/etc/rc.shutdown
@@ -0,0 +1,5 @@
+#!/bin/sh
+# Default rc.shutdown for void; add your custom commands here.
+#
+# This is run by runit in stage 3 after the services are stopped
+# (see /etc/runit/3).
diff --git a/fiss.svg b/fiss.svg
@@ -0,0 +1,28 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="1000px" height="725px" version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <title>Fiss</title>
+ <desc>Friedel's initialisation and service supervision</desc>
+ <defs>
+ <style type="text/css">
+ @font-face {
+ font-family: "Source Code Pro";
+ src:
+ url("data:application/font-woff;charset=utf-8;base64,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")
+ format("woff2");
+ }
+ text {
+ font-family: 'Source Code Pro';
+ font-weight: 800;
+ font-size: 180pt;
+ fill: #ddd;
+ transform: rotate(-26.5deg) skew(36.8deg);
+ }
+ </style>
+ </defs>
+ <path d="M0,475 l500,250 500,-250 -500,-250 z" fill="#ce0056" />
+ <path d="M0,400 l500,250 500,-250 -500,-250 z" fill="#1793d1" />
+ <path d="M0,325 l500,250 500,-250 -500,-250 z" fill="#478061" />
+ <path d="M0,250 l500,250 500,-250 -500,-250 z" fill="#333333" />
+ <text x="-170px" y="520px">fiss</text>
+</svg>
+\ No newline at end of file
diff --git a/include/config.h b/include/config.h
@@ -0,0 +1,43 @@
+#pragma once
+
+// environment variable where the current runlevel is stored
+#define SV_RUNLEVEL_ENV "SERVICE_RUNLEVEL"
+// 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 512
+// maximal dependencies a service can have
+#define SV_DEPENDS_MAX 16
+// maximal amount a service may fail
+#define SV_FAIL_MAX 32
+// maximal amount of services that can be registered
+#define SV_SERVICE_MAX 128
+// default runlevel
+#define SV_RUNLEVEL "default"
+// path to service-dir
+#define SV_SERVICE_DIR "/etc/fiss/service"
+// path to start-script
+#define SV_START_EXEC "/etc/fiss/start"
+// path to stop-script
+#define SV_STOP_EXEC "/etc/fiss/stop"
+// the current version
+#define SV_VERSION "0.1.0"
+// time to wait to accept new connection
+#define SV_ACCEPT_INTERVAL 1 // seconds
+// control socket (%s is the runlevel)
+#define SV_CONTROL_SOCKET "/run/fiss/control-%s.socket"
+// maximal size of <service>/params
+#define SV_PARAM_FILE_MAX 16384 // 16kb
+
+#define SV_PARAM_FILE_LINE_MAX 1024 // 16kb
+
+#define SV_ENV_FILE_LINE_MAX 1024 // 16kb
+// shell to enter if fiss failed
+#define SV_RESCUE_SHELL "/bin/bash"
+// max dependencies in the dep-tree
+#define SV_DEPENDENCY_MAX 512
+// max arguments a service can have
+#define SV_ARGUMENTS_MAX 16
+#define SV_ENV_MAX 16
+
+#define SV_LOG_DIR "/run/fiss/log"
+\ No newline at end of file
diff --git a/include/config_parser.h b/include/config_parser.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "service.h"
+
+
+void parse_env_file(char** env);
+void parse_param_file(service_t* s, char* args[]);
+pid_t parse_pid_file(service_t* s);
+\ No newline at end of file
diff --git a/include/pattern.h b/include/pattern.h
@@ -0,0 +1,3 @@
+#pragma once
+
+int pattern_test(const char* pattern, const char* match);
+\ No newline at end of file
diff --git a/include/service.h b/include/service.h
@@ -0,0 +1,103 @@
+#pragma once
+
+#include "config.h"
+#include "util.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <time.h>
+#include <unistd.h>
+
+#define SV_SERIAL_LEN 19
+#define SV_HAS_LOGSERVICE ((void*) 1)
+
+#define EBADCMD 1 // command not found
+#define ENOSV 2 // service required
+#define EBADSV 3 // no matching services
+#define EBEXT 4 // invalid extra
+
+typedef enum {
+ S_START = 'u', // start if not running and restart if failed
+ S_STOP = 'd', // stop if running and not restart if failed
+ S_SEND = 'k', // + signal | send signal to service
+ S_PAUSE = 'p', // pause service (send SIGSTOP)
+ S_RESUME = 'c', // unpause service (send SIGCONT)
+ S_REVIVE = 'v', // revive died service
+ S_UPDATE = 'g', // force update info // todo
+ S_EXIT = 'x', // kill the fsvs instance
+ S_REFRESH = 'y', // if service is given refresh command dir otherwise look for new services ??
+ S_STATUS = 'a', // get status of all services
+ S_CHLEVEL = 'l', // change runlevel
+} sv_command_t;
+
+typedef enum service_state {
+ STATE_INACTIVE,
+ STATE_STARTING,
+ STATE_ACTIVE_DUMMY,
+ STATE_ACTIVE_FOREGROUND,
+ STATE_ACTIVE_BACKGROUND,
+ STATE_ACTIVE_PID,
+ STATE_FINISHING,
+ STATE_STOPPING,
+ STATE_DEAD,
+} service_state_t;
+
+typedef enum service_exit {
+ EXIT_NONE,
+ EXIT_NORMAL,
+ EXIT_SIGNALED,
+} service_exit_t;
+
+typedef struct service {
+ char name[SV_NAME_MAX]; // name of service
+ service_state_t state;
+ pid_t pid; // pid of run
+ time_t status_change; // last status change
+ pipe_t log_pipe; // pipe for logging
+ bool restart; // should restart on exit
+ bool restart_once; // if once-{} is present
+ service_exit_t 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;
+ struct service* log_service;
+} service_t;
+
+typedef struct dependency {
+ service_t* service;
+ service_t* depends;
+} dependency_t;
+
+
+extern string command_error[];
+extern string command_string[];
+
+extern service_t services[];
+extern int services_size;
+extern string runlevel;
+extern int null_fd;
+extern int control_socket;
+extern bool daemon_running;
+extern bool verbose;
+extern string service_dir;
+extern dependency_t depends[];
+extern int depends_size;
+
+
+char service_get_command(string command);
+int service_command(char command, char extra, string service, service_t* response, int response_max);
+int service_handle_command(service_t* s, sv_command_t command, uint8_t extra, service_t** response);
+int service_pattern(string name, service_t** dest, int dest_max);
+int service_refresh(service_t** added);
+int service_supervise(string service_dir, string runlevel, bool force_socket);
+service_t* service_get(string name);
+service_t* service_register(string name, bool log_service, bool* changed);
+void service_check_state(service_t* s, bool signaled, int return_code);
+void service_handle_socket(int client);
+void service_load(service_t* s, const uint8_t* buffer); // for fsvc
+void service_send(service_t* s, int signal);
+void service_start(service_t* s, bool* changed);
+void service_stop(service_t* s, bool* changed);
+void service_store(service_t* s, uint8_t* buffer); // for fsvs
+void service_update_dependency(service_t* s);
+\ No newline at end of file
diff --git a/include/user_group.h b/include/user_group.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <unistd.h>
+
+
+int parse_ugid(char* str, uid_t* uid, gid_t* gids);
+\ No newline at end of file
diff --git a/include/util.h b/include/util.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <time.h>
+#include <unistd.h>
+
+#define streq(a, b) (!strcmp((a), (b)))
+#define stringify(s) #s
+#define static_stringify(s) stringify(s)
+
+#define print_error(msg, ...) (fprintf(stderr, "error: " msg ": %s\n", ##__VA_ARGS__, strerror(errno)))
+#define print_warning(msg, ...) (fprintf(stderr, "warning: " msg ": %s\n", ##__VA_ARGS__, strerror(errno)))
+
+typedef const char* string;
+
+typedef struct {
+ int read;
+ int write;
+} pipe_t;
+
+ssize_t dgetline(int fd, char* line, size_t line_buffer);
+ssize_t readstr(int fd, char* str);
+ssize_t writestr(int fd, string str);
+\ No newline at end of file
diff --git a/man/halt.8 b/man/halt.8
@@ -0,0 +1,72 @@
+.Dd September 5, 2019
+.Dt HALT 8
+.Os Linux
+.Sh NAME
+.Nm halt ,
+.Nm reboot ,
+.Nm poweroff
+.Nd stop the system
+.Sh SYNOPSIS
+.Nm halt
+.Op Fl n
+.Op Fl f
+.Op Fl d
+.Op Fl w
+.Op Fl B
+.Nm reboot
+.Op Fl n
+.Op Fl f
+.Nm poweroff
+.Op Fl n
+.Op Fl f
+.Sh DESCRIPTION
+.Nm halt
+/
+.Nm reboot
+/
+.Nm poweroff
+tells
+.Xr init 8
+to bring down, reboot, or power off the system.
+Without
+.Fl f ,
+it is a shortcut for
+.Nm init 0
+/
+.Nm init 6 .
+.Bl -tag -width indent
+.It Fl n
+Don't sync before reboot or halt.
+Note that the kernel and storage drivers may still sync.
+.It Fl f
+Force halt or reboot, don't call
+.Xr init 8 .
+This is
+.Sy dangerous !
+.It Fl d
+Do not write the wtmp record.
+.It Fl w
+Just write the wtmp record.
+.It Fl B
+Just write the wtmp record, but for a boot.
+.El
+.Sh UNSUPPORTED OPTIONS
+This version of
+.Nm
+is based on
+.Xr runit 8 ,
+the following features are
+.Sy not
+supported and silently ignored:
+.Bl -tag -width indent
+.It Fl h
+to put hard drives in standby mode.
+.It Fl i
+to shut down network interfaces.
+.El
+.Sh SEE ALSO
+.Xr init 8 ,
+.Xr shutdown 8
+.Sh AUTHOR
+.An Leah Neukirchen ,
+.Mt [email protected] .
diff --git a/man/modules-load.8 b/man/modules-load.8
@@ -0,0 +1,52 @@
+.Dd June 1, 2016
+.Dt MODULES-LOAD 8
+.Os Linux
+.Sh NAME
+.Nm modules-load
+.Nd Configure kernel modules to load at boot
+.Sh SYNOPSIS
+.Nm modules-load
+.Op Fl nv
+.Sh DESCRIPTION
+.Nm
+reads files which contain kernel modules to load during boot from the list of
+locations below.
+.Bl -tag -width indent
+.It Fl n
+dry-run mode.
+This option does everything but actually insert or delete the modules.
+.It Fl v
+verbose mode.
+Print messages about what the program is doing.
+.El
+.Sh FILES
+Configuration files are read from the following locations:
+.Bl -tag -width indent
+.It /etc/modules-load.d/*.conf
+.It /run/modules-load.d/*.conf
+.It /usr/lib/modules-load.d/*.conf
+.El
+.Pp
+The configuration files should simply contain a list of kernel module names
+to load, separated by newlines.
+Empty lines and lines whose first non-whitespace character is # or ; are
+ignored.
+.Sh EXAMPLES
+.Pa /etc/modules-load.d/virtio-net.conf :
+.Bd -literal -offset indent
+# Load virtio-net.ko at boot
+virtio-net
+.Ed
+.Sh SEE ALSO
+.Xr modprobe 8
+.Sh HISTORY
+This program is a replacement for the
+.Nm modules-load
+utility provided by
+.Nm systemd .
+.Sh AUTHOR
+.An Leah Neukirchen ,
+.Mt [email protected] .
+.Sh LICENSE
+.Nm
+is in the public domain.
diff --git a/man/pause.1 b/man/pause.1
@@ -0,0 +1,39 @@
+.Dd September 27, 2012
+.Dt PAUSE 1
+.Os Linux
+.Sh NAME
+.Nm pause
+.Nd don't exit, efficiently
+.Sh SYNOPSIS
+.Nm pause
+.Sh DESCRIPTION
+.Nm pause
+waits to be terminated by a signal.
+It can be used when service supervision is used but there is no
+long-running program to supervise.
+.Nm pause
+uses minimal system resources.
+.Sh EXAMPLES
+Setting up a static IP address with
+.Xr plugsv 8 .
+.Pp
+.Pa /etc/netsv/eth0/run :
+.Bd -literal -offset indent
+#!/bin/sh
+ip link set eth0 up
+ip addr add 192.0.2.1/24 dev eth0
+exec pause
+.Ed
+.Pp
+.Pa /etc/netsv/eth0/finish :
+.Bd -literal -offset indent
+#!/bin/sh
+ip addr del 192.0.2.1/24 dev eth0
+ip link set eth0 down
+.Ed
+.Sh SEE ALSO
+.Xr sleep 1 ,
+.Xr pause 2
+.Sh AUTHOR
+.An Leah Neukirchen ,
+.Mt [email protected] .
diff --git a/man/shutdown.8 b/man/shutdown.8
@@ -0,0 +1,90 @@
+.Dd July 29, 2014
+.Dt SHUTDOWN 8
+.Os Linux
+.Sh NAME
+.Nm shutdown
+.Nd bring down the system
+.Sh SYNOPSIS
+.Nm shutdown
+.Op Fl rhP
+.Op Fl fF
+.Op Cm now | Cm + Ns Ar mins
+.Op Ar message ...
+.Sh DESCRIPTION
+.Nm
+brings the system down in a secure way.
+All logged-in users
+are notified that the system is going down, and
+.Xr login 1
+is blocked.
+.Pp
+By default,
+.Nm
+puts the system into single user mode.
+Rebooting and halting the system can be done using the following options:
+.Bl -tag -width indent
+.It Fl c
+Cancel an ongoing shutdown.
+.It Fl f
+Enable fast booting; skip
+.Xr fsck 8
+on next boot.
+.It Fl F
+Force run of
+.Xr fsck 8
+on next boot.
+.It Fl h
+Halt the system.
+.It Fl k
+Don't really shutdown; only send the warning messages to everybody.
+.It Fl P
+Poweroff the system.
+.It Fl r
+Reboot the system.
+.It Cm now
+Shutdown without further waiting.
+.It Cm + Ns Ar mins
+Wait
+.Ar mins
+minutes before shutting down.
+.It Ar message
+Message displayed to all users, defaults to "system is going down".
+.El
+.Sh UNSUPPORTED OPTIONS
+This version of
+.Nm
+is based on
+.Xr runit 8 ,
+the following features are
+.Sy not
+supported:
+.Bl -tag -width indent
+.It Fl t Ar secs
+to wait
+.Ar secs
+seconds between SIGKILL and SIGTERM on shutdown is silently ignored.
+.It Fl a
+Use
+.Pa /etc/shutdown.allow .
+.It Fl H
+Drop into boot monitor.
+.It Fl n
+Don't call
+.Xr init 8 .
+.It Ar hh Ns : Ns Ar mm
+Absolute time specification is not implemented.
+.El
+.Sh EXAMPLES
+Turn off the system:
+.Dl # shutdown -h now
+.Sh SEE ALSO
+.Xr fsck 8 ,
+.Xr halt 8 ,
+.Xr init 8 ,
+.Xr poweroff 8 ,
+.Xr reboot 8 ,
+.Xr runit 8 ,
+.Xr runsvchdir 8
+.Sh AUTHOR
+.An Leah Neukirchen ,
+.Mt [email protected] .
diff --git a/man/vlogger.8 b/man/vlogger.8
@@ -0,0 +1,209 @@
+.Dd March 1, 2017
+.Dt VLOGGER 8
+.Os
+.Sh NAME
+.Nm vlogger
+.Nd log messages to syslog or an arbitrary executable
+.Sh SYNOPSIS
+.Nm vlogger
+.Op Fl isS
+.Op Fl f Ar file
+.Op Fl p Ar pri
+.Op Fl t Ar tag
+.Op Ar message ...
+.Sh DESCRIPTION
+The
+.Nm
+utility writes messages to the system log or an arbitrary executable.
+.Pp
+If
+.Nm
+is executed as
+.Nm logger
+it will always use the system log and behave like the regular
+.Xr logger 1 .
+.Pp
+Without
+.Ar message
+arguments
+.Nm
+reads messages from
+.Dv stdin
+or the file specified with the
+.Fl f
+flag.
+If the
+.Pa /etc/vlogger
+executable exists
+.Nm
+executes it with
+.Ar tag ,
+.Ar level
+and
+.Ar facility
+as arguments,
+replacing the
+.Nm
+process.
+.Pp
+If
+.Nm
+is executed as a log service for
+.Xr runit 8
+or another daemontools like
+supervision suite it uses the service name as default
+.Ar tag .
+As example if
+.Nm
+is linked to
+.Pa /var/service/foo/log/run
+it uses
+.Dq foo
+as
+.Ar tag
+and
+.Dq daemon.notice
+as
+.Ar pri .
+.Pp
+The options are as follows:
+.Bl -tag -width "-f file"
+.It Fl f Ar file
+Read lines from the specified
+.Ar file .
+This option cannot be combine
+.Ar message
+arguments.
+.It Fl i
+Log the PID of the
+.Nm
+process.
+Only supported if
+.Xr syslog 3
+is used.
+.It Fl p Ar pri
+The.
+.Ar pri
+can be
+.Pa facility.level
+or just
+.Pa facility .
+See
+.Sx FACILITIES ,
+.Sx LEVELS
+or
+.Xr syslog 3 .
+The default is
+.Dq user.notice .
+.It Fl S
+Force
+.Nm
+to use
+.Xr syslog 3
+even if
+.Pa /etc/vlogger
+exists.
+.It Fl s
+Output the message to standard error, as well as
+.Xr syslog 3 .
+Only supported if
+.Xr syslog 3
+is used.
+.It Fl t Ar tag
+Defines the
+.Xr openlog 3
+.Pa ident
+which is used as prefix for each log message or passed as first argument to
+.Pa /etc/vlogger .
+The default is the
+.Ev LOGNAME
+environment variable.
+.It Ar message
+Write the
+.Ar message
+to the system log.
+.El
+.Sh FACILITIES
+.Bl -tag -width 11n -compact
+.It auth
+.It authpriv
+.It cron
+.It daemon
+.It ftp
+.It kern
+can not be used from userspace replaced with
+.Pa daemon .
+.It lpr
+.It mail
+.It news
+.It syslog
+.It user
+.It uucp
+.It local[0-7]
+.It security
+deprecated synonym for
+.Pa auth .
+.El
+.Sh LEVELS
+.Bl -tag -width 11n -compact
+.It emerg
+.It alert
+.It crit
+.It err
+.It warning
+.It notice
+.It info
+.It debug
+.It panic
+deprecated synonym for
+.Pa emerg .
+.It error
+deprecated synonym for
+.Ar err .
+.It warn
+deprecated synonym for
+.Pa warning .
+.El
+.Sh FILES
+.Bl -tag -width indent
+.It /etc/vlogger
+An optional executable file that is used to handle the messages.
+It is executed with
+.Ar tag ,
+.Ar level
+and
+.Ar facility
+as arguments
+and replaces the
+.Nm
+process.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+.Pa /etc/vlogger :
+.Bd -literal -offset indent
+#!/bin/sh
+exec svlogd /var/log/$1
+.Ed
+.Sh SEE ALSO
+.Xr logger 1 ,
+.Xr syslog 3 ,
+.Xr svlogd 8
+.Sh HISTORY
+This program is a replacement for the
+.Nm logger
+utility provided by
+.Nm util-linux .
+.Sh AUTHORS
+.An Duncan Overbruck Aq Mt [email protected]
+.Sh LICENSE
+.Nm
+is in the public domain.
+.Pp
+To the extent possible under law,
+the creator of this work
+has waived all copyright and related or
+neighboring rights to this work.
+.Pp
+.Lk http://creativecommons.org/publicdomain/zero/1.0/
diff --git a/man/zzz.8 b/man/zzz.8
@@ -0,0 +1,94 @@
+.Dd July 25, 2014
+.Dt ZZZ 8
+.Os Linux
+.Sh NAME
+.Nm zzz ,
+.Nm ZZZ
+.Nd suspend or hibernate your computer
+.Sh SYNOPSIS
+.Nm zzz
+.Op Fl nSzZRH
+.Nm ZZZ
+.Op Fl nSzZRH
+.Sh DESCRIPTION
+.Nm
+is a simple script to suspend or hibernate your computer.
+It supports hooks before and after suspending.
+.Bl -tag -width indent
+.It Fl n
+dry-run mode.
+Instead of performing an ACPI action,
+.Nm
+will just sleep for a few seconds.
+.It Fl S
+Enter low-power idle mode (ACPI S1, kernel name "freeze").
+.It Fl z
+Enter suspend to RAM mode (ACPI S3, kernel name "mem").
+This is the default for
+.Nm zzz .
+.It Fl Z
+Enter hibernate to disk mode (ACPI S4, kernel name "disk") and power off.
+This is the default for
+.Nm ZZZ .
+.It Fl R
+Enter hibernate to disk mode and reboot.
+This can be used to switch operating systems.
+.It Fl H
+Enter hibernate to disk mode and suspend.
+This is also know as suspend-hybrid.
+.El
+.Sh FILES
+Before suspending,
+.Nm zzz
+runs the executable files in
+.Pa /etc/zzz.d/suspend
+in alphanumeric order.
+After suspending,
+.Nm zzz
+runs the executable files in
+.Pa /etc/zzz.d/resume
+in alphanumeric order (not in reverse order!).
+.Pp
+The environment variable
+.Ev ZZZ_MODE
+can be used in these hooks to differentiate between
+.Ic standby ,
+.Ic suspend ,
+and
+.Ic resume .
+.Sh DIAGNOSTICS
+.Bl -tag -width indent
+.It suspend/hibernate not supported
+The hardware does not support ACPI S3/S4 with this kernel.
+.It sleep permission denied
+You lack sufficent privilege to write to
+.Pa /sys/power/state .
+.It another instance of zzz is running
+.Nm
+locks
+.Pa /sys/power
+during operation.
+Perhaps a hook is stuck?
+.It Zzzz... yawn.
+The system has woken up again.
+Everything is fine.
+You feel refreshed.
+.Sh SEE ALSO
+.Xr pm-action 8 ,
+.Xr s2disk 8 ,
+.Xr s2ram 8 ,
+OpenBSD's
+.Xr apm 8
+.Sh HISTORY
+A similar
+.Nm apm
+command appeared in
+.Nx 1.3
+and
+.Ox 1.2 .
+.Sh AUTHOR
+.An Leah Neukirchen ,
+.Mt [email protected] .
+.Sh LICENSE
+.Nm
+is in the public domain.
diff --git a/src/command.c b/src/command.c
@@ -0,0 +1,93 @@
+#include "service.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+
+string command_error[] = {
+ [0] = "success",
+ [EBADCMD] = "command not found",
+ [ENOSV] = "service required",
+ [EBADSV] = "no matching services",
+ [EBEXT] = "invalid extra"
+};
+
+string command_string[] = {
+ (void*) S_START, "start", // start if not running and restart if failed
+ (void*) S_START, "up", // start if not running and restart if failed
+ (void*) S_STOP, "stop", // stop if running and not restart if failed
+ (void*) S_STOP, "down", // stop if running and not restart if failed
+ (void*) S_SEND, "send", // + signal | send signal to service
+ (void*) S_SEND, "kill", // + signal | send signal to service
+ (void*) S_PAUSE, "pause", // pause service (send SIGSTOP)
+ (void*) S_RESUME, "resume", // unpause service (send SIGCONT)
+ (void*) S_REVIVE, "revive", // revive died service
+ (void*) S_UPDATE, "update", // force update info // todo
+ (void*) S_REFRESH, "refresh", // if service is given refresh command dir otherwise look for new services
+ (void*) S_REFRESH, "reload", // if service is given refresh command dir otherwise look for new services
+ (void*) S_STATUS, "status", // get status of all services
+ (void*) S_EXIT, "exit", // exit
+ (void*) S_CHLEVEL, "chlevel",
+ 0, 0
+};
+
+
+int service_command(char command, char extra, string service, service_t* response, int response_max) {
+ char request[2] = { command, extra };
+
+ int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sockfd == -1) {
+ print_error("cannot connect to socket");
+ exit(1);
+ }
+
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ snprintf(addr.sun_path, sizeof(addr.sun_path), SV_CONTROL_SOCKET, runlevel);
+
+ int ret = connect(sockfd, (struct sockaddr*) &addr, sizeof(addr));
+ if (ret == -1) {
+ print_error("cannot connect to %s", addr.sun_path);
+ exit(EXIT_FAILURE);
+ }
+
+ write(sockfd, request, sizeof(request));
+ writestr(sockfd, service);
+
+ int res;
+ read(sockfd, &res, 1);
+
+ uint8_t service_buffer[19];
+
+ if (res == 0) {
+ if (response) {
+ while (res < response_max && readstr(sockfd, response[res].name) > 1) {
+ read(sockfd, service_buffer, sizeof(service_buffer));
+ service_load(&response[res], service_buffer);
+ // print_service(&s);
+ res++;
+ }
+ }
+ } else {
+ res *= -1;
+ }
+
+ close(sockfd);
+ return res;
+}
+
+char service_get_command(string command) {
+ char cmd_abbr = '\0';
+ for (string* cn = command_string; cn[1] != NULL; cn += 2) {
+ if (streq(command, cn[1])) {
+ cmd_abbr = (size_t) cn[0];
+ break;
+ }
+ }
+ return cmd_abbr;
+}
+\ No newline at end of file
diff --git a/src/command_handler.c b/src/command_handler.c
@@ -0,0 +1,107 @@
+#include "service.h"
+
+#include <signal.h>
+#include <stdio.h>
+
+
+int service_handle_command(service_t* s, sv_command_t command, unsigned char extra, service_t** response) {
+ bool changed = false;
+ switch (command) {
+ case S_STATUS:
+ if (s != NULL) {
+ response[0] = s;
+ return 1;
+ }
+ for (int i = 0; i < services_size; i++) {
+ response[i] = &services[i];
+ }
+ return services_size;
+
+ case S_START:
+ if (s == NULL)
+ return -ENOSV;
+ if (extra > 2) {
+ return -EBEXT;
+ }
+ if (extra == 1 || extra == 2) { // pin
+ changed = !s->restart;
+ s->restart = true;
+ } else {
+ s->restart_once = true;
+ }
+ if (extra == 0 || extra == 1)
+ service_start(s, &changed);
+
+ if (!changed)
+ return 0;
+ response[0] = s;
+ return 1;
+
+ case S_STOP:
+ if (s == NULL)
+ return -ENOSV;
+ if (extra > 2) {
+ return -EBEXT;
+ }
+ if (extra == 1 || extra == 2) { // pin
+ changed = s->restart;
+ s->restart = false;
+ }
+ if (extra == 0 || extra == 1)
+ service_stop(s, &changed);
+
+ if (!changed)
+ return 0;
+ response[0] = s;
+ return 1;
+
+ case S_SEND:
+ if (s == NULL)
+ return -ENOSV;
+
+ service_send(s, extra);
+ return 1;
+
+ case S_PAUSE:
+ if (s == NULL)
+ return -ENOSV;
+
+ s->paused = true;
+ service_send(s, SIGSTOP);
+ response[0] = s;
+ return 1;
+
+ case S_RESUME:
+ if (s == NULL)
+ return -ENOSV;
+
+ s->paused = false;
+ service_send(s, SIGCONT);
+ response[0] = s;
+ return 1;
+
+ case S_REVIVE:
+ if (s == NULL)
+ return -ENOSV;
+
+ s->state = STATE_INACTIVE;
+ service_start(s, &changed);
+
+ if (!changed)
+ return 0;
+
+ response[0] = s;
+ return 1;
+
+ case S_EXIT:
+ daemon_running = false;
+ return 0;
+
+ case S_REFRESH:
+ return service_refresh(response);
+
+ default:
+ fprintf(stderr, "warning: handling command: unknown command 0x%2x%2x\n", command, extra);
+ return -EBADSV;
+ }
+}
diff --git a/src/config_parser.c b/src/config_parser.c
@@ -0,0 +1,83 @@
+#include "config_parser.h"
+
+#include <fcntl.h>
+#include <linux/limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+
+void parse_param_file(service_t* s, char* args[]) {
+ int param_file;
+ int args_size = 0;
+ int line_size = 0;
+ char c;
+
+ snprintf(args[args_size++], SV_PARAM_FILE_LINE_MAX, "%s/%s/%s", service_dir, s->name, "run");
+
+ bool start = true;
+ if ((param_file = open("params", O_RDONLY)) != -1) {
+ while (read(param_file, &c, 1) > 0) {
+ if (start && c == '%') {
+ args_size--;
+ continue;
+ }
+ if (c == '\n') {
+ args[args_size++][line_size] = '\0';
+
+ line_size = 0;
+ } else {
+ args[args_size][line_size++] = c;
+ }
+ start = false;
+ }
+ if (line_size > 0)
+ args[args_size++][line_size] = '\0';
+ close(param_file);
+ }
+
+ args[args_size] = NULL;
+}
+
+void parse_env_file(char** env) {
+ int env_file;
+ int env_size = 0;
+ int line_size = 0;
+ char c;
+
+ if ((env_file = open("env", O_RDONLY)) != -1) {
+ while (read(env_file, &c, 1) > 0) {
+ if (c == '\n') {
+ env[env_size++][line_size] = '\0';
+
+ line_size = 0;
+ } else {
+ env[env_size][line_size++] = c;
+ }
+ }
+ if (line_size > 0)
+ env[env_size++][line_size] = '\0';
+ close(env_file);
+ }
+
+ env[env_size] = NULL;
+}
+
+
+pid_t parse_pid_file(service_t* s) {
+ char path_buf[PATH_MAX];
+ snprintf(path_buf, PATH_MAX, "%s/%s/pid", service_dir, s->name);
+ int pid_file;
+ if ((pid_file = open(path_buf, O_RDONLY)) == -1)
+ return 0;
+
+ char buffer[20];
+ int n;
+ if ((n = read(pid_file, buffer, sizeof(buffer))) <= 0) {
+ close(pid_file);
+ return 0;
+ }
+ buffer[n] = '\0';
+
+ close(pid_file);
+ return atoi(buffer);
+}
diff --git a/src/dependency.c b/src/dependency.c
@@ -0,0 +1,48 @@
+#include "service.h"
+#include "util.h"
+
+#include <fcntl.h>
+#include <linux/limits.h>
+#include <stdio.h>
+#include <string.h>
+
+
+void service_add_dependency(service_t* s, service_t* d) {
+ if (s == d)
+ return;
+
+ depends[depends_size].service = s;
+ depends[depends_size].depends = d;
+ depends_size++;
+}
+
+void service_update_dependency(service_t* s) {
+ service_t* dep;
+
+ if (s->log_service) { // aka keep first entry (the log service) if a log service is used
+ service_add_dependency(s, s->log_service);
+ }
+
+ int depends_file;
+ char depends_path[PATH_MAX];
+ snprintf(depends_path, PATH_MAX, "%s/%s/%s", service_dir, s->name, "depends");
+
+ if ((depends_file = open(depends_path, O_RDONLY)) == -1)
+ return;
+
+ char line[512];
+ 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/exec/finit.c b/src/exec/finit.c
@@ -0,0 +1,150 @@
+#include "config.h"
+#include "service.h"
+#include "util.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+
+void sigblock_all(bool unblock);
+
+int handle_initctl(int argc, string* argv) {
+ if (argc != 2 || argv[1][1] != '\0' || (argv[1][0] != '0' && argv[1][0] != '6')) {
+ printf("Usage: %s <0|6>\n", argv[0]);
+ return 1;
+ }
+ if (getuid() != 0) {
+ printf("can only be run as root...\n");
+ return 1;
+ }
+ int sig = argv[1][0] == '0' ? SIGTERM : SIGINT;
+ if (kill(1, sig) == -1) {
+ print_error("unable to kill init");
+ return 1;
+ }
+ return 0;
+}
+
+
+void handle_stage1();
+void handle_stage3();
+
+static bool do_reboot;
+
+static void signal_interrupt(int signum) {
+ daemon_running = false;
+ do_reboot = signum == SIGINT;
+}
+
+
+int main(int argc, string* argv) {
+ int ttyfd;
+ sigset_t ss;
+
+ if (getpid() != 1) {
+ return handle_initctl(argc, argv);
+ }
+ setsid();
+
+ sigblock_all(false);
+
+ /* console */
+ if ((ttyfd = open("/dev/console", O_WRONLY)) != -1) {
+ dup2(ttyfd, 0);
+ dup2(ttyfd, 1);
+ dup2(ttyfd, 2);
+ if (ttyfd > 2) close(ttyfd);
+ }
+
+ // disable ctrl-alt-delete
+ reboot(0);
+
+ printf("booting...\n");
+
+ handle_stage1();
+
+ if (daemon_running) { // stage1 succeed
+ sigblock_all(true);
+
+ struct sigaction sigact = { 0 };
+ sigact.sa_handler = signal_interrupt;
+ sigaction(SIGTERM, &sigact, NULL);
+ sigaction(SIGINT, &sigact, NULL);
+
+ service_supervise(SV_SERVICE_DIR, SV_RUNLEVEL, true);
+ sigblock_all(false);
+ }
+ handle_stage3();
+
+ /* reget stderr */
+ if ((ttyfd = open("/dev/console", O_WRONLY)) != -1) {
+ dup2(ttyfd, 1);
+ dup2(ttyfd, 2);
+ if (ttyfd > 2) close(ttyfd);
+ }
+
+#ifdef RB_AUTOBOOT
+ /* fallthrough stage 3 */
+ printf("sending KILL signal to all processes...\n");
+ kill(-1, SIGKILL);
+ pid_t pid;
+
+ if ((pid = fork()) <= 0) {
+ if (do_reboot) {
+ printf("system reboot\n");
+ sync();
+ reboot(RB_AUTOBOOT);
+ } else {
+# ifdef RB_POWER_OFF
+ printf("system power off\n");
+ sync();
+ reboot(RB_POWER_OFF);
+ sleep(2);
+# endif
+# ifdef RB_HALT_SYSTEM
+ printf("system halt\n");
+ sync();
+ reboot(RB_HALT_SYSTEM);
+# else
+# ifdef RB_HALT
+ printf("system halt\n");
+ sync();
+ reboot(RB_HALT);
+# else
+ printf("system reboot\n");
+ sync();
+ reboot(RB_AUTOBOOT);
+# endif
+# 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/src/exec/fsvc.c b/src/exec/fsvc.c
@@ -0,0 +1,184 @@
+#include "config.h"
+#include "service.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+
+static const char HELP_MESSAGE[] =
+ "Usage:\n"
+ " %s [options] <runlevel>\n"
+ "\n"
+ "Options:\n"
+ " -h, --help ............... prints this and exits\n"
+ " -i, --as-init ............ execute start/stop script\n"
+ " -o, --stdout ............. print service stdout/stderr in console\n"
+ " -s, --service-dir <path> . using service-dir (default: " SV_SERVICE_DIR ")\n"
+ " -v, --verbose ............ print more info\n"
+ " -V, --version ............ prints current version and exits\n"
+ "\n";
+
+static const char VERSION_MESSAGE[] =
+ "FISS v" SV_VERSION "\n";
+
+void print_status(service_t* s) {
+ const char* state;
+ switch (s->state) {
+ case STATE_INACTIVE:
+ state = "inactive";
+ break;
+ case STATE_STARTING:
+ state = "starting";
+ break;
+ case STATE_ACTIVE_PID:
+ state = "active (pid)";
+ break;
+ case STATE_ACTIVE_BACKGROUND:
+ state = "active (background)";
+ break;
+ case STATE_ACTIVE_DUMMY:
+ state = "active (dummy)";
+ break;
+ case STATE_ACTIVE_FOREGROUND:
+ state = "active";
+ break;
+ case STATE_FINISHING:
+ state = "finishing";
+ break;
+ case STATE_STOPPING:
+ state = "stopping";
+ break;
+ case STATE_DEAD:
+ state = "dead";
+ break;
+ }
+ time_t diff = time(NULL) - s->status_change;
+ string diff_unit = "sec";
+ if (diff >= 60) {
+ diff /= 60;
+ diff_unit = "min";
+ }
+ if (diff >= 60) {
+ diff /= 60;
+ diff_unit = "hours";
+ }
+ if (diff >= 24) {
+ diff /= 24;
+ diff_unit = "days";
+ }
+ printf("%s since %lu%s", state, diff, diff_unit);
+}
+
+void print_service(service_t* s, service_t* log) {
+ printf("- %s (", s->name);
+ print_status(s);
+ printf(")\n");
+ printf(" [ %c ] restart on exit\n", s->restart ? 'x' : ' ');
+ printf(" [%3d] last return code (%s)\n", s->return_code, s->last_exit == EXIT_SIGNALED ? "signaled" : "exited");
+ if (s->log_service) {
+ printf(" logging: ");
+ print_status(log);
+ printf("\n");
+ }
+ printf("\n");
+}
+
+static const struct option long_options[] = {
+ { "help", no_argument, 0, 'h' },
+ { "verbose", no_argument, 0, 'v' },
+ { "version", no_argument, 0, 'V' },
+ { "runlevel", no_argument, 0, 'r' },
+ { "service-dir", no_argument, 0, 's' },
+ { "pin", no_argument, 0, 'n' },
+ { "now", no_argument, 0, 'p' },
+ { 0 }
+};
+
+int main(int argc, char** argv) {
+ runlevel = getenv(SV_RUNLEVEL_ENV) ?: SV_RUNLEVEL;
+ service_dir = SV_SERVICE_DIR;
+
+ int c;
+ while ((c = getopt_long(argc, argv, ":hVvnps:r:", long_options, NULL)) > 0) {
+ switch (c) {
+ case 'r':
+ runlevel = optarg;
+ break;
+ case 's':
+ service_dir = optarg;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ case 'V':
+ printf(VERSION_MESSAGE);
+ return 0;
+ case 'h':
+ printf(HELP_MESSAGE, argv[0]);
+ return 0;
+ case '?':
+ if (optopt)
+ fprintf(stderr, "error: invalid option -%c\n", optopt);
+ else
+ fprintf(stderr, "error: invalid option %s\n", argv[optind - 1]);
+ return 1;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ printf("%s [options] <command> [service]\n", argv[0]);
+ return 1;
+ }
+
+ char* command = argv[0];
+ string service = argc >= 2 ? argv[1] : "";
+ int extra = argc >= 3 ? atoi(argv[2]) : 0;
+
+ service_t response[50];
+ int res = 0;
+
+ if (streq(command, "check")) {
+ service_t s;
+ int rc;
+ if ((rc = service_command(S_STATUS, 0, service, &s, 1)) != 1) {
+ printf("error: %s (errno: %d)\n", command_error[-res], -res);
+ return 1;
+ }
+ return s.state == STATE_ACTIVE_PID || s.state == STATE_ACTIVE_DUMMY || s.state == STATE_ACTIVE_FOREGROUND || s.state == STATE_ACTIVE_BACKGROUND;
+
+ } else {
+ char cmd_abbr;
+ if ((cmd_abbr = service_get_command(command)) == 0)
+ res = -EBADCMD;
+ else
+ res = service_command(cmd_abbr, extra, service, response, 50);
+
+ if (res < 0) {
+ printf("error: %s (errno: %d)\n", command_error[-res], -res);
+ return 1;
+ }
+
+ for (int i = 0; i < res; i++) {
+ service_t* log = NULL;
+ if (response[i].log_service) {
+ for (int j = 0; j < res; j++) {
+ if (strncmp(response[i].name, response[j].name, strlen(response[i].name)) == 0) {
+ log = &response[j];
+ break;
+ }
+ }
+ }
+ print_service(&response[i], log);
+ }
+ }
+}
diff --git a/src/exec/fsvs.c b/src/exec/fsvs.c
@@ -0,0 +1,121 @@
+// daemon manager
+
+#include "config.h"
+#include "service.h"
+#include "util.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdint.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>
+
+#define SV_DEPENDS_MAX_STR static_stringify(SV_DEPENDS_MAX)
+#define MAX_SERVICE_STR static_stringify(SV_SERVICE_MAX)
+#define SV_STOP_TIMEOUT_STR static_stringify(SV_STOP_TIMEOUT)
+
+
+static const char HELP_MESSAGE[] =
+ "Usage:\n"
+ " %s [options] <runlevel>\n"
+ "\n"
+ "Options:\n"
+ " -h, --help ............... prints this and exits\n"
+ " -i, --as-init ............ execute start/stop script\n"
+ " -o, --stdout ............. print service stdout/stderr in console\n"
+ " -s, --service-dir <path> . using service-dir (default: " SV_SERVICE_DIR ")\n"
+ " -v, --verbose ............ print more info\n"
+ " -V, --version ............ prints current version and exits\n"
+ "\n";
+
+static const char VERSION_MESSAGE[] =
+ "FISS v" SV_VERSION "\n"
+ "\n"
+ "Features:\n"
+ " service directory: " SV_SERVICE_DIR "\n"
+ " service control socket: " SV_CONTROL_SOCKET "\n"
+ " max. services: " MAX_SERVICE_STR "\n"
+ " max. dependencies: " SV_DEPENDS_MAX_STR "\n"
+ " stop timeout: " SV_STOP_TIMEOUT_STR "sec\n"
+ "\n";
+
+static const struct option long_options[] = {
+ { "autostart", no_argument, 0, 'a' },
+ { "help", no_argument, 0, 'h' },
+ { "verbose", no_argument, 0, 'v' },
+ { "version", no_argument, 0, 'V' },
+ { "force-socket", no_argument, 0, 'f' },
+ { 0 }
+};
+
+static bool consider_autostart = false;
+static bool stdout_redirect = false;
+
+static void signal_interrupt(int signum) {
+ (void) signum;
+
+ daemon_running = false;
+}
+
+int main(int argc, char** argv) {
+ bool force_socket = false;
+
+ int c;
+ while ((c = getopt_long(argc, argv, ":ahiosf:vV", long_options, NULL)) > 0) {
+ switch (c) {
+ case 'a':
+ consider_autostart = true;
+ break;
+ case 'h':
+ printf(VERSION_MESSAGE, "<runlevel>");
+ printf(HELP_MESSAGE, argv[0]);
+ return 0;
+ case 'o':
+ stdout_redirect = true;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ case 'V':
+ printf(VERSION_MESSAGE, "<runlevel>");
+ return 0;
+ case 'f':
+ force_socket = true;
+ break;
+ case '?':
+ if (optopt)
+ fprintf(stderr, "error: invalid option -%c\n", optopt);
+ else
+ fprintf(stderr, "error: invalid option %s\n", argv[optind - 1]);
+ return 1;
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+ if (argc == 0) {
+ fprintf(stderr, "error: missing <service-dir>\n");
+ return 1;
+ } else if (argc == 1) {
+ fprintf(stderr, "error: missing <runlevel>\n");
+ return 1;
+ } else if (argc > 2) {
+ fprintf(stderr, "error: too many arguments\n");
+ return 1;
+ }
+
+ signal(SIGINT, signal_interrupt);
+ signal(SIGCONT, signal_interrupt);
+
+ return service_supervise(argv[0], argv[1], force_socket);
+}
diff --git a/src/exec/halt.c b/src/exec/halt.c
@@ -0,0 +1,136 @@
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/reboot.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#include <utmp.h>
+
+extern char* __progname;
+
+typedef enum { NOOP,
+ HALT,
+ REBOOT,
+ POWEROFF } action_type;
+
+#ifndef OUR_WTMP
+# define OUR_WTMP "/var/log/wtmp"
+#endif
+
+#ifndef OUR_UTMP
+# define OUR_UTMP "/run/utmp"
+#endif
+
+void write_wtmp(int boot) {
+ int fd;
+
+ if ((fd = open(OUR_WTMP, O_WRONLY | O_APPEND)) < 0)
+ return;
+
+ struct utmp utmp = { 0 };
+ struct utsname uname_buf;
+ struct timeval tv;
+
+ gettimeofday(&tv, 0);
+ utmp.ut_tv.tv_sec = tv.tv_sec;
+ utmp.ut_tv.tv_usec = tv.tv_usec;
+
+ utmp.ut_type = boot ? BOOT_TIME : RUN_LVL;
+
+ strncpy(utmp.ut_name, boot ? "reboot" : "shutdown", sizeof utmp.ut_name);
+ strncpy(utmp.ut_id, "~~", sizeof utmp.ut_id);
+ strncpy(utmp.ut_line, boot ? "~" : "~~", sizeof utmp.ut_line);
+ if (uname(&uname_buf) == 0)
+ strncpy(utmp.ut_host, uname_buf.release, sizeof utmp.ut_host);
+
+ write(fd, (char*) &utmp, sizeof utmp);
+ close(fd);
+
+ if (boot) {
+ if ((fd = open(OUR_UTMP, O_WRONLY | O_APPEND)) < 0)
+ return;
+ write(fd, (char*) &utmp, sizeof utmp);
+ close(fd);
+ }
+}
+
+int main(int argc, char* argv[]) {
+ int do_sync = 1;
+ int do_force = 0;
+ int do_wtmp = 1;
+ int opt;
+ action_type action = NOOP;
+
+ if (strncmp(__progname, "halt", 4) == 0)
+ action = HALT;
+ else if (strncmp(__progname, "reboot", 6) == 0)
+ action = REBOOT;
+ else if (strncmp(__progname, "poweroff", 8) == 0)
+ action = POWEROFF;
+ else
+ warnx("no default behavior, needs to be called as halt/reboot/poweroff.");
+
+ while ((opt = getopt(argc, argv, "dfhinwB")) != -1)
+ switch (opt) {
+ case 'n':
+ do_sync = 0;
+ break;
+ case 'w':
+ action = NOOP;
+ 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:
+ errx(1, "Usage: %s [-n] [-f] [-d] [-w] [-B]", __progname);
+ }
+
+ if (do_wtmp)
+ write_wtmp(0);
+
+ if (do_sync)
+ sync();
+
+ switch (action) {
+ case HALT:
+ if (do_force)
+ reboot(RB_HALT_SYSTEM);
+ else
+ execl("/sbin/finit", "init", "0", (char*) 0);
+ err(1, "halt failed");
+ break;
+ case POWEROFF:
+ if (do_force)
+ reboot(RB_POWER_OFF);
+ else
+ execl("/sbin/finit", "init", "0", (char*) 0);
+ err(1, "poweroff failed");
+ break;
+ case REBOOT:
+ if (do_force)
+ reboot(RB_AUTOBOOT);
+ else
+ execl("/sbin/finit", "init", "6", (char*) 0);
+ err(1, "reboot failed");
+ break;
+ case NOOP:
+ break;
+ }
+
+ return 0;
+}
diff --git a/src/exec/pause.c b/src/exec/pause.c
@@ -0,0 +1,16 @@
+#include <signal.h>
+#include <unistd.h>
+
+static void signal_nop(int signum) {
+ (void) signum;
+}
+
+int main() {
+ signal(SIGTERM, signal_nop);
+ signal(SIGINT, signal_nop);
+ signal(SIGHUP, SIG_IGN);
+
+ pause();
+
+ return 0;
+}
diff --git a/src/exec/seedrng.c b/src/exec/seedrng.c
@@ -0,0 +1,470 @@
+/* Based on code from <https://git.zx2c4.com/seedrng/about/>. */
+
+#include "util.h"
+
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/random.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/random.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef LOCALSTATEDIR
+# define LOCALSTATEDIR "/var/lib"
+#endif
+
+#define SEED_DIR LOCALSTATEDIR "/seedrng"
+#define CREDITABLE_SEED "seed.credit"
+#define NON_CREDITABLE_SEED "seed.no-credit"
+
+enum blake2s_lengths {
+ BLAKE2S_BLOCK_LEN = 64,
+ BLAKE2S_HASH_LEN = 32,
+ BLAKE2S_KEY_LEN = 32
+};
+
+enum seedrng_lengths {
+ MAX_SEED_LEN = 512,
+ MIN_SEED_LEN = BLAKE2S_HASH_LEN
+};
+
+struct blake2s_state {
+ uint32_t h[8];
+ uint32_t t[2];
+ uint32_t f[2];
+ uint8_t buf[BLAKE2S_BLOCK_LEN];
+ unsigned int buflen;
+ unsigned int outlen;
+};
+
+#define le32_to_cpup(a) le32toh(*(a))
+#define cpu_to_le32(a) htole32(a)
+#ifndef ARRAY_SIZE
+# define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#endif
+#ifndef DIV_ROUND_UP
+# define DIV_ROUND_UP(n, d) (((n) + (d) -1) / (d))
+#endif
+
+static inline void cpu_to_le32_array(uint32_t* buf, unsigned int words) {
+ while (words--) {
+ *buf = cpu_to_le32(*buf);
+ ++buf;
+ }
+}
+
+static inline void le32_to_cpu_array(uint32_t* buf, unsigned int words) {
+ while (words--) {
+ *buf = le32_to_cpup(buf);
+ ++buf;
+ }
+}
+
+static inline uint32_t ror32(uint32_t word, unsigned int shift) {
+ return (word >> (shift & 31)) | (word << ((-shift) & 31));
+}
+
+static const uint32_t blake2s_iv[8] = {
+ 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL,
+ 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL
+};
+
+static const uint8_t blake2s_sigma[10][16] = {
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
+ { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
+ { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
+ { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
+ { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
+ { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
+ { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
+ { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
+ { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
+};
+
+static void blake2s_set_lastblock(struct blake2s_state* state) {
+ state->f[0] = -1;
+}
+
+static void blake2s_increment_counter(struct blake2s_state* state, const uint32_t inc) {
+ state->t[0] += inc;
+ state->t[1] += (state->t[0] < inc);
+}
+
+static void blake2s_init_param(struct blake2s_state* state, const uint32_t param) {
+ int i;
+
+ memset(state, 0, sizeof(*state));
+ for (i = 0; i < 8; ++i)
+ state->h[i] = blake2s_iv[i];
+ state->h[0] ^= param;
+}
+
+static void blake2s_init(struct blake2s_state* state, const size_t outlen) {
+ blake2s_init_param(state, 0x01010000 | outlen);
+ state->outlen = outlen;
+}
+
+static void blake2s_compress(struct blake2s_state* state, const uint8_t* block, size_t nblocks, const uint32_t inc) {
+ uint32_t m[16];
+ uint32_t v[16];
+ int i;
+
+ while (nblocks > 0) {
+ blake2s_increment_counter(state, inc);
+ memcpy(m, block, BLAKE2S_BLOCK_LEN);
+ le32_to_cpu_array(m, ARRAY_SIZE(m));
+ memcpy(v, state->h, 32);
+ v[8] = blake2s_iv[0];
+ v[9] = blake2s_iv[1];
+ v[10] = blake2s_iv[2];
+ v[11] = blake2s_iv[3];
+ v[12] = blake2s_iv[4] ^ state->t[0];
+ v[13] = blake2s_iv[5] ^ state->t[1];
+ v[14] = blake2s_iv[6] ^ state->f[0];
+ v[15] = blake2s_iv[7] ^ state->f[1];
+
+#define G(r, i, a, b, c, d) \
+ do { \
+ a += b + m[blake2s_sigma[r][2 * i + 0]]; \
+ d = ror32(d ^ a, 16); \
+ c += d; \
+ b = ror32(b ^ c, 12); \
+ a += b + m[blake2s_sigma[r][2 * i + 1]]; \
+ d = ror32(d ^ a, 8); \
+ c += d; \
+ b = ror32(b ^ c, 7); \
+ } while (0)
+
+#define ROUND(r) \
+ do { \
+ G(r, 0, v[0], v[4], v[8], v[12]); \
+ G(r, 1, v[1], v[5], v[9], v[13]); \
+ G(r, 2, v[2], v[6], v[10], v[14]); \
+ G(r, 3, v[3], v[7], v[11], v[15]); \
+ G(r, 4, v[0], v[5], v[10], v[15]); \
+ G(r, 5, v[1], v[6], v[11], v[12]); \
+ G(r, 6, v[2], v[7], v[8], v[13]); \
+ G(r, 7, v[3], v[4], v[9], v[14]); \
+ } while (0)
+ ROUND(0);
+ ROUND(1);
+ ROUND(2);
+ ROUND(3);
+ ROUND(4);
+ ROUND(5);
+ ROUND(6);
+ ROUND(7);
+ ROUND(8);
+ ROUND(9);
+
+#undef G
+#undef ROUND
+
+ for (i = 0; i < 8; ++i)
+ state->h[i] ^= v[i] ^ v[i + 8];
+
+ block += BLAKE2S_BLOCK_LEN;
+ --nblocks;
+ }
+}
+
+static void blake2s_update(struct blake2s_state* state, const void* inp, size_t inlen) {
+ const size_t fill = BLAKE2S_BLOCK_LEN - state->buflen;
+ const uint8_t* in = inp;
+
+ if (!inlen)
+ return;
+ if (inlen > fill) {
+ memcpy(state->buf + state->buflen, in, fill);
+ blake2s_compress(state, state->buf, 1, BLAKE2S_BLOCK_LEN);
+ state->buflen = 0;
+ in += fill;
+ inlen -= fill;
+ }
+ if (inlen > BLAKE2S_BLOCK_LEN) {
+ const size_t nblocks = DIV_ROUND_UP(inlen, BLAKE2S_BLOCK_LEN);
+ blake2s_compress(state, in, nblocks - 1, BLAKE2S_BLOCK_LEN);
+ in += BLAKE2S_BLOCK_LEN * (nblocks - 1);
+ inlen -= BLAKE2S_BLOCK_LEN * (nblocks - 1);
+ }
+ memcpy(state->buf + state->buflen, in, inlen);
+ state->buflen += inlen;
+}
+
+static void blake2s_final(struct blake2s_state* state, uint8_t* out) {
+ blake2s_set_lastblock(state);
+ memset(state->buf + state->buflen, 0, BLAKE2S_BLOCK_LEN - state->buflen);
+ blake2s_compress(state, state->buf, 1, state->buflen);
+ cpu_to_le32_array(state->h, ARRAY_SIZE(state->h));
+ memcpy(out, state->h, state->outlen);
+}
+
+static ssize_t getrandom_full(void* buf, size_t count, unsigned int flags) {
+ ssize_t ret, total = 0;
+ uint8_t* p = buf;
+
+ do {
+ ret = getrandom(p, count, flags);
+ if (ret < 0 && errno == EINTR)
+ continue;
+ else if (ret < 0)
+ return ret;
+ total += ret;
+ p += ret;
+ count -= ret;
+ } while (count);
+ return total;
+}
+
+static ssize_t read_full(int fd, void* buf, size_t count) {
+ ssize_t ret, total = 0;
+ uint8_t* p = buf;
+
+ do {
+ ret = read(fd, p, count);
+ if (ret < 0 && errno == EINTR)
+ continue;
+ else if (ret < 0)
+ return ret;
+ else if (ret == 0)
+ break;
+ total += ret;
+ p += ret;
+ count -= ret;
+ } while (count);
+ return total;
+}
+
+static ssize_t write_full(int fd, const void* buf, size_t count) {
+ ssize_t ret, total = 0;
+ const uint8_t* p = buf;
+
+ do {
+ ret = write(fd, p, count);
+ if (ret < 0 && errno == EINTR)
+ continue;
+ else if (ret < 0)
+ return ret;
+ total += ret;
+ p += ret;
+ count -= ret;
+ } while (count);
+ return total;
+}
+
+static size_t determine_optimal_seed_len(void) {
+ size_t ret = 0;
+ char poolsize_str[11] = { 0 };
+ int fd = open("/proc/sys/kernel/random/poolsize", O_RDONLY);
+
+ if (fd < 0 || read_full(fd, poolsize_str, sizeof(poolsize_str) - 1) < 0) {
+ perror("Unable to determine pool size, falling back to 256 bits");
+ ret = MIN_SEED_LEN;
+ } else
+ ret = DIV_ROUND_UP(strtoul(poolsize_str, NULL, 10), 8);
+ if (fd >= 0)
+ close(fd);
+ if (ret < MIN_SEED_LEN)
+ ret = MIN_SEED_LEN;
+ else if (ret > MAX_SEED_LEN)
+ ret = MAX_SEED_LEN;
+ return ret;
+}
+
+static int read_new_seed(uint8_t* seed, size_t len, bool* is_creditable) {
+ ssize_t ret;
+ int urandom_fd;
+
+ *is_creditable = false;
+ ret = getrandom_full(seed, len, GRND_NONBLOCK);
+ if (ret == (ssize_t) len) {
+ *is_creditable = true;
+ return 0;
+ } else if (ret < 0 && errno == ENOSYS) {
+ struct pollfd random_fd = {
+ .fd = open("/dev/random", O_RDONLY),
+ .events = POLLIN
+ };
+ if (random_fd.fd < 0)
+ return -errno;
+ *is_creditable = poll(&random_fd, 1, 0) == 1;
+ close(random_fd.fd);
+ } else if (getrandom_full(seed, len, GRND_INSECURE) == (ssize_t) len)
+ return 0;
+ urandom_fd = open("/dev/urandom", O_RDONLY);
+ if (urandom_fd < 0)
+ return -1;
+ ret = read_full(urandom_fd, seed, len);
+ if (ret == (ssize_t) len)
+ ret = 0;
+ else
+ ret = -errno ? -errno : -EIO;
+ close(urandom_fd);
+ errno = -ret;
+ return ret ? -1 : 0;
+}
+
+static int seed_rng(uint8_t* seed, size_t len, bool credit) {
+ struct {
+ int entropy_count;
+ int buf_size;
+ uint8_t buffer[MAX_SEED_LEN];
+ } req = {
+ .entropy_count = credit ? len * 8 : 0,
+ .buf_size = len
+ };
+ int random_fd, ret;
+
+ if (len > sizeof(req.buffer)) {
+ errno = EFBIG;
+ return -1;
+ }
+ memcpy(req.buffer, seed, len);
+
+ random_fd = open("/dev/urandom", O_RDONLY);
+ if (random_fd < 0)
+ return -1;
+ ret = ioctl(random_fd, RNDADDENTROPY, &req);
+ if (ret)
+ ret = -errno ? -errno : -EIO;
+ close(random_fd);
+ errno = -ret;
+ return ret ? -1 : 0;
+}
+
+static int seed_from_file_if_exists(string filename, int dfd, bool credit, struct blake2s_state* hash) {
+ uint8_t seed[MAX_SEED_LEN];
+ ssize_t seed_len;
+ int fd = -1, ret = 0;
+
+ fd = openat(dfd, filename, O_RDONLY);
+ if (fd < 0 && errno == ENOENT)
+ return 0;
+ else if (fd < 0) {
+ ret = -errno;
+ perror("Unable to open seed file");
+ goto out;
+ }
+ seed_len = read_full(fd, seed, sizeof(seed));
+ if (seed_len < 0) {
+ ret = -errno;
+ perror("Unable to read seed file");
+ goto out;
+ }
+ if ((unlinkat(dfd, filename, 0) < 0 || fsync(dfd) < 0) && seed_len) {
+ ret = -errno;
+ perror("Unable to remove seed after reading, so not seeding");
+ goto out;
+ }
+ if (!seed_len)
+ goto out;
+
+ blake2s_update(hash, &seed_len, sizeof(seed_len));
+ blake2s_update(hash, seed, seed_len);
+
+ printf("Seeding %zd bits %s crediting\n", seed_len * 8, credit ? "and" : "without");
+ if (seed_rng(seed, seed_len, credit) < 0) {
+ ret = -errno;
+ perror("Unable to seed");
+ }
+
+out:
+ if (fd >= 0)
+ close(fd);
+ errno = -ret;
+ return ret ? -1 : 0;
+}
+
+static bool skip_credit(void) {
+ string skip = getenv("SEEDRNG_SKIP_CREDIT");
+ return skip && (!strcmp(skip, "1") || !strcasecmp(skip, "true") ||
+ !strcasecmp(skip, "yes") || !strcasecmp(skip, "y"));
+}
+
+int main(int argc __attribute__((unused)), char* argv[] __attribute__((unused))) {
+ static const char seedrng_prefix[] = "SeedRNG v1 Old+New Prefix";
+ static const char seedrng_failure[] = "SeedRNG v1 No New Seed Failure";
+ int fd = -1, dfd = -1, program_ret = 0;
+ uint8_t new_seed[MAX_SEED_LEN];
+ size_t new_seed_len;
+ bool new_seed_creditable;
+ struct timespec realtime = { 0 }, boottime = { 0 };
+ struct blake2s_state hash;
+
+ umask(0077);
+ if (getuid()) {
+ errno = EACCES;
+ perror("This program requires root");
+ return 1;
+ }
+
+ blake2s_init(&hash, BLAKE2S_HASH_LEN);
+ blake2s_update(&hash, seedrng_prefix, strlen(seedrng_prefix));
+ clock_gettime(CLOCK_REALTIME, &realtime);
+ clock_gettime(CLOCK_BOOTTIME, &boottime);
+ blake2s_update(&hash, &realtime, sizeof(realtime));
+ blake2s_update(&hash, &boottime, sizeof(boottime));
+
+ if (mkdir(SEED_DIR, 0700) < 0 && errno != EEXIST) {
+ perror("Unable to create seed directory");
+ return 1;
+ }
+
+ dfd = open(SEED_DIR, O_DIRECTORY | O_RDONLY);
+ if (dfd < 0 || flock(dfd, LOCK_EX) < 0) {
+ perror("Unable to lock seed directory");
+ program_ret = 1;
+ goto out;
+ }
+
+ if (seed_from_file_if_exists(NON_CREDITABLE_SEED, dfd, false, &hash) < 0)
+ program_ret |= 1 << 1;
+ if (seed_from_file_if_exists(CREDITABLE_SEED, dfd, !skip_credit(), &hash) < 0)
+ program_ret |= 1 << 2;
+
+ new_seed_len = determine_optimal_seed_len();
+ if (read_new_seed(new_seed, new_seed_len, &new_seed_creditable) < 0) {
+ perror("Unable to read new seed");
+ new_seed_len = BLAKE2S_HASH_LEN;
+ strncpy((char*) new_seed, seedrng_failure, new_seed_len);
+ program_ret |= 1 << 3;
+ }
+ blake2s_update(&hash, &new_seed_len, sizeof(new_seed_len));
+ blake2s_update(&hash, new_seed, new_seed_len);
+ blake2s_final(&hash, new_seed + new_seed_len - BLAKE2S_HASH_LEN);
+
+ printf("Saving %zu bits of %s seed for next boot\n", new_seed_len * 8, new_seed_creditable ? "creditable" : "non-creditable");
+ fd = openat(dfd, NON_CREDITABLE_SEED, O_WRONLY | O_CREAT | O_TRUNC, 0400);
+ if (fd < 0) {
+ perror("Unable to open seed file for writing");
+ program_ret |= 1 << 4;
+ goto out;
+ }
+ if (write_full(fd, new_seed, new_seed_len) != (ssize_t) new_seed_len || fsync(fd) < 0) {
+ perror("Unable to write seed file");
+ program_ret |= 1 << 5;
+ goto out;
+ }
+ if (new_seed_creditable && renameat(dfd, NON_CREDITABLE_SEED, dfd, CREDITABLE_SEED) < 0) {
+ perror("Unable to make new seed creditable");
+ program_ret |= 1 << 6;
+ }
+out:
+ if (fd >= 0)
+ close(fd);
+ if (dfd >= 0)
+ close(dfd);
+ return program_ret;
+}
diff --git a/src/exec/vlogger.c b/src/exec/vlogger.c
@@ -0,0 +1,192 @@
+#include "util.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+extern char* __progname;
+
+static char pwd[PATH_MAX];
+
+typedef struct {
+ string const c_name;
+ int c_val;
+} CODE;
+
+CODE 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 }
+};
+
+CODE 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(string s, int* facility, int* level) {
+ char* p;
+ CODE* cp;
+
+ if ((p = strchr(s, '.'))) {
+ *p++ = 0;
+ for (cp = prioritynames; cp->c_name; cp++) {
+ if (strcmp(cp->c_name, p) == 0)
+ *level = cp->c_val;
+ }
+ }
+ if (*s)
+ for (cp = facilitynames; cp->c_name; cp++) {
+ if (strcmp(cp->c_name, s) == 0)
+ *facility = cp->c_val;
+ }
+}
+
+int main(int argc, char* argv[]) {
+ char buf[1024];
+ char *p, *argv0;
+ char* tag = NULL;
+ int c;
+ int Sflag = 0;
+ int logflags = 0;
+ int facility = LOG_USER;
+ int level = LOG_NOTICE;
+
+ argv0 = *argv;
+
+ if (strcmp(argv0, "./run") == 0) {
+ 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 (strcmp(__progname, "logger") == 0) {
+ /* behave just like logger(1) and only use syslog */
+ Sflag++;
+ }
+
+ while ((c = getopt(argc, argv, "f:ip:Sst:")) != -1)
+ switch (c) {
+ case 'f':
+ if (freopen(optarg, "r", stdin) == NULL) {
+ fprintf(stderr, "vlogger: %s: %s\n", optarg, strerror(errno));
+ return 1;
+ }
+ break;
+ case 'i':
+ logflags |= LOG_PID;
+ break;
+ case 'p':
+ strpriority(optarg, &facility, &level);
+ break;
+ case 'S':
+ Sflag++;
+ break;
+ case 's':
+ logflags |= LOG_PERROR;
+ break;
+ case 't':
+ tag = optarg;
+ break;
+ default:
+ fprintf(stderr, "usage: vlogger [-isS] [-f file] [-p pri] [-t tag] [message ...]\n");
+ exit(1);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ Sflag++;
+
+ if (!Sflag && access("/etc/vlogger", X_OK) != -1) {
+ CODE* cp;
+ const char *sfacility = "", *slevel = "";
+ for (cp = prioritynames; cp->c_name; cp++) {
+ if (cp->c_val == level)
+ slevel = cp->c_name;
+ }
+ for (cp = facilitynames; cp->c_name; cp++) {
+ if (cp->c_val == facility)
+ sfacility = cp->c_name;
+ }
+ execl("/etc/vlogger", argv0, tag ? tag : "", slevel, sfacility, (char*) 0);
+ fprintf(stderr, "vlogger: exec: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ openlog(tag ? tag : getlogin(), logflags, facility);
+
+ if (argc > 0) {
+ size_t len;
+ char * p, *e;
+ p = buf;
+ *p = '\0';
+ e = buf + sizeof buf - 2;
+ for (; *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/pattern.c b/src/pattern.c
@@ -0,0 +1,38 @@
+#include "pattern.h"
+
+static const char* strend(const char* s) {
+ while (*s)
+ s++;
+ return s;
+}
+
+int pattern_test(const char* pattern, const char* match) {
+ int i = 0;
+ const char *p, *m, *b;
+
+ // if more than one '*': exit with error
+ for (p = pattern; *p != '\0'; p++) {
+ if (*p == '*' && i++)
+ return -1;
+ }
+
+ m = match;
+ for (p = pattern; *p != '\0' && *p != '*'; p++, m++) {
+ if (*m == '\0' || (*p != *m && *p != '%'))
+ return 0;
+ }
+
+ if (*p == '\0' && *m != '\0')
+ return 0;
+
+ if (*p == '*') {
+ b = m;
+ m = strend(match);
+ for (p = strend(pattern); p >= pattern && *p != '*'; p--, m--) {
+ if (m < b || (*p != *m && *p != '%'))
+ return 0;
+ }
+ }
+
+ return 1;
+}
+\ No newline at end of file
diff --git a/src/register.c b/src/register.c
@@ -0,0 +1,72 @@
+#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>
+
+service_t* service_register(string name, bool log_service, bool* changed_ptr) {
+ service_t* s;
+
+ if ((s = service_get(name)) != NULL)
+ return s;
+
+ s = &services[services_size++];
+ s->state = STATE_INACTIVE;
+ s->status_change = time(NULL);
+ s->restart = false;
+ s->restart_once = false;
+ 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;
+
+ strcpy(s->name, name);
+
+ s->is_log_service = log_service;
+
+ struct stat stat_buf;
+
+ char path_buffer[PATH_MAX];
+ snprintf(path_buffer, PATH_MAX, "%s/%s/%s", service_dir, s->name, "log");
+
+ if (s->is_log_service) {
+ if (s->log_pipe.read == 0 || s->log_pipe.write == 0)
+ pipe((int*) &s->log_pipe);
+
+ } else if (stat(path_buffer, &stat_buf) > -1 && S_ISDIR(stat_buf.st_mode)) {
+ snprintf(path_buffer, PATH_MAX, "%s/%s", s->name, "log");
+
+ if (!s->log_service)
+ s->log_service = service_register(path_buffer, true, NULL);
+ }
+
+ bool autostart, autostart_once;
+
+ snprintf(path_buffer, PATH_MAX, "%s/%s/up-%s", service_dir, s->name, runlevel);
+ autostart = stat(path_buffer, &stat_buf) != -1 && S_ISREG(stat_buf.st_mode);
+
+ snprintf(path_buffer, PATH_MAX, "%s/%s/once-%s", service_dir, s->name, runlevel);
+ autostart_once = stat(path_buffer, &stat_buf) != -1 && S_ISREG(stat_buf.st_mode);
+
+ if (autostart && autostart_once) {
+ fprintf(stderr, "error: %s is marked for up AND once!\n", s->name);
+ } else {
+ s->restart = autostart;
+ s->restart_once = autostart_once;
+ }
+
+ s->status_change = time(NULL);
+
+ if (changed_ptr != NULL)
+ *changed_ptr = true;
+
+ return s;
+}
diff --git a/src/restart.c b/src/restart.c
@@ -0,0 +1,114 @@
+#include "config_parser.h"
+#include "service.h"
+#include "util.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <linux/limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+
+static void do_finish(service_t* s) {
+ char path_buffer[PATH_MAX];
+ struct stat stat_buffer;
+ snprintf(path_buffer, PATH_MAX, "%s/%s/finish", service_dir, s->name);
+
+ if (stat(path_buffer, &stat_buffer) == 0 && stat_buffer.st_mode & S_IEXEC) {
+ s->state = STATE_FINISHING;
+ if ((s->pid = fork()) == -1) {
+ print_error("cannot fork process");
+ } else if (s->pid == 0) {
+ dup2(null_fd, STDIN_FILENO);
+ dup2(null_fd, STDOUT_FILENO);
+ dup2(null_fd, STDERR_FILENO);
+
+ execlp(path_buffer, path_buffer, NULL);
+ print_error("cannot execute finish process");
+ _exit(1);
+ }
+ } else if (s->fail_count > SV_FAIL_MAX) {
+ s->state = STATE_DEAD;
+ printf(":: %s died\n", s->name);
+ } else {
+ s->state = STATE_INACTIVE;
+ }
+}
+
+
+void service_check_state(service_t* s, bool signaled, int return_code) {
+ s->status_change = time(NULL);
+ s->pid = 0;
+ s->restart_once = false;
+
+ char path_buffer[PATH_MAX];
+ struct stat stat_buffer;
+
+ switch (s->state) {
+ 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_PID:
+ case STATE_ACTIVE_BACKGROUND:
+ case STATE_STOPPING:
+ do_finish(s);
+ break;
+
+ case STATE_FINISHING:
+ if (s->fail_count > SV_FAIL_MAX) {
+ s->state = STATE_DEAD;
+ printf(":: %s died\n", s->name);
+ } else {
+ s->state = STATE_INACTIVE;
+ }
+ break;
+ case STATE_STARTING:
+ if (!signaled && return_code == 0) {
+ if (snprintf(path_buffer, PATH_MAX, "%s/%s/stop", service_dir, s->name) && stat(path_buffer, &stat_buffer) == 0 && stat_buffer.st_mode & S_IXUSR) {
+ s->state = STATE_ACTIVE_BACKGROUND;
+ } else if (snprintf(path_buffer, PATH_MAX, "%s/%s/pid", service_dir, s->name) && stat(path_buffer, &stat_buffer) == 0 && stat_buffer.st_mode & S_IRUSR) {
+ s->pid = parse_pid_file(s);
+ s->state = STATE_ACTIVE_PID;
+ } 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_DEAD:
+ case STATE_INACTIVE:
+ printf("warn: %s died but was set to inactive\n", s->name);
+ }
+}
diff --git a/src/script/fsvc-lsb.sh b/src/script/fsvc-lsb.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+fsvc=/sbin/fsvc
+fsvc_lsb=fsvc-lsb
+
+name=$(basename $0)
+
+if [ "$name" = "$fsvc_lsb" ]; then
+ echo "warning: calling fsvs-lsb without service"
+ echo " probabally this will cause an error but maybe it's intentionally"
+fi
+
+if [ -z "$1" ]; then
+ echo "error: missing <command>"
+ echo "usage: $0 <command>"
+ exit 1
+fi
+
+exec $fsvc $name $1
+\ No newline at end of file
diff --git a/src/script/modules-load.sh b/src/script/modules-load.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# modules-load [-n] [-v] - modules-load.d(5) compatible kernel module loader
+
+# Set the PATH variable to include /bin and /sbin
+export PATH=/bin:/sbin
+
+# Find modules to load based on modules-load parameters and configuration files
+find_modules() {
+ # Parameters passed as modules-load= or rd.modules-load= in kernel command line.
+ sed -nr 's/,/\n/g;s/(.* |^)(rd\.)?modules-load=([^ ]*).*/\3/p' /proc/cmdline
+
+ # Find files /{etc,run,usr/lib}/modules-load.d/*.conf in that order.
+ find -L /etc/modules-load.d /run/modules-load.d /usr/lib/modules-load.d \
+ -maxdepth 1 -name '*.conf' -printf '%p %P\n' 2>/dev/null |
+ # Load each basename only once.
+ sort -k2 -s | uniq -f1 | cut -d' ' -f1 |
+ # Read the files, output all non-empty, non-comment lines.
+ tr '\012' '\0' | xargs -0 -r grep -h -v -e '^[#;]' -e '^$'
+}
+
+# Load modules using modprobe
+load_modules() {
+ # Call modprobe on the list of modules
+ tr '\012' '\0' | xargs -0 -r modprobe -ab "$@"
+}
+
+# Find and load modules
+find_modules | load_modules
+\ No newline at end of file
diff --git a/src/script/poweroff.lnk b/src/script/poweroff.lnk
@@ -0,0 +1 @@
+halt
+\ No newline at end of file
diff --git a/src/script/reboot.lnk b/src/script/reboot.lnk
@@ -0,0 +1 @@
+halt
+\ No newline at end of file
diff --git a/src/script/shutdown.sh b/src/script/shutdown.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+# shutdown - shutdown(8) lookalike for runit
+
+single() {
+ fsvc chlevel single
+}
+
+abort() {
+ printf '%s\n' "$1" >&2
+ exit 1
+}
+
+usage() {
+ abort "Usage: ${0##*/} [-fF] [-kchPr] time [warning message]"
+}
+
+action=single
+
+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/script/zzz.sh b/src/script/zzz.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+# zzz - really simple suspend script
+
+# Define usage information
+USAGE="Usage: ${0##*/} [-nSzZR]
+ -n dry run (sleep for 5s instead of suspend/hibernate)
+ -S Low-power idle (ACPI S1)
+ -z suspend to RAM (ACPI S3) [DEFAULT for zzz(8)]
+ -Z hibernate to disk & power off (ACPI S4) [DEFAULT for ZZZ(8)]
+ -R hibernate to disk & reboot
+ -H hibernate to disk & suspend (aka suspend-hybrid)"
+
+# Define a function to print error messages and exit with error code 1
+fail() {
+ echo "${0##*/}: $*" >&2
+ exit 1
+}
+
+# Set default values for environment variables
+export ZZZ_MODE=suspend
+export ZZZ_HIBERNATE_MODE=platform
+
+# Check the name of the script to determine the default mode
+case "$0" in
+ *ZZZ) ZZZ_MODE=hibernate;;
+esac
+
+# Parse command-line options
+while getopts hnSzHRZ opt; do
+ case "$opt" in
+ n) ZZZ_MODE=noop;;
+ S) ZZZ_MODE=standby;;
+ z) ZZZ_MODE=suspend;;
+ Z) ZZZ_MODE=hibernate;;
+ R) ZZZ_MODE=hibernate; ZZZ_HIBERNATE_MODE=reboot;;
+ H) ZZZ_MODE=hibernate; ZZZ_HIBERNATE_MODE=suspend;;
+ [h?]) fail "$USAGE";;
+ esac
+done
+
+# Shift command-line arguments to skip processed options
+shift $((OPTIND-1))
+
+# Check if the selected mode is supported
+case "$ZZZ_MODE" in
+ suspend) grep -q mem /sys/power/state || fail "suspend not supported";;
+ hibernate) grep -q disk /sys/power/state || fail "hibernate not supported";;
+esac
+
+# Check if the current user has permission to sleep the system
+test -w /sys/power/state || fail "sleep permission denied"
+
+# Run the main logic in a subshell with a file lock to prevent multiple instances
+(
+ flock -n 9 || fail "another instance of zzz is running"
+
+ printf "Zzzz... "
+
+ # Run suspend hooks
+ for hook in /etc/zzz.d/suspend/*; do
+ [ -x "$hook" ] && "$hook"
+ done
+
+ # Sleep the system according to the selected mode
+ case "$ZZZ_MODE" in
+ standby) printf freeze >/sys/power/state || fail "standby failed";;
+ suspend) printf mem >/sys/power/state || fail "suspend failed";;
+ hibernate)
+ echo $ZZZ_HIBERNATE_MODE >/sys/power/disk
+ printf disk >/sys/power/state || fail "hibernate failed"
+ ;;
+ noop) sleep 5;;
+ esac
+
+ # Run resume hooks
+ for hook in /etc/zzz.d/resume/*; do
+ [ -x "$hook" ] && "$hook"
+ done
+
+ echo "yawn."
+) 9</sys/power
diff --git a/src/serialize.c b/src/serialize.c
@@ -0,0 +1,77 @@
+#include "config.h"
+#include "service.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+
+void service_store(service_t* s, uint8_t* buffer) {
+ buffer[0] = (s->state);
+ buffer[1] = (s->pid >> 0) & 0xff;
+ buffer[2] = (s->pid >> 8) & 0xff;
+ buffer[3] = (s->pid >> 16) & 0xff;
+ buffer[4] = (s->pid >> 24) & 0xff;
+ buffer[5] = (s->status_change >> 0) & 0xff;
+ buffer[6] = (s->status_change >> 8) & 0xff;
+ buffer[7] = (s->status_change >> 16) & 0xff;
+ buffer[8] = (s->status_change >> 24) & 0xff;
+ buffer[9] = (s->status_change >> 32) & 0xff;
+ buffer[10] = (s->status_change >> 40) & 0xff;
+ buffer[11] = (s->status_change >> 48) & 0xff;
+ buffer[12] = (s->status_change >> 56) & 0xff;
+ buffer[13] = (s->fail_count);
+ buffer[14] = (s->return_code);
+ buffer[15] = (s->last_exit << 0) |
+ (s->restart << 2) |
+ (s->is_log_service << 4) |
+ ((s->log_service != NULL) << 5);
+
+ // +--+--+--+--+--+--+--+--+
+ // |FS|ZO|LS|AU|PS|DE|SG|RS|
+ // +--+--+--+--+--+--+--+--+
+ // RS = restart if died
+ // SG = is signaled
+ // DE = is dead
+ // PS = paused
+ // AU = autostart
+ // LS = has log service
+ // ZM = is zombie (cannot die)
+ // FS = has finish script
+
+ // status file
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // | PID | FIN_PID | STATUS_CHANGE |RC|FC|FL|
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // PID = pid of the service (0 if inactive)
+ // FIN_PID = pid of the finish script (0 if inactive)
+ // STATUS_CHANGE = unix timestamp of the last time the service changed
+ // RC = last return code
+ // FC = count of failture in a row
+ // FL = flags
+}
+
+void service_load(service_t* s, const uint8_t* buffer) {
+ s->state = buffer[0];
+ s->pid = ((uint32_t) buffer[1] << 0) |
+ ((uint32_t) buffer[2] << 8) |
+ ((uint32_t) buffer[3] << 16) |
+ ((uint32_t) buffer[4] << 24);
+ s->status_change = ((uint64_t) buffer[5] << 0) |
+ ((uint64_t) buffer[6] << 8) |
+ ((uint64_t) buffer[7] << 16) |
+ ((uint64_t) buffer[8] << 24) |
+ ((uint64_t) buffer[9] << 32) |
+ ((uint64_t) buffer[10] << 40) |
+ ((uint64_t) buffer[11] << 48) |
+ ((uint64_t) buffer[12] << 56);
+ s->fail_count = buffer[13];
+ s->return_code = buffer[14];
+ s->last_exit = buffer[15] & 0x03;
+ s->restart = (buffer[15] >> 2) & 0x01;
+ s->is_log_service = (buffer[15] >> 4) & 0x01;
+ s->log_service = (buffer[15] >> 5) ? (void*) 1 : NULL;
+}
+\ No newline at end of file
diff --git a/src/service.c b/src/service.c
@@ -0,0 +1,97 @@
+// daemon manager
+
+#include "service.h"
+
+#include "config.h"
+#include "pattern.h"
+#include "util.h"
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+service_t services[SV_SERVICE_MAX];
+int services_size = 0;
+string runlevel;
+string service_dir;
+int control_socket;
+int null_fd;
+bool verbose = false;
+dependency_t depends[SV_DEPENDENCY_MAX];
+int depends_size;
+
+
+service_t* service_get(string name) {
+ for (int i = 0; i < services_size; i++) {
+ if (streq(services[i].name, name))
+ return &services[i];
+ }
+ return NULL;
+}
+
+int service_pattern(string name, service_t** dest, int dest_max) {
+ int size = 0;
+ for (int i = 0; i < services_size && size < dest_max; i++) {
+ if (pattern_test(name, services[i].name))
+ dest[size++] = &services[i];
+ }
+ return size;
+}
+
+int service_refresh(service_t** added) {
+ DIR* dp;
+ struct dirent* ep;
+ dp = opendir(service_dir);
+ if (dp == NULL) {
+ print_error("cannot open directory %s", service_dir);
+ return -1;
+ }
+
+ struct stat stat_str;
+ char path_buffer[PATH_MAX];
+ int added_len = 0;
+
+ for (int i = 0; i < services_size; i++) {
+ service_t* s = &services[i];
+ snprintf(path_buffer, PATH_MAX, "%s/%s", service_dir, s->name);
+ if (stat(path_buffer, &stat_str) == -1 || !S_ISDIR(stat_str.st_mode)) {
+ if (s->pid)
+ kill(s->pid, SIGKILL);
+ if (i < services_size - 1) {
+ memmove(services + i, services + i + 1, services_size - i - 1);
+ i--;
+ }
+ services_size--;
+ }
+ }
+
+ while ((ep = readdir(dp)) != NULL) {
+ if (ep->d_name[0] == '.')
+ continue;
+ snprintf(path_buffer, PATH_MAX, "%s/%s", service_dir, ep->d_name);
+ if (stat(path_buffer, &stat_str) == -1 || !S_ISDIR(stat_str.st_mode))
+ continue;
+
+ bool changed;
+ service_t* s = service_register(ep->d_name, false, &changed);
+ if (changed && added != NULL)
+ added[added_len++] = s;
+ }
+
+ closedir(dp);
+
+ depends_size = 0;
+ for (int i = 0; i < services_size; i++)
+ service_update_dependency(&services[i]);
+
+ return added_len;
+}
diff --git a/src/socket_handler.c b/src/socket_handler.c
@@ -0,0 +1,61 @@
+#include "service.h"
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+
+void service_handle_socket(int client) {
+ char command[2] = { 0, 0 };
+ char service_name[SV_NAME_MAX];
+ read(client, command, sizeof(command));
+
+ printf("command: %c\n", command[0]);
+
+ ssize_t service_len = readstr(client, service_name);
+
+ printf("command: %c-%02x with service '%s'\n", command[0], command[1], service_name);
+
+ int res = 0;
+ int res_off = 0;
+ service_t* response[128];
+ service_t* request[128];
+
+ if (service_len > 0) {
+ int req_size = service_pattern(service_name, request, 128);
+ if (req_size == 0) {
+ res = -EBADSV;
+ goto cleanup;
+ }
+ for (int i = 0; i < req_size; i++) {
+ res = service_handle_command(request[i], command[0], command[1], response + res_off);
+ if (res < 0)
+ goto cleanup;
+ res_off += res;
+ }
+ } else {
+ res = service_handle_command(NULL, command[0], command[1], response);
+ if (res < 0)
+ goto cleanup;
+
+ res_off = res;
+ }
+
+
+cleanup:
+ if (res < 0) {
+ res *= -1;
+ write(client, &res, 1);
+ goto cleanup;
+ } else {
+ write(client, "", 1);
+ uint8_t service_buffer[SV_SERIAL_LEN];
+
+ for (int i = 0; i < res_off; i++) {
+ service_store(response[i], service_buffer);
+ writestr(client, response[i]->name);
+ write(client, service_buffer, sizeof(service_buffer));
+ }
+ write(client, "", 1);
+ }
+ close(client);
+}
+\ No newline at end of file
diff --git a/src/stage.c b/src/stage.c
@@ -0,0 +1,202 @@
+#include "service.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+
+void sigblock_all(bool unblock) {
+ sigset_t ss;
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGALRM);
+ sigaddset(&ss, SIGCHLD);
+ sigaddset(&ss, SIGCONT);
+ sigaddset(&ss, SIGHUP);
+ sigaddset(&ss, SIGINT);
+ sigaddset(&ss, SIGPIPE);
+ sigaddset(&ss, SIGTERM);
+ sigprocmask(unblock, &ss, NULL);
+}
+
+void handle_stage1() {
+ int pid, ttyfd, exitstat;
+ sigset_t ss;
+ while ((pid = fork()) == -1) {
+ print_error("unable to fork for stage1");
+ sleep(5);
+ }
+ if (pid == 0) {
+ /* child */
+
+ /* stage 1 gets full control of console */
+ if ((ttyfd = open("/dev/console", O_RDWR)) == -1) {
+ print_error("unable to open /dev/console");
+ } else {
+ ioctl(ttyfd, TIOCSCTTY, NULL); // make the controlling process
+ dup2(ttyfd, 0);
+ if (ttyfd > 2) close(ttyfd);
+ }
+
+ sigblock_all(true);
+
+
+ struct sigaction sigact = { 0 };
+ sigact.sa_handler = SIG_DFL;
+ sigaction(SIGCLD, &sigact, NULL);
+ sigaction(SIGINT, &sigact, NULL);
+
+ sigact.sa_handler = SIG_IGN;
+ sigaction(SIGCONT, &sigact, NULL);
+
+ printf("enter stage1\n");
+ execlp(SV_START_EXEC, SV_START_EXEC, NULL);
+ print_error("unable to exec stage1");
+ _exit(1);
+ }
+ bool dont_wait = false;
+ for (;;) {
+ int child;
+ int sig = 0;
+
+ if (!dont_wait) {
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGCHLD);
+ sigaddset(&ss, SIGCONT);
+ sigaddset(&ss, SIGINT);
+
+ sigwait(&ss, &sig);
+ }
+ dont_wait = false;
+
+ do {
+ child = waitpid(-1, &exitstat, WNOHANG);
+ } while (child > 0 && child != pid);
+
+ if (child == -1) {
+ print_error("waitpid failed, pausing");
+ sleep(5);
+ }
+
+ /* reget stderr */
+ if ((ttyfd = open("/dev/console", O_WRONLY)) != -1) {
+ dup2(ttyfd, 1);
+ dup2(ttyfd, 2);
+ if (ttyfd > 2)
+ close(ttyfd);
+ }
+
+ if (child == pid) {
+ if (!WIFEXITED(exitstat) || WEXITSTATUS(exitstat) != 0) {
+ printf("child failed\n");
+ if (WIFSIGNALED(exitstat)) {
+ /* this is stage 1 */
+ printf("leave stage 1\n");
+ printf("skipping stage 2\n");
+ daemon_running = false;
+ break;
+ }
+ }
+ printf("leave stage1\n");
+ break;
+ }
+ if (child != 0) {
+ /* collect terminated children */
+
+ dont_wait = true;
+ continue;
+ }
+
+ /* sig? */
+ if (sig != SIGCONT && sig != SIGINT) {
+ continue;
+ }
+
+ printf("signals only work in stage 2\n");
+ }
+}
+
+
+void handle_stage3() {
+ int pid, ttyfd, exitstat;
+ sigset_t ss;
+ while ((pid = fork()) == -1) {
+ print_error("unable to fork for state3");
+ sleep(5);
+ }
+ if (pid == 0) {
+ /* child */
+
+ setsid();
+
+ sigblock_all(true);
+
+
+ struct sigaction sigact = { 0 };
+ sigact.sa_handler = SIG_DFL;
+ sigaction(SIGCLD, &sigact, NULL);
+ sigaction(SIGINT, &sigact, NULL);
+
+ sigact.sa_handler = SIG_IGN;
+ sigaction(SIGCONT, &sigact, NULL);
+
+ printf("enter stage3\n");
+ execlp(SV_STOP_EXEC, SV_STOP_EXEC, NULL);
+ print_error("unable to exec stage3");
+ _exit(1);
+ }
+ bool dont_wait = false;
+ for (;;) {
+ int child;
+ int sig;
+
+ if (!dont_wait) {
+ sigemptyset(&ss);
+ sigaddset(&ss, SIGCHLD);
+ sigaddset(&ss, SIGCONT);
+ sigaddset(&ss, SIGINT);
+
+ sigwait(&ss, &sig);
+ }
+ dont_wait = false;
+
+ do {
+ child = waitpid(-1, &exitstat, WNOHANG);
+ } while (child > 0 && child != pid);
+
+ if (child == -1) {
+ print_error("waitpid failed, pausing");
+ sleep(5);
+ }
+
+ /* reget stderr */
+ if ((ttyfd = open("/dev/console", O_WRONLY)) != -1) {
+ dup2(ttyfd, 1);
+ dup2(ttyfd, 2);
+ if (ttyfd > 2)
+ close(ttyfd);
+ }
+
+ if (child == pid) {
+ if (!WIFEXITED(exitstat) || WEXITSTATUS(exitstat) != 0) {
+ printf("child failed\n");
+ }
+ printf("leave stage: stage3\n");
+ break;
+ }
+ if (child != 0) {
+ /* collect terminated children */
+ dont_wait = true;
+ continue;
+ }
+
+ /* sig? */
+ if (sig != SIGCONT && sig != SIGINT) {
+ continue;
+ }
+ printf("signals only work in stage 2\n");
+ }
+}
diff --git a/src/start.c b/src/start.c
@@ -0,0 +1,160 @@
+#include "config.h"
+#include "config_parser.h"
+#include "service.h"
+#include "user_group.h"
+#include "util.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+static void set_pipes(service_t* s) {
+ struct stat estat;
+
+ 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("log", &estat) == 0 && estat.st_mode & 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 (stat("nolog", &estat) == 0 && S_ISREG(estat.st_mode)) {
+ dup2(null_fd, STDIN_FILENO);
+ dup2(null_fd, STDOUT_FILENO);
+ dup2(null_fd, STDERR_FILENO);
+ } else {
+ char service_log[PATH_MAX];
+ snprintf(service_log, PATH_MAX, "%s/%s-%s.log", SV_LOG_DIR, s->name, runlevel);
+
+ int log_fd;
+ 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);
+ }
+}
+
+static void set_user() {
+ char buffer[1024];
+ int user_file;
+ if ((user_file = open("user", O_RDONLY)) != -1) {
+ ssize_t n;
+ if ((n = read(user_file, buffer, sizeof(buffer))) == -1) {
+ printf("failed reading user\n");
+ close(user_file);
+ return;
+ }
+ buffer[n] = '\0';
+
+ uid_t uid;
+ gid_t gids[60];
+ if ((n = parse_ugid(buffer, &uid, gids)) <= 0) {
+ printf("error parsing user\n");
+ close(user_file);
+ return;
+ }
+
+ setgroups(n, gids);
+ setgid(gids[0]);
+ setuid(uid);
+
+ close(user_file);
+ }
+}
+
+
+void service_start(service_t* s, bool* changed) {
+ if (s->state != STATE_INACTIVE)
+ return;
+
+ if (changed)
+ *changed = true;
+
+ printf(":: starting %s \n", s->name);
+ for (int i = 0; i < depends_size; i++) {
+ if (depends[i].service == s)
+ service_start(depends[i].depends, NULL);
+ }
+
+ for (int i = 0; i < depends_size; i++) {
+ if (depends[i].service == s)
+ service_start(depends[i].depends, NULL);
+ }
+
+ char path_buf[PATH_MAX];
+
+ struct stat estat;
+ if (sprintf(path_buf, "%s/%s/run", service_dir, s->name) && stat(path_buf, &estat) == 0 && estat.st_mode & S_IXUSR) {
+ s->state = STATE_ACTIVE_FOREGROUND;
+ } else if (sprintf(path_buf, "%s/%s/start", service_dir, s->name) && stat(path_buf, &estat) == 0 && estat.st_mode & S_IXUSR) {
+ s->state = STATE_STARTING;
+ } else if (sprintf(path_buf, "%s/%s/depends", service_dir, s->name) && stat(path_buf, &estat) == 0 && estat.st_mode & S_IREAD) {
+ s->state = STATE_ACTIVE_DUMMY;
+ } else {
+ printf("error in %s: `run`, `start` or `depends` not found\n", s->name);
+ }
+
+ if (s->state != STATE_ACTIVE_DUMMY) {
+ if ((s->pid = fork()) == -1) {
+ print_error("cannot fork process");
+ exit(1);
+ } else if (s->pid == 0) { // child
+ if (setsid() == -1)
+ print_error("cannot setsid");
+
+ char dir_path[PATH_MAX];
+ snprintf(dir_path, PATH_MAX, "%s/%s", service_dir, s->name);
+ if (chdir(dir_path) == -1)
+ print_error("chdir failed");
+
+ set_pipes(s);
+
+ char args[SV_ARGUMENTS_MAX][SV_PARAM_FILE_LINE_MAX];
+ char* argv[SV_ARGUMENTS_MAX];
+ for (int i = 0; i < SV_ARGUMENTS_MAX; i++)
+ argv[i] = args[i];
+ char envs[SV_ENV_MAX][SV_ENV_FILE_LINE_MAX];
+ char* envv[SV_ENV_MAX];
+ for (int i = 0; i < SV_ENV_MAX; i++)
+ envv[i] = envs[i];
+
+ parse_param_file(s, argv);
+ parse_env_file(envv);
+
+ set_user();
+
+ if (s->state == STATE_STARTING) {
+ execve("./start", argv, envv);
+ } else {
+ execve("./run", argv, envv);
+ }
+ print_error("cannot execute service");
+ _exit(1);
+ }
+ }
+ s->status_change = time(NULL);
+ printf(":: started %s \n", s->name);
+}
diff --git a/src/stop.c b/src/stop.c
@@ -0,0 +1,62 @@
+#include "service.h"
+
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+void service_stop(service_t* s, bool* changed) {
+ char path_buffer[PATH_MAX];
+
+ switch (s->state) {
+ case STATE_ACTIVE_DUMMY:
+ service_check_state(s, false, 0);
+ if (changed)
+ *changed = true;
+ break;
+ case STATE_ACTIVE_FOREGROUND:
+ case STATE_ACTIVE_PID:
+ kill(s->pid, SIGTERM);
+ if (changed)
+ *changed = true;
+ break;
+ case STATE_ACTIVE_BACKGROUND:
+ snprintf(path_buffer, PATH_MAX, "%s/%s/stop", service_dir, s->name);
+
+ s->state = STATE_STOPPING;
+ if ((s->pid = fork()) == -1) {
+ print_error("cannot fork process");
+ } else if (s->pid == 0) {
+ dup2(null_fd, STDIN_FILENO);
+ dup2(null_fd, STDOUT_FILENO);
+ dup2(null_fd, STDERR_FILENO);
+
+ execlp(path_buffer, path_buffer, NULL);
+ print_error("cannot execute stop process");
+ _exit(1);
+ }
+ if (changed)
+ *changed = true;
+ break;
+ case STATE_STARTING:
+ case STATE_STOPPING:
+ case STATE_FINISHING:
+ kill(s->pid, SIGTERM);
+ if (changed)
+ *changed = true;
+
+ case STATE_INACTIVE:
+ case STATE_DEAD:
+ break;
+ }
+}
+
+void service_send(service_t* s, int signal) {
+ if (!s->pid)
+ return;
+
+ if (s->state == STATE_ACTIVE_FOREGROUND || s->state == STATE_ACTIVE_PID)
+ kill(s->pid, signal);
+}
diff --git a/src/supervise.c b/src/supervise.c
@@ -0,0 +1,218 @@
+#include "config.h"
+#include "config_parser.h"
+#include "service.h"
+#include "util.h"
+
+#include <asm-generic/errno.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdint.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>
+
+
+bool daemon_running = true;
+
+
+static void signal_child(int unused) {
+ (void) unused;
+
+ int status;
+ pid_t died_pid;
+ service_t* s = NULL;
+
+ if ((died_pid = wait(&status)) == -1) {
+ print_error("cannot wait for process");
+ 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_check_state(s, WIFSIGNALED(status), WIFSIGNALED(status) ? WTERMSIG(status) : WEXITSTATUS(status));
+}
+
+static bool is_dependency(service_t* d) {
+ service_t* s;
+ for (int i = 0; i < depends_size; i++) {
+ s = depends[i].service;
+ if (depends[i].depends == d && (s->state != STATE_INACTIVE || s->restart || s->restart_once || is_dependency(s)))
+ return true;
+ }
+ return false;
+}
+
+static void check_deaths() {
+ service_t* s;
+ for (int i = 0; i < services_size; i++) {
+ s = &services[i];
+ if (s->state == STATE_ACTIVE_PID) {
+ if (kill(s->pid, 0) == -1 && errno == ESRCH)
+ service_check_state(s, false, 0);
+ }
+ }
+}
+
+static void check_services() {
+ service_t* s;
+ for (int i = 0; i < services_size; i++) {
+ s = &services[i];
+ if (s->state == STATE_DEAD)
+ continue;
+ if (s->restart || s->restart_once || is_dependency(s)) {
+ if (s->state == STATE_INACTIVE) {
+ service_start(s, NULL);
+ }
+ } else {
+ if (s->state != STATE_INACTIVE) {
+ service_stop(s, NULL);
+ }
+ }
+ }
+}
+
+int service_supervise(string service_dir_, string runlevel_, bool force_socket) {
+ struct sigaction sigact = { 0 };
+ sigact.sa_handler = signal_child;
+ sigaction(SIGCHLD, &sigact, NULL);
+ sigact.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &sigact, NULL);
+
+ runlevel = runlevel_;
+ service_dir = service_dir_;
+
+ setenv("SERVICE_RUNLEVEL", runlevel, true);
+
+ char socket_path[PATH_MAX];
+ snprintf(socket_path, PATH_MAX, SV_CONTROL_SOCKET, runlevel);
+
+ if ((null_fd = open("/dev/null", O_RDWR)) == -1) {
+ print_error("cannot open /dev/null");
+ null_fd = 1;
+ }
+
+ printf(":: starting services on '%s'\n", runlevel);
+
+ if (service_refresh(NULL) < 0)
+ return 1;
+
+ printf(":: started services\n");
+
+ struct stat socket_stat;
+ if (force_socket) {
+ if (unlink(socket_path) == -1 && errno != ENOENT) {
+ print_error("cannot unlink socket");
+ }
+ } else if (stat(socket_path, &socket_stat) != -1 && S_ISREG(socket_stat.st_mode)) {
+ printf("error: %s exist and is locking supervision,\nrun this program with '-f' flag if you are sure no other superviser is running.", socket_path);
+ return 1;
+ }
+ // create socket
+ if ((control_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ print_error("cannot create socket");
+ return 1;
+ }
+
+ // bind socket to address
+ struct sockaddr_un addr = { 0 };
+ addr.sun_family = AF_UNIX;
+ strcpy(addr.sun_path, socket_path);
+ if (bind(control_socket, (struct sockaddr*) &addr, sizeof(addr)) == -1) {
+ print_error("cannot bind %s to socket", socket_path);
+ return 1;
+ }
+
+ // listen for connections
+ if (listen(control_socket, 5) == -1) {
+ print_error("cannot listen to control socket");
+ return 1;
+ }
+
+ int sockflags = fcntl(control_socket, F_GETFL, 0);
+ if (sockflags == -1) {
+ print_warning("fcntl-getflags on control-socket failed");
+ } else {
+ if (fcntl(control_socket, F_SETFL, sockflags | O_NONBLOCK) == -1)
+ print_warning("fcntl-setflags on control-socket failed");
+ }
+
+ // accept connections and handle requests
+ int client_fd;
+ while (daemon_running) {
+ check_deaths();
+ check_services();
+
+ if ((client_fd = accept(control_socket, NULL, NULL)) == -1) {
+ if (errno == EWOULDBLOCK) {
+ sleep(SV_ACCEPT_INTERVAL);
+ } else {
+ print_error("cannot accept client from control-socket");
+ return 1;
+ }
+ } else {
+ service_handle_socket(client_fd);
+ }
+ }
+
+ close(control_socket);
+
+ if (unlink(socket_path) == -1 && errno != ENOENT) {
+ print_error("cannot unlink socket");
+ }
+
+ printf(":: terminating\n");
+
+ service_t* s;
+ for (int i = 0; i < services_size; i++) {
+ s = &services[i];
+ service_stop(s, NULL);
+ }
+
+ time_t start = time(NULL);
+ int running;
+ do {
+ sleep(1); // sleep for one second
+ running = 0;
+ for (int i = 0; i < services_size; i++) {
+ if (services[i].state != STATE_INACTIVE)
+ running++;
+ }
+ printf(":: %d running...\r", running);
+ } while (running > 0 && (time(NULL) - start) < SV_STOP_TIMEOUT);
+
+ printf("\n");
+
+ for (int i = 0; i < services_size; i++) {
+ if (services[i].pid) {
+ printf(":: killing %s\n", services[i].name);
+ service_send(&services[i], SIGKILL);
+ }
+ }
+
+ printf(":: all services stopped\n");
+
+ signal(SIGPIPE, SIG_DFL);
+ signal(SIGCHLD, SIG_DFL);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGCONT, SIG_DFL);
+ return 0;
+}
diff --git a/src/user_group.c b/src/user_group.c
@@ -0,0 +1,77 @@
+#include "user_group.h"
+
+#include <grp.h>
+#include <pwd.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+/* uid:gid[:gid[:gid]...] */
+static int parse_ugid_num(char* str, uid_t* uid, gid_t* gids) {
+ int i;
+
+ char* end;
+ *uid = strtoul(str, &end, 10);
+
+ if (*end != ':')
+ return -1;
+
+ str = end + 1;
+ for (i = 0; i < 60; ++i, ++str) {
+ gids[i++] = strtoul(str, &end, 10);
+
+ if (*end != ':')
+ break;
+
+ str = end + 1;
+ }
+
+ if (*str != '\0')
+ return -1;
+
+ return i;
+}
+
+int parse_ugid(char* str, uid_t* uid, gid_t* gids) {
+ struct passwd* pwd;
+ struct group* gr;
+ char* end;
+ char* groupstr = NULL;
+ int gid_size = 0;
+
+ if (str[0] == ':')
+ return (parse_ugid_num(str + 1, uid, gids));
+
+ if ((end = strchr(str, ':')) != NULL) {
+ end[0] = '\0';
+ groupstr = end + 1;
+ }
+
+ if ((pwd = getpwnam(str)) == NULL) {
+ return -1;
+ }
+ *uid = pwd->pw_uid;
+
+ if (groupstr == NULL) {
+ gids[0] = pwd->pw_gid;
+ return 1;
+ }
+
+ char* next = groupstr;
+
+ while (next && gid_size < 60) {
+ groupstr = next;
+ if ((end = strchr(groupstr, ':')) != NULL) {
+ end[0] = '\0';
+ next = end + 1;
+ } else {
+ next = NULL;
+ }
+ if ((gr = getgrnam(groupstr)) == NULL)
+ return -1;
+
+ gids[gid_size++] = gr->gr_gid;
+ }
+
+ return gid_size;
+}
diff --git a/src/util.c b/src/util.c
@@ -0,0 +1,38 @@
+#include "util.h"
+
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+
+ssize_t dgetline(int fd, char* line, size_t line_buffer) {
+ ssize_t line_size = 0;
+ ssize_t rc;
+ char c;
+ while (line_size < (ssize_t) line_buffer - 1 && (rc = read(fd, &c, 1)) == 1) {
+ if (c == '\r')
+ continue;
+ if (c == '\n')
+ break;
+ line[line_size++] = c;
+ }
+ line[line_size] = '\0';
+ if (rc == -1 && line_size == 0)
+ return -1;
+ return line_size;
+}
+
+ssize_t readstr(int fd, char* str) {
+ ssize_t len = 0;
+ int rc;
+
+ while ((rc = read(fd, &str[len], 1)) == 1 && str[len] != '\0')
+ len++;
+
+ str[len] = '\0';
+ return rc == -1 ? -1 : len;
+}
+
+ssize_t writestr(int fd, string str) {
+ return write(fd, str, strlen(str) + 1);
+}
+\ No newline at end of file
diff --git a/usr/share/fiss/crypt.awk b/usr/share/fiss/crypt.awk
@@ -0,0 +1,105 @@
+/^#/ || /^$/ { next }
+NF>4 { print "a valid crypttab has max 4 cols not " NF >"/dev/stderr"; next }
+{
+ # decode the src variants
+ split($2, o_src, "=")
+ if (o_src[1] == "UUID" || o_src[1] == "PARTUUID") ("blkid -l -o device -t " $2) | getline src;
+ else src=o_src[1];
+
+ # no password or none is given, ask fo it
+ if ( NF == 2 ) {
+ ccmd="cryptsetup luksOpen " src " " $1;
+ system(ccmd);
+ ccmd="";
+ }
+ else if (NF == 3 ) {
+ dest=$1
+ key=$3
+ split($3, po, "=");
+ if ( po[1] == "none") ccmd="cryptsetup luksOpen " src " " dest;
+ else ccmd="cryptsetup luksOpen -d " key " " src" " dest;
+ system(ccmd);
+ ccmd="";
+ }
+ else {
+ # the option field is not empty parse the options
+ dest=$1
+ key=$3
+ split($4, opts, ",");
+ commonopts="";
+ swapopts="";
+ luksopts="";
+ for(i in opts) {
+ split(opts[i], para, "=");
+ par=para[1];
+ val=para[2];
+ if ( par == "readonly" || par == "read-only") commonopts=commonopts "-r ";
+ else if ( par == "discard" ) commonopts=commonopts "--allow-discards ";
+ else if ( par == "no-read-workqueue" ) commonopts=commonopts "--perf-no_read_workqueue ";
+ else if ( par == "no-write-workqueue" ) commonopts=commonopts "--perf-no_write_workqueue ";
+ else if ( par == "tries" ) commonopts=commonopts "-T " val " ";
+ else if ( par == "swap" ) makeswap="y";
+ else if ( par == "cipher" ) swapopts=swapopts "-c " val " ";
+ else if ( par == "size" ) swapopts=swapopts "-s " val " ";
+ else if ( par == "hash" ) swapopts=swapopts "-h " val " ";
+ else if ( par == "offset" ) swapopts=swapopts "-o " val " ";
+ else if ( par == "skip" ) swapopts=swapopts "-p " val " ";
+ else if ( par == "verify" ) swapopts=swapopts "-y ";
+ #else if ( par == "noauto" )
+ #else if ( par == "nofail" )
+ #else if ( par == "plain" )
+ #else if ( par == "timeout" )
+ #else if ( par == "tmp" )
+ else if ( par == "luks" ) use_luks="y";
+ else if ( par == "keyscript" ) {use_keyscript="y"; keyscript=val;}
+ else if ( par == "keyslot" || par == "key-slot" ) luksopts=luksopts "-S " val " ";
+ else if ( par == "keyfile-size" ) luksopts=luksopts "-l " val " ";
+ else if ( par == "keyfile-offset" ) luksopts=luksopts "--keyfile-offset=" val " ";
+ else if ( par == "header" ) luksopts=luksopts "--header=" val " ";
+ else {
+ print "option: " par " not supported " >"/dev/stderr";
+ makeswap="";
+ use_luks="";
+ use_keyscript="";
+ next;
+ }
+ }
+ if ( makeswap == "y" && use_luks != "y" ) {
+ ccmd="cryptsetup " swapopts commonopts "-d " key " create " dest " " src;
+ ccmd_2="mkswap /dev/mapper/" dest;
+ makeswap="";
+ use_luks="";
+ use_keyscript="";
+ system(ccmd);
+ system(ccmd_2);
+ ccmd="";
+ ccmd_2="";
+ next;
+ }
+ if ( use_luks == "y" && makeswap != "y" ){
+ if ( use_keyscript == "y") {
+ ccmd=keyscript " | cryptsetup " luksopts commonopts "luksOpen -d - " src " " dest;
+ use_keyscript="";
+ }
+ else {
+ if ( key == "none" ){
+ ccmd="cryptsetup " luksopts commonopts "luksOpen " src " " dest;
+ }
+ else {
+ ccmd="cryptsetup " luksopts commonopts "luksOpen -d " key " " src " " dest;
+ }
+ }
+ }
+ else {
+ print "use swap OR luks as option" >"/dev/stderr";
+ ccmd="";
+ }
+ makeswap="";
+ use_luks="";
+ use_keyscript="";
+ if ( ccmd != ""){
+ system(ccmd);
+ ccmd=""
+ }
+ }
+}
diff --git a/usr/share/fiss/utils b/usr/share/fiss/utils
@@ -0,0 +1,54 @@
+# *-*-shell-*-*
+
+msg() {
+ # bold
+ printf "\033[1m=> $@\033[m\n"
+}
+
+msg_ok() {
+ # bold/green
+ printf "\033[1m\033[32m OK\033[m\n"
+}
+
+msg_error() {
+ # bold/red
+ printf "\033[1m\033[31mERROR: $@\033[m\n"
+}
+
+msg_warn() {
+ # bold/yellow
+ printf "\033[1m\033[33mWARNING: $@\033[m\n"
+}
+
+emergency_shell() {
+ echo
+ echo "Cannot continue due to errors above, starting emergency shell."
+ echo "When ready type exit to continue booting."
+ /bin/sh -l
+}
+
+detect_virt() {
+ # Detect LXC (and other) containers
+ [ -z "${container+x}" ] || export VIRTUALIZATION=1
+}
+
+deactivate_vgs() {
+ _group=${1:-All}
+ if [ -x /sbin/vgchange -o -x /bin/vgchange ]; then
+ vgs=$(vgs|wc -l)
+ if [ $vgs -gt 0 ]; then
+ msg "Deactivating $_group LVM Volume Groups..."
+ vgchange -an
+ fi
+ fi
+}
+
+deactivate_crypt() {
+ if [ -x /sbin/dmsetup -o -x /bin/dmsetup ]; then
+ msg "Deactivating Crypt Volumes"
+ for v in $(dmsetup ls --target crypt --exec "dmsetup info -c --noheadings -o open,name"); do
+ [ ${v%%:*} = "0" ] && cryptsetup close ${v##*:}
+ done
+ deactivate_vgs "Crypt"
+ fi
+}