fiss

Friedel's Initialization and Service Supervision
Log | Files | Refs | LICENSE

commit ac95deae520297b3aaafee07b14d42b6c9bf837c
Author: Friedel Schon <[email protected]>
Date:   Mon, 10 Apr 2023 10:36:50 +0200

first commit

Diffstat:
AMakefile | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adocs/command.txt | 15+++++++++++++++
Adocs/control.txt | 14++++++++++++++
Adocs/files.txt | 0
Adocs/readme.txt | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adocs/service-dir.txt | 30++++++++++++++++++++++++++++++
Adocs/service.txt | 8++++++++
Adocs/svstatus.txt | 14++++++++++++++
Adocs/usage.txt | 25+++++++++++++++++++++++++
Aetc/dracut/10-runit-void.conf | 3+++
Aetc/fiss/service/agetty-console/finish | 2++
Aetc/fiss/service/agetty-console/params | 5+++++
Aetc/fiss/service/agetty-console/run | 2++
Aetc/fiss/service/agetty-generic/finish | 3+++
Aetc/fiss/service/agetty-generic/run | 2++
Aetc/fiss/service/agetty-hvc0/finish | 2++
Aetc/fiss/service/agetty-hvc0/params | 4++++
Aetc/fiss/service/agetty-hvc0/run | 2++
Aetc/fiss/service/agetty-hvsi0/finish | 2++
Aetc/fiss/service/agetty-hvsi0/params | 5+++++
Aetc/fiss/service/agetty-hvsi0/run | 2++
Aetc/fiss/service/agetty-serial/finish | 2++
Aetc/fiss/service/agetty-serial/params | 5+++++
Aetc/fiss/service/agetty-serial/run | 2++
Aetc/fiss/service/agetty-tty1/finish | 2++
Aetc/fiss/service/agetty-tty1/params | 5+++++
Aetc/fiss/service/agetty-tty1/run | 2++
Aetc/fiss/service/agetty-tty1/up-default | 0
Aetc/fiss/service/agetty-tty2/finish | 2++
Aetc/fiss/service/agetty-tty2/params | 4++++
Aetc/fiss/service/agetty-tty2/run | 2++
Aetc/fiss/service/agetty-tty2/up-default | 0
Aetc/fiss/service/agetty-tty3/finish | 2++
Aetc/fiss/service/agetty-tty3/params | 4++++
Aetc/fiss/service/agetty-tty3/run | 2++
Aetc/fiss/service/agetty-tty3/up-default | 0
Aetc/fiss/service/agetty-tty4/finish | 2++
Aetc/fiss/service/agetty-tty4/params | 4++++
Aetc/fiss/service/agetty-tty4/run | 2++
Aetc/fiss/service/agetty-tty4/up-default | 0
Aetc/fiss/service/agetty-tty5/finish | 2++
Aetc/fiss/service/agetty-tty5/params | 4++++
Aetc/fiss/service/agetty-tty5/run | 2++
Aetc/fiss/service/agetty-tty5/up-default | 0
Aetc/fiss/service/agetty-tty6/finish | 2++
Aetc/fiss/service/agetty-tty6/params | 4++++
Aetc/fiss/service/agetty-tty6/run | 2++
Aetc/fiss/service/agetty-tty6/up-default | 0
Aetc/fiss/service/agetty-ttyAMA0/finish | 2++
Aetc/fiss/service/agetty-ttyAMA0/params | 5+++++
Aetc/fiss/service/agetty-ttyAMA0/run | 2++
Aetc/fiss/service/agetty-ttyS0/finish | 2++
Aetc/fiss/service/agetty-ttyS0/params | 5+++++
Aetc/fiss/service/agetty-ttyS0/run | 2++
Aetc/fiss/service/agetty-ttyUSB0/finish | 2++
Aetc/fiss/service/agetty-ttyUSB0/params | 5+++++
Aetc/fiss/service/agetty-ttyUSB0/run | 2++
Aetc/fiss/service/sulogin/run | 5+++++
Aetc/fiss/start | 31+++++++++++++++++++++++++++++++
Aetc/fiss/start.d/00-pseudofs.sh | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Aetc/fiss/start.d/01-static-devnodes.sh | 6++++++
Aetc/fiss/start.d/02-kmods.sh | 9+++++++++
Aetc/fiss/start.d/02-udev.sh | 19+++++++++++++++++++
Aetc/fiss/start.d/03-console-setup.sh | 26++++++++++++++++++++++++++
Aetc/fiss/start.d/03-filesystems.sh | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aetc/fiss/start.d/04-swap.sh | 6++++++
Aetc/fiss/start.d/05-misc.sh | 25+++++++++++++++++++++++++
Aetc/fiss/start.d/08-sysctl.sh | 20++++++++++++++++++++
Aetc/fiss/start.d/98-sbin-merge.sh | 11+++++++++++
Aetc/fiss/start.d/99-cleanup.sh | 10++++++++++
Aetc/fiss/stop | 16++++++++++++++++
Aetc/fiss/stop.d/20-rc-shutdown.sh | 1+
Aetc/fiss/stop.d/30-seedrng.sh | 4++++
Aetc/fiss/stop.d/40-hwclock.sh | 3+++
Aetc/fiss/stop.d/50-wtmp.sh | 1+
Aetc/fiss/stop.d/60-udev.sh | 4++++
Aetc/fiss/stop.d/70-pkill.sh | 5+++++
Aetc/fiss/stop.d/80-filesystems.sh | 14++++++++++++++
Aetc/fiss/stop.d/90-kexec.sh | 7+++++++
Aetc/rc.conf | 47+++++++++++++++++++++++++++++++++++++++++++++++
Aetc/rc.local | 5+++++
Aetc/rc.shutdown | 5+++++
Afiss.svg | 29+++++++++++++++++++++++++++++
Ainclude/config.h | 44++++++++++++++++++++++++++++++++++++++++++++
Ainclude/config_parser.h | 9+++++++++
Ainclude/pattern.h | 4++++
Ainclude/service.h | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainclude/user_group.h | 7+++++++
Ainclude/util.h | 23+++++++++++++++++++++++
Aman/halt.8 | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aman/modules-load.8 | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Aman/pause.1 | 39+++++++++++++++++++++++++++++++++++++++
Aman/shutdown.8 | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aman/vlogger.8 | 209+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aman/zzz.8 | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/command.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/command_handler.c | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/config_parser.c | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/dependency.c | 48++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/exec/finit.c | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/exec/fsvc.c | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/exec/fsvs.c | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/exec/halt.c | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/exec/pause.c | 16++++++++++++++++
Asrc/exec/seedrng.c | 470+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/exec/vlogger.c | 192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/pattern.c | 39+++++++++++++++++++++++++++++++++++++++
Asrc/register.c | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/restart.c | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/script/fsvc-lsb.sh | 20++++++++++++++++++++
Asrc/script/modules-load.sh | 29+++++++++++++++++++++++++++++
Asrc/script/poweroff.lnk | 2++
Asrc/script/reboot.lnk | 2++
Asrc/script/shutdown.sh | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/script/zzz.sh | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/serialize.c | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/service.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/socket_handler.c | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/stage.c | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/start.c | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/stop.c | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/supervise.c | 218+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/user_group.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/util.c | 39+++++++++++++++++++++++++++++++++++++++
Ausr/share/fiss/crypt.awk | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ausr/share/fiss/utils | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 +}