Merge pull request #493 from finit-project/file-format

New file format
This commit is contained in:
Joachim Wiberg authored and GitHub committed 2026-08-01 10:08:19 +02:00
commit 3062f49370
106 files changed
+5092 -1607

No files matched your search

+1 -2
View File
@@ -34,8 +34,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt-get -y update sudo apt-get -y update
sudo apt-get -y install pkg-config tree jq libcap-dev sudo apt-get -y install pkg-config tree jq libcap-dev libconfuse-dev
sudo apt-get -y install pkg-config
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
tar xf libuev-2.4.1.tar.xz tar xf libuev-2.4.1.tar.xz
+1 -1
View File
@@ -55,7 +55,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt-get -y update sudo apt-get -y update
sudo apt-get -y install pkg-config libcap-dev sudo apt-get -y install pkg-config libcap-dev libconfuse-dev
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
tar xf libuev-2.4.1.tar.xz tar xf libuev-2.4.1.tar.xz
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- name: Installing dependencies ... - name: Installing dependencies ...
run: | run: |
sudo apt-get -y update sudo apt-get -y update
sudo apt-get -y install pkg-config jq libcap-dev sudo apt-get -y install pkg-config jq libcap-dev libconfuse-dev
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
tar xf libuev-2.4.1.tar.xz tar xf libuev-2.4.1.tar.xz
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt-get -y update sudo apt-get -y update
sudo apt-get -y install pkg-config jq libcap-dev sudo apt-get -y install pkg-config jq libcap-dev libconfuse-dev
wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz wget https://github.com/troglobit/libuev/releases/download/v2.4.1/libuev-2.4.1.tar.xz
wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz wget https://github.com/troglobit/libite/releases/download/v2.6.2/libite-2.6.2.tar.gz
tar xf libuev-2.4.1.tar.xz tar xf libuev-2.4.1.tar.xz
+7 -1
View File
@@ -1,4 +1,4 @@
AC_INIT([Finit], [4.17], [https://github.com/finit-project/finit/issues], AC_INIT([Finit], [5.0-dev], [https://github.com/finit-project/finit/issues],
[finit], [https://http://finit-project.github.io/]) [finit], [https://http://finit-project.github.io/])
AC_CONFIG_AUX_DIR(aux) AC_CONFIG_AUX_DIR(aux)
AM_INIT_AUTOMAKE([1.11 foreign subdir-objects]) AM_INIT_AUTOMAKE([1.11 foreign subdir-objects])
@@ -50,6 +50,12 @@ PKG_PROG_PKG_CONFIG
# Check for required libraries # Check for required libraries
PKG_CHECK_MODULES([uev], [libuev >= 2.4.1]) PKG_CHECK_MODULES([uev], [libuev >= 2.4.1])
PKG_CHECK_MODULES([lite], [libite >= 2.6.1]) PKG_CHECK_MODULES([lite], [libite >= 2.6.1])
# 3.3 is the floor: CFGF_KEYSTRVAL, which environment {} and the
# free-form cgroup keys are built on, does not exist before it. 3.3
# parses both correctly. Two workarounds hang off this number, the
# spurious KEYSTRVAL warning and cfg_parse_buf() losing the file name
# of a template; grep the XXX notes in src/conf.c before raising it.
PKG_CHECK_MODULES([confuse], [libconfuse >= 3.3])
# Check for configured Finit features # Check for configured Finit features
AC_ARG_ENABLE(auto_reload, AC_ARG_ENABLE(auto_reload,
+16 -6
View File
@@ -3,24 +3,34 @@
## Runlevel to start after bootstrap, runlevel 'S' ## Runlevel to start after bootstrap, runlevel 'S'
# Default is 2 # Default is 2
#runlevel 2 #runlevel = 2
# Top-level cgroups and their default settings. All groups mandatory # Top-level cgroups and their default settings. All groups mandatory
# but more can be added, max 8 groups in total currently. The cgroup # but more can be added, max 8 groups in total currently. The cgroup
# 'root' is also available, reserved for RT processes. Settings are # 'root' is also available, reserved for RT processes. Settings are
# as-is, only one shorthand 'mem.' exists, other than that it's the # as-is, only one shorthand 'mem.' exists, other than that it's the
# cgroup v2 controller default names. # cgroup v2 controller default names.
#cgroup init cpu.weight:100 #cgroup init { cpu.weight = 100 }
#cgroup user cpu.weight:100 #cgroup user { cpu.weight = 100 }
#cgroup system cpu.weight:9700 #cgroup system { cpu.weight = 9700 }
# Task to run if ctrl-alt-del is pressed. This condition is asserted by # Task to run if ctrl-alt-del is pressed. This condition is asserted by
# Finit upon receiving SIGINT (from the kernel). # Finit upon receiving SIGINT (from the kernel).
#task <sys/key/ctrlaltdel> [12345789] initctl reboot -- Rebooting system #task reboot {
# description = "Rebooting system"
# runlevel = "12345789"
# conditions = { "sys/key/ctrlaltdel" }
# command = "initctl reboot"
#}
# Task to run if the kernel gets a power fail condition is pressed. # Task to run if the kernel gets a power fail condition is pressed.
# Asserted by Finit upon receiving SIGPWR (from the kernel). # Asserted by Finit upon receiving SIGPWR (from the kernel).
#task <sys/pwr/fail> [12345789] initctl poweroff -- Shutting down system #task poweroff {
# description = "Shutting down system"
# runlevel = "12345789"
# conditions = { "sys/pwr/fail" }
# command = "initctl poweroff"
#}
## Bootstrap services and tasks ## Bootstrap services and tasks
# Can be listed here or in /etc/finit.d/*.conf # Can be listed here or in /etc/finit.d/*.conf
+7 -1
View File
@@ -1,3 +1,9 @@
# Handle PWR button to shutdown/reboot -- useful in Qemu (virt-manager) # Handle PWR button to shutdown/reboot -- useful in Qemu (virt-manager)
# Depends on syslogd having started. Redirect any output to log. # Depends on syslogd having started. Redirect any output to log.
service [2345] <pid/syslogd> log acpid -f -- ACPI daemon service acpid {
description = "ACPI daemon"
runlevel = "2345"
conditions = { "pid/syslogd" }
log { }
command = "acpid -f"
}
+7 -1
View File
@@ -1 +1,7 @@
service [2345] <pid/syslogd> env:-/etc/conf.d/crond crond -f -S $CRON_OPTS -- Cron daemon service crond {
description = "Cron daemon"
runlevel = "2345"
conditions = { "pid/syslogd" }
envfile = "-/etc/conf.d/crond"
command = "crond -f -S $CRON_OPTS"
}
@@ -1,2 +1,7 @@
# Start SSH daemon as soon as basic networking comes up # Start SSH daemon as soon as basic networking comes up
service [2345] env:-/etc/conf.d/dropbear dropbear -R -F $DROPBEAR_OPTS -- Dropbear SSH daemon service dropbear {
description = "Dropbear SSH daemon"
runlevel = "2345"
envfile = "-/etc/conf.d/dropbear"
command = "dropbear -R -F $DROPBEAR_OPTS"
}
+23 -4
View File
@@ -1,5 +1,24 @@
# Consoles to start getty on when system is up # Consoles to start getty on when system is up
tty [12345] /dev/ttyS0 noclear tty ttyS0 {
tty [12345] /dev/tty1 noclear nowait runlevel = "12345"
tty [2345] /dev/tty2 noclear nowait device = "/dev/ttyS0"
tty [2345] /dev/tty3 noclear nowait noclear = true
}
tty tty1 {
runlevel = "12345"
device = "/dev/tty1"
noclear = true
nowait = true
}
tty tty2 {
runlevel = "2345"
device = "/dev/tty2"
noclear = true
nowait = true
}
tty tty3 {
runlevel = "2345"
device = "/dev/tty3"
noclear = true
nowait = true
}
+6 -1
View File
@@ -1,2 +1,7 @@
# Oneshot task to run once at bootstrap # Oneshot task to run once at bootstrap
task [S] env:/etc/conf.d/loadkmap zcat $KEYMAP | loadkmap -- Loading keymap task keymap {
description = "Loading keymap"
runlevel = "S"
envfile = "/etc/conf.d/loadkmap"
command = "zcat $KEYMAP | loadkmap"
}
+5 -1
View File
@@ -1 +1,5 @@
service [2345] lxdm -- Lightweight Login Manager service lxdm {
description = "Lightweight Login Manager"
runlevel = "2345"
command = "lxdm"
}
@@ -1,7 +1,2 @@
## Linux kernel modules to load ## Linux kernel modules to load
#module fbcon #modules = { "fbcon", "button", "evdev", "mousedev", "8139cp", "softdog" }
#module button
#module evdev
#module mousedev
#module 8139cp
#module softdog
+8 -1
View File
@@ -1,2 +1,9 @@
# Busybox ntpd has no option to use syslog when running in foreground # Busybox ntpd has no option to use syslog when running in foreground
service [2345] <pid/syslogd> env:-/etc/conf.d/ntpd log ntpd -n $NTPD_OPTS -- NTP daemon service ntpd {
description = "NTP daemon"
runlevel = "2345"
conditions = { "pid/syslogd" }
envfile = "-/etc/conf.d/ntpd"
log { }
command = "ntpd -n $NTPD_OPTS"
}
+13 -2
View File
@@ -1,4 +1,15 @@
# Start syslogd as soon as possible, should always run # Start syslogd as soon as possible, should always run
# Start klogd as soon as possible, but after syslogd # Start klogd as soon as possible, but after syslogd
service [S12345] env:-/etc/conf.d/syslog syslogd -n $SYSLOGD_OPTS -- Syslog daemon service syslogd {
service [S12345] <pid/syslogd> env:-/etc/conf.d/klogd klogd -n $KLOGD_OPTS -- Kernel log daemon description = "Syslog daemon"
runlevel = "S12345"
envfile = "-/etc/conf.d/syslog"
command = "syslogd -n $SYSLOGD_OPTS"
}
service klogd {
description = "Kernel log daemon"
runlevel = "S12345"
conditions = { "pid/syslogd" }
envfile = "-/etc/conf.d/klogd"
command = "klogd -n $KLOGD_OPTS"
}
@@ -1 +1,6 @@
service [S123456789] env:-/etc/conf.d/watchdog watchdog $WATCHDOG_OPTS $WATCHDOG_DEV -- System watchdog service watchdog {
description = "System watchdog"
runlevel = "S123456789"
envfile = "-/etc/conf.d/watchdog"
command = "watchdog $WATCHDOG_OPTS $WATCHDOG_DEV"
}
+16 -6
View File
@@ -3,24 +3,34 @@
## Runlevel to start after bootstrap, runlevel 'S' ## Runlevel to start after bootstrap, runlevel 'S'
# Default is 2 # Default is 2
#runlevel 2 #runlevel = 2
# Top-level cgroups and their default settings. All groups mandatory # Top-level cgroups and their default settings. All groups mandatory
# but more can be added, max 8 groups in total currently. The cgroup # but more can be added, max 8 groups in total currently. The cgroup
# 'root' is also available, reserved for RT processes. Settings are # 'root' is also available, reserved for RT processes. Settings are
# as-is, only one shorthand 'mem.' exists, other than that it's the # as-is, only one shorthand 'mem.' exists, other than that it's the
# cgroup v2 controller default names. # cgroup v2 controller default names.
#cgroup init cpu.weight:100 #cgroup init { cpu.weight = 100 }
#cgroup user cpu.weight:100 #cgroup user { cpu.weight = 100 }
#cgroup system cpu.weight:9700 #cgroup system { cpu.weight = 9700 }
# Task to run if ctrl-alt-del is pressed. This condition is asserted by # Task to run if ctrl-alt-del is pressed. This condition is asserted by
# Finit upon receiving SIGINT (from the kernel). # Finit upon receiving SIGINT (from the kernel).
#task <sys/key/ctrlaltdel> [12345789] initctl reboot -- Rebooting system #task reboot {
# description = "Rebooting system"
# runlevel = "12345789"
# conditions = { "sys/key/ctrlaltdel" }
# command = "initctl reboot"
#}
# Task to run if the kernel gets a power fail condition is pressed. # Task to run if the kernel gets a power fail condition is pressed.
# Asserted by Finit upon receiving SIGPWR (from the kernel). # Asserted by Finit upon receiving SIGPWR (from the kernel).
#task <sys/pwr/fail> [12345789] initctl poweroff -- Shutting down system #task poweroff {
# description = "Shutting down system"
# runlevel = "12345789"
# conditions = { "sys/pwr/fail" }
# command = "initctl poweroff"
#}
## Bootstrap services and tasks ## Bootstrap services and tasks
# Can be listed here or in /etc/finit.d/*.conf # Can be listed here or in /etc/finit.d/*.conf
+8 -1
View File
@@ -1,3 +1,10 @@
# Handle PWR button to shutdown/reboot -- useful in Qemu (virt-manager) # Handle PWR button to shutdown/reboot -- useful in Qemu (virt-manager)
# Depends on a syslog daemon having started. Redirect any output to log. # Depends on a syslog daemon having started. Redirect any output to log.
service [2345] <pid/syslogd> env:-/etc/default/acpid log acpid -f $OPTIONS -- ACPI daemon service acpid {
description = "ACPI daemon"
runlevel = "2345"
conditions = { "pid/syslogd" }
envfile = "-/etc/default/acpid"
log { }
command = "acpid -f $OPTIONS"
}
@@ -1 +1,8 @@
task [2345] <sys/pwr/ac,pid/syslogd> halt:usr1 env:-/etc/default/anacron anacron -d -q $ANACRON_ARGS -- Anacron jobs task anacron {
description = "Anacron jobs"
runlevel = "2345"
conditions = { "sys/pwr/ac", "pid/syslogd" }
envfile = "-/etc/default/anacron"
stop-signal = "usr1"
command = "anacron -d -q $ANACRON_ARGS"
}
@@ -1,2 +1,6 @@
sysv [S] /etc/init.d/apparmor -- AppArmor initialization sysv apparmor {
description = "AppArmor initialization"
runlevel = "S"
command = "/etc/init.d/apparmor"
}
@@ -1 +1,6 @@
service [2345] env:-/etc/default/avahi-daemon avahi-daemon -s $AVAHI_DAEMON_OPTS -- Avahi mDNS/DNS-SD Stack service avahi-daemon {
description = "Avahi mDNS/DNS-SD Stack"
runlevel = "2345"
envfile = "-/etc/default/avahi-daemon"
command = "avahi-daemon -s $AVAHI_DAEMON_OPTS"
}
@@ -1,3 +1,9 @@
# To ignore errors like "org.freedesktop.hostname1", update the env file with # To ignore errors like "org.freedesktop.hostname1", update the env file with
# "--noplugin=hostname", i.e. append to BLUETOOTHD_OPTS # "--noplugin=hostname", i.e. append to BLUETOOTHD_OPTS
service [2345] log:null env:-/etc/default/bluetooth /usr/libexec/bluetooth/bluetoothd -n $BLUETOOTHD_OPTS -- Bluetooth service service bluetoothd {
description = "Bluetooth service"
runlevel = "2345"
envfile = "-/etc/default/bluetooth"
log { file = "/dev/null" }
command = "/usr/libexec/bluetooth/bluetoothd -n $BLUETOOTHD_OPTS"
}
@@ -1,2 +1,6 @@
# Oneshot task to run once at bootstrap # Oneshot task to run once at bootstrap
task [S] /lib/console-setup/console-setup.sh -- Setting up console font and keymap task console-setup {
description = "Setting up console font and keymap"
runlevel = "S"
command = "/lib/console-setup/console-setup.sh"
}
+7 -1
View File
@@ -1 +1,7 @@
service [2345] <sys/pwr/ac,pid/syslogd> env:-/etc/default/cron cron -f $EXTRA_OPTS -- Background job processing daemon service cron {
description = "Background job processing daemon"
runlevel = "2345"
conditions = { "sys/pwr/ac", "pid/syslogd" }
envfile = "-/etc/default/cron"
command = "cron -f $EXTRA_OPTS"
}
@@ -1,4 +1,9 @@
# On some systems, bootstrap can be really quick and cause elogind # On some systems, bootstrap can be really quick and cause elogind
# to not start before a user has managed to log in. To prevent # to not start before a user has managed to log in. To prevent
# this, add <pid/elogind> as condition to your TTYs in getty.conf # this, add <pid/elogind> as condition to your TTYs in getty.conf
service [2345] <pid/syslogd> /usr/libexec/elogind -- Login manager service elogind {
description = "Login manager"
runlevel = "2345"
conditions = { "pid/syslogd" }
command = "/usr/libexec/elogind"
}
+37 -6
View File
@@ -1,7 +1,38 @@
# Consoles to start getty on when system is up # Consoles to start getty on when system is up
tty [12345] /dev/tty1 linux noclear nowait tty tty1 {
tty [2345] /dev/tty2 linux nowait runlevel = "12345"
tty [2345] /dev/tty3 linux nowait device = "/dev/tty1"
tty [2345] /dev/tty4 linux nowait term = "linux"
tty [2345] /dev/tty5 linux nowait noclear = true
tty [2345] /dev/tty6 linux nowait nowait = true
}
tty tty2 {
runlevel = "2345"
device = "/dev/tty2"
term = "linux"
nowait = true
}
tty tty3 {
runlevel = "2345"
device = "/dev/tty3"
term = "linux"
nowait = true
}
tty tty4 {
runlevel = "2345"
device = "/dev/tty4"
term = "linux"
nowait = true
}
tty tty5 {
runlevel = "2345"
device = "/dev/tty5"
term = "linux"
nowait = true
}
tty tty6 {
runlevel = "2345"
device = "/dev/tty6"
term = "linux"
nowait = true
}
@@ -1,2 +1,6 @@
# Oneshot task to run once at bootstrap # Oneshot task to run once at bootstrap
task [S] /lib/console-setup/keyboard-setup.sh -- Setting up console keyboard layout task keyboard-setup {
description = "Setting up console keyboard layout"
runlevel = "S"
command = "/lib/console-setup/keyboard-setup.sh"
}
@@ -1 +1,5 @@
service [3] lightdm -- Light Display Manager service lightdm {
description = "Light Display Manager"
runlevel = "3"
command = "lightdm"
}
@@ -1,8 +1,2 @@
## Linux kernel modules to load ## Linux kernel modules to load
#module fbcon #modules = { "fbcon", "button", "evdev", "loop", "psmouse", "mousedev", "8139cp" }
#module button
#module evdev
#module loop
#module psmouse
#module mousedev
#module 8139cp
@@ -1,2 +1,6 @@
# Need to stop plymouth, or it'll block stdin on tty1 # Need to stop plymouth, or it'll block stdin on tty1
task [S] /usr/bin/plymouth quit -- Stopping Plymouth Boot Screen task plymouth {
description = "Stopping Plymouth Boot Screen"
runlevel = "S"
command = "/usr/bin/plymouth quit"
}
@@ -1,3 +1,8 @@
# Start rsyslogd as soon as possible, should always run # Start rsyslogd as soon as possible, should always run
# Provides pid/syslogd condition # Provides pid/syslogd condition
service [S12345] name:syslogd env:-/etc/default/rsyslog rsyslogd -n $RSYSLOGD_OPTIONS -- Reliable syslog daemon service syslogd {
description = "Reliable syslog daemon"
runlevel = "S12345"
envfile = "-/etc/default/rsyslog"
command = "rsyslogd -n $RSYSLOGD_OPTIONS"
}
+7 -1
View File
@@ -1,2 +1,8 @@
# Simple NTP client (SNTP) # Simple NTP client (SNTP)
service [2345] <net/route/default> env:-/etc/default/sntpd sntpd -n $SNTPD_OPTIONS -- SNTP client service sntpd {
description = "SNTP client"
runlevel = "2345"
conditions = { "net/route/default" }
envfile = "-/etc/default/sntpd"
command = "sntpd -n $SNTPD_OPTIONS"
}
+7 -1
View File
@@ -1,2 +1,8 @@
# Start SSH daemon as soon as basic networking comes up # Start SSH daemon as soon as basic networking comes up
service [2345] <net/lo/up> env:-/etc/default/ssh /usr/sbin/sshd -D $SSHD_OPTS -- OpenSSH daemon service sshd {
description = "OpenSSH daemon"
runlevel = "2345"
conditions = { "net/lo/up" }
envfile = "-/etc/default/ssh"
command = "/usr/sbin/sshd -D $SSHD_OPTS"
}
@@ -1,2 +1,7 @@
# Start sysklogd as soon as possible, should always run if enabled # Start sysklogd as soon as possible, should always run if enabled
service [S123456789] env:-/etc/default/syslogd syslogd -F $SYSLOGD_OPTIONS -- System logging daemon service syslogd {
description = "System logging daemon"
runlevel = "S123456789"
envfile = "-/etc/default/syslogd"
command = "syslogd -F $SYSLOGD_OPTIONS"
}
+5 -1
View File
@@ -1,4 +1,8 @@
# Here we use XDM since it has few dependencies (relatively speaking). # Here we use XDM since it has few dependencies (relatively speaking).
# It is also an example of a service without a -- description. # It is also an example of a service without a -- description.
# service [3] lxdm -- Lightweight Login Manager # service [3] lxdm -- Lightweight Login Manager
service [3] xdm -nodaemon -- X Display Manager service xdm {
description = "X Display Manager"
runlevel = "3"
command = "xdm -nodaemon"
}
+107 -43
View File
@@ -1,85 +1,149 @@
# Sample finit.conf to demonstrate the syntax # Sample finit.conf to demonstrate the syntax
# #
host testbed hostname = "testbed"
# Linux kernel modules to load # Linux kernel modules to load
module button modules = { "button", "evdev", "loop", "psmouse" }
module evdev
module loop
module psmouse
# Top-level cgroups and their default settings. All groups mandatory # Top-level cgroups and their default settings. All groups mandatory
# but more can be added, max 8 groups in total currently. The cgroup # but more can be added, max 8 groups in total currently. The cgroup
# 'root' is also available, reserved for RT processes. Settings are # 'root' is also available, reserved for RT processes. Settings are
# as-is, only one shorthand 'mem.' exists, other than that it's the # as-is, only one shorthand 'mem.' exists, other than that it's the
# cgroup v2 controller default names. # cgroup v2 controller default names.
cgroup init cpu.weight:100 cgroup init { cpu.weight = 100 }
cgroup user cpu.weight:100 cgroup user { cpu.weight = 100 }
cgroup system cpu.weight:9700 cpu.max:50000 cgroup system { cpu.weight = 9700 cpu.max = 50000 }
# Example extra cgroup # Example extra cgroup
cgroup maint cpu.weight:100 cgroup maint { cpu.weight = 100 }
# Runlevel to start after bootstrap, runlevel 'S' # Runlevel to start after bootstrap, runlevel 'S'
runlevel 2 runlevel = 2
# Network bringup script # Network bringup script
network service networking start network = "service networking start"
# Max file size for each log file: 100 kiB, rotate max 4 copies: # Max file size for each log file: 100 kiB, rotate max 4 copies:
# log => log.1 => log.2.gz => log.3.gz => log.4.gz # log => log.1 => log.2.gz => log.3.gz => log.4.gz
log size:100k count:4 log {
size = 100k
count = 4
}
# Virtual consoles to start getty on # Virtual consoles to start getty on
tty /dev/tty1 tty tty1 { device = "/dev/tty1" }
tty /dev/tty2 tty tty2 { device = "/dev/tty2" }
tty /dev/tty3 tty tty3 { device = "/dev/tty3" }
# Task to run if ctrl-alt-del is pressed. This condition is asserted by # Task to run if ctrl-alt-del is pressed. This condition is asserted by
# Finit upon receiving SIGINT (from the kernel). # Finit upon receiving SIGINT (from the kernel).
task <sys/key/ctrlaltdel> [12345789] initctl reboot -- Rebooting system task reboot {
description = "Rebooting system"
runlevel = "12345789"
conditions = { "sys/key/ctrlaltdel" }
command = "initctl reboot"
}
# Task to run if the kernel gets a power fail condition is pressed. # Task to run if the kernel gets a power fail condition is pressed.
# Asserted by Finit upon receiving SIGPWR (from the kernel). # Asserted by Finit upon receiving SIGPWR (from the kernel).
task <sys/pwr/fail> [12345789] initctl poweroff -- Shutting down system task poweroff {
description = "Shutting down system"
runlevel = "12345789"
conditions = { "sys/pwr/fail" }
command = "initctl poweroff"
}
# Alternative method instead of runparts # Alternative method instead of runparts
#task [S] /etc/init.d/keyboard-setup start -- Setting up preliminary keymap #task keyboard-setup {
#task [S] /etc/init.d/acpid start -- Starting ACPI Daemon # description = "Setting up preliminary keymap"
#task [S] /etc/init.d/kbd start -- Preparing console # runlevel = "S"
#run [2] /etc/init.d/networking start -- Start networking # command = "/etc/init.d/keyboard-setup start"
#}
#task acpid {
# description = "Starting ACPI Daemon"
# runlevel = "S"
# command = "/etc/init.d/acpid start"
#}
#task kbd {
# description = "Preparing console"
# runlevel = "S"
# command = "/etc/init.d/kbd start"
#}
#run networking {
# description = "Start networking"
# runlevel = "2"
# command = "/etc/init.d/networking start"
#}
# Services to be monitored and respawned as needed # Services to be monitored and respawned as needed
# klgod and syslogd are placed in the maint cgroup # klogd and syslogd are placed in the maint cgroup. There is no
cgroup.maint # "current cgroup" to switch in this format, every block names its own.
service [2345] <pid/syslogd> klogd -n -- Kernel logging server service klogd {
service [2345] syslogd -n -- Syslog server description = "Kernel logging server"
runlevel = "2345"
conditions = { "pid/syslogd" }
cgroup maint {}
command = "klogd -n"
}
service syslogd {
description = "Syslog server"
runlevel = "2345"
cgroup maint {}
command = "syslogd -n"
}
# gdm in the system cgroup (default when read from it's own .conf file) # gdm gets its own limits inside the system group, which is where
cgroup.system # services end up when no cgroup block is given.
service [3] cgroup:cpu.weight:250,mem.max:10M gdm -- GNOME Display Manager service gdm {
description = "GNOME Display Manager"
runlevel = "3"
cgroup system {
cpu.weight = 250
memory.max = 10M
}
command = "gdm"
}
# Start SSH daemon with opts from /etc/default/ssh (if available), move # Start SSH daemon with opts from /etc/default/ssh (if available), move
# to user cgroup with cpu.weight:250. Default for services is system # to user cgroup with cpu.weight 250. Default weight in the user group
# group. Default weight in user group is 100 (above). Max length of # is 100 (above).
# cgroup argument is (currently) 63 chars. service sshd {
# description = "OpenSSH daemon"
# Notice alternative syntax where current cgroup is only set for this runlevel = "2345"
# particular service. envfile = "-/etc/default/ssh"
service [2345] cgroup.user:cpu.weight:250,mem.max:10M env:-/etc/default/ssh /usr/sbin/sshd -D $SSHD_OPTS -- OpenSSH daemon cgroup user {
cpu.weight = 250
memory.max = 10M
}
command = "/usr/sbin/sshd -D $SSHD_OPTS"
}
# The BusyBox ntpd does not use syslog when running in the foreground # The BusyBox ntpd does not use syslog when running in the foreground
# So we use this trick to redirect stdout/stderr to a log file. The # So we use this trick to redirect stdout/stderr to a log file. The
# log file is rotated with the above settings. The condition declares # log file is rotated with the above settings. The condition declares
# a dependency on a system default route (gateway) to be set. A single # a dependency on a system default route (gateway) to be set. ntpd
# <!> at the beginning means ntpd does not respect SIGHUP for restart. # does not respect SIGHUP, so Finit restarts it on reload instead.
service [2345] <!net/route/default> log:/var/log/ntpd.log ntpd -n -l -I eth0 -- NTP daemon service ntpd {
description = "NTP daemon"
runlevel = "2345"
conditions = { "net/route/default" }
reload-signal = "none"
log { file = "/var/log/ntpd.log" }
command = "ntpd -n -l -I eth0"
}
# Run start scripts from this directory # Run start scripts from this directory
# runparts /etc/start.d #runparts = "/etc/start.d"
# For multiple instances of the same service, add :ID somewhere between # For multiple instances of the same service, add :ID to the block title.
# the service/run/task keyword and the command. service httpd:80 {
service :80 [2345] /sbin/httpd -f -h /http -p 80 -- Web server description = "Web server"
service :8080 [2345] /sbin/httpd -f -h /http -p 8080 -- Old web server runlevel = "2345"
command = "/sbin/httpd -f -h /http -p 80"
}
service httpd:8080 {
description = "Old web server"
runlevel = "2345"
command = "/sbin/httpd -f -h /http -p 8080"
}
+16 -6
View File
@@ -3,24 +3,34 @@
## Runlevel to start after bootstrap, runlevel 'S' ## Runlevel to start after bootstrap, runlevel 'S'
# Default is 2 # Default is 2
#runlevel 2 #runlevel = 2
# Top-level cgroups and their default settings. All groups mandatory # Top-level cgroups and their default settings. All groups mandatory
# but more can be added, max 8 groups in total currently. The cgroup # but more can be added, max 8 groups in total currently. The cgroup
# 'root' is also available, reserved for RT processes. Settings are # 'root' is also available, reserved for RT processes. Settings are
# as-is, only one shorthand 'mem.' exists, other than that it's the # as-is, only one shorthand 'mem.' exists, other than that it's the
# cgroup v2 controller default names. # cgroup v2 controller default names.
#cgroup init cpu.weight:100 #cgroup init { cpu.weight = 100 }
#cgroup user cpu.weight:100 #cgroup user { cpu.weight = 100 }
#cgroup system cpu.weight:9700 #cgroup system { cpu.weight = 9700 }
# Task to run if ctrl-alt-del is pressed. This condition is asserted by # Task to run if ctrl-alt-del is pressed. This condition is asserted by
# Finit upon receiving SIGINT (from the kernel). # Finit upon receiving SIGINT (from the kernel).
#task <sys/key/ctrlaltdel> [12345789] initctl reboot -- Rebooting system #task reboot {
# description = "Rebooting system"
# runlevel = "12345789"
# conditions = { "sys/key/ctrlaltdel" }
# command = "initctl reboot"
#}
# Task to run if the kernel gets a power fail condition is pressed. # Task to run if the kernel gets a power fail condition is pressed.
# Asserted by Finit upon receiving SIGPWR (from the kernel). # Asserted by Finit upon receiving SIGPWR (from the kernel).
#task <sys/pwr/fail> [12345789] initctl poweroff -- Shutting down system #task poweroff {
# description = "Shutting down system"
# runlevel = "12345789"
# conditions = { "sys/pwr/fail" }
# command = "initctl poweroff"
#}
## Bootstrap services and tasks ## Bootstrap services and tasks
# Can be listed here or in /etc/finit.d/*.conf # Can be listed here or in /etc/finit.d/*.conf
+5 -1
View File
@@ -1,3 +1,7 @@
# Handle PWR button to shutdown/reboot -- useful in Qemu (virt-manager) # Handle PWR button to shutdown/reboot -- useful in Qemu (virt-manager)
# Depends on syslogd having started. Redirect any output to log. # Depends on syslogd having started. Redirect any output to log.
service [2345] acpid -f -- ACPI daemon service acpid {
description = "ACPI daemon"
runlevel = "2345"
command = "acpid -f"
}
+6 -1
View File
@@ -1 +1,6 @@
service [2345] <pid/syslogd> crond -f -S -c /etc/crontabs -- Cron daemon service crond {
description = "Cron daemon"
runlevel = "2345"
conditions = { "pid/syslogd" }
command = "crond -f -S -c /etc/crontabs"
}
+5 -1
View File
@@ -1 +1,5 @@
service [2345] dhcpcd -B -M -- DHCP client service dhcpcd {
description = "DHCP client"
runlevel = "2345"
command = "dhcpcd -B -M"
}
+5 -1
View File
@@ -1 +1,5 @@
service [S12345] dmeventd -f -- Device mapper event daemon service dmeventd {
description = "Device mapper event daemon"
runlevel = "S12345"
command = "dmeventd -f"
}
+18 -3
View File
@@ -1,4 +1,19 @@
# Consoles to start Finit built-in getty on when system is up # Consoles to start Finit built-in getty on when system is up
tty [12345] /dev/tty1 noclear nowait tty tty1 {
tty [2345] /dev/tty2 noclear nowait runlevel = "12345"
tty [2345] /dev/tty3 noclear nowait device = "/dev/tty1"
noclear = true
nowait = true
}
tty tty2 {
runlevel = "2345"
device = "/dev/tty2"
noclear = true
nowait = true
}
tty tty3 {
runlevel = "2345"
device = "/dev/tty3"
noclear = true
nowait = true
}
+5 -1
View File
@@ -1,2 +1,6 @@
# Oneshot task to run once at bootstrap # Oneshot task to run once at bootstrap
task [S] zcat /etc/keymap/se.bmap.gz | loadkmap -- Loading keymap task keymap {
description = "Loading keymap"
runlevel = "S"
command = "zcat /etc/keymap/se.bmap.gz | loadkmap"
}
+5 -1
View File
@@ -1 +1,5 @@
service [2345] lxdm -- Lightweight Login Manager service lxdm {
description = "Lightweight Login Manager"
runlevel = "2345"
command = "lxdm"
}
+2 -6
View File
@@ -1,7 +1,3 @@
## Linux kernel modules to load ## Linux kernel modules to load
#module fbcon # Other candidates: fbcon, button, evdev, mousedev, 8139cp
#module button modules = { "softdog" }
#module evdev
#module mousedev
#module 8139cp
module softdog
+7 -1
View File
@@ -1,2 +1,8 @@
# Silly Busybox ntpd has no option to use syslog when running in foreground # Silly Busybox ntpd has no option to use syslog when running in foreground
service [2345] <pid/syslogd> log ntpd -n -N -p pool.ntp.org -- NTP daemon service ntpd {
description = "NTP daemon"
runlevel = "2345"
conditions = { "pid/syslogd" }
log { }
command = "ntpd -n -N -p pool.ntp.org"
}
+5 -1
View File
@@ -1,2 +1,6 @@
# Start SSH daemon as soon as basic networking comes up # Start SSH daemon as soon as basic networking comes up
service [2345] /usr/sbin/sshd -D -- OpenSSH daemon service sshd {
description = "OpenSSH daemon"
runlevel = "2345"
command = "/usr/sbin/sshd -D"
}
+11 -2
View File
@@ -1,4 +1,13 @@
# Start syslogd as soon as possible, should always run # Start syslogd as soon as possible, should always run
# Start klogd as soon as possible, but after syslogd # Start klogd as soon as possible, but after syslogd
service [S12345] syslogd -n -- Syslog daemon service syslogd {
service [S12345] <pid/syslogd> klogd -n -- Kernel log daemon description = "Syslog daemon"
runlevel = "S12345"
command = "syslogd -n"
}
service klogd {
description = "Kernel log daemon"
runlevel = "S12345"
conditions = { "pid/syslogd" }
command = "klogd -n"
}
+7 -1
View File
@@ -1 +1,7 @@
service @uuidd:uuidd [2345] uuidd -F -- UUID generation daemon service uuidd {
description = "UUID generation daemon"
runlevel = "2345"
user = "uuidd"
group = "uuidd"
command = "uuidd -F"
}
+5 -1
View File
@@ -171,7 +171,11 @@ Notice the `--` separator.
To debug startup issues, in particular issues with getty/login, add To debug startup issues, in particular issues with getty/login, add
the following to your Finit .conf file: the following to your Finit .conf file:
tty [12345789] notty noclear tty board {
runlevel = "12345789"
notty = true
noclear = true
}
The `notty` option ensures reusing the stdin/stdout set up by the The `notty` option ensures reusing the stdin/stdout set up by the
kernel. Remember, this is only for debugging and would leave your kernel. Remember, this is only for debugging and would leave your
+4 -3
View File
@@ -41,7 +41,7 @@ The `bool` setting is one of `on, off, true false, 1, 0`.
Useful when starting up in various [rescue mode][rescue], factory, or Useful when starting up in various [rescue mode][rescue], factory, or
production test setups. Use the top-level configuration file production test setups. Use the top-level configuration file
directive `rcsd /path/to/finit.d` to override the default rcS.d setting `rcsd = "/path/to/finit.d"` to override the default rcS.d
directory. directory.
* `finit.debug[=bool]`: Enable finit debug. This is operated * `finit.debug[=bool]`: Enable finit debug. This is operated
@@ -104,8 +104,9 @@ The `bool` setting is one of `on, off, true false, 1, 0`.
* `single`, or `S`: Overrides the configured runlevel (default: 2) to go * `single`, or `S`: Overrides the configured runlevel (default: 2) to go
to after bootstrap by forcing it to runlevel 1, this is also known to after bootstrap by forcing it to runlevel 1, this is also known
as single user mode. Useful to debug startup problems. All services as single user mode. Useful to debug startup problems. All services
and TTYs in `[1]` will be started, so a `tty [1] @console nologin` and TTYs in runlevel 1 will be started, so a `tty` block with
configuration presents you with a root console without login. `runlevel = "1"`, `device = "@console"`, and `nologin = true`
presents you with a root console without login.
* `1-9`, except `6`: override the configured `runlevel`. Like the `S` * `1-9`, except `6`: override the configured `runlevel`. Like the `S`
and `rescue`, giving a single number on the kernel command line tells and `rescue`, giving a single number on the kernel command line tells
+79 -37
View File
@@ -7,19 +7,27 @@ mechanism for common synchronization problems. For example:
- *"wait for service A to start before starting service B"*, or - *"wait for service A to start before starting service B"*, or
- *"wait for basic network access to be available"* - *"wait for basic network access to be available"*
Conditions are similar in syntax to declaring runlevels per service. A condition is named in the `conditions` list of a `service`, `task`, or
They are specified within angle brackets `<>` and can be applied to any `run` block. The list may hold several, and they are logically AND'ed
of the `service`, `task`, or `run` stanza. Multiple conditions may be
specified separated by comma. Multiple conditions are logically AND'ed
during evaluation, i.e. all conditions must be satisfied in order for a during evaluation, i.e. all conditions must be satisfied in order for a
service to run. service to run. In running text, and in `initctl` output, a condition
is written inside angle brackets, `<pid/syslogd>`.
Two special prefixes can be used inside the angle brackets: One prefix can be used on a condition:
- `!` -- service does not support `SIGHUP` (noreload), or run/task
should not block runlevel changes (i.e., bootstrap)
- `~` -- propagate reload from this dependency, see below - `~` -- propagate reload from this dependency, see below
The line-based format also accepts a leading `!` on the list, which is
not a condition and not a negation. It is a flag on the block, and it
means two unrelated things depending on where it sits: a service does
not support `SIGHUP` (noreload), or a run/task should not block
runlevel changes, i.e. bootstrap. Each of those is its own key here:
| Line-based | Block format |
|---|---|
| `<!...>` on a service or sysv | `reload-signal = "none"` |
| `<!...>` on a run or task | `required = false` |
Finit guarantees by default that all run/tasks run (at least) once Finit guarantees by default that all run/tasks run (at least) once
per runlevel. For most tasks this is a good default, for example per runlevel. For most tasks this is a good default, for example
checking SSH host keys or loading keymap. However, for conditions checking SSH host keys or loading keymap. However, for conditions
@@ -27,7 +35,12 @@ that are unlikely to happen it is not. (See example below.)
### Example ### Example
service [2345] <pid/setupd,pid/zebra> /sbin/netd -- Network monitor service netd {
description = "Network monitor"
runlevel = "2345"
conditions = { "pid/setupd", "pid/zebra" }
command = "/sbin/netd"
}
In this example the Network monitor daemon `netd` is not started until In this example the Network monitor daemon `netd` is not started until
both the `pid/setupd` *and* `pid/zebra` conditions are satisfied. A both the `pid/setupd` *and* `pid/zebra` conditions are satisfied. A
@@ -42,15 +55,28 @@ being created, i.e., the service's default readiness notification.
Another example is `dropbear`, it does not support `SIGHUP`, but we can Another example is `dropbear`, it does not support `SIGHUP`, but we can
also see optional sourcing of arguments from an environment file: also see optional sourcing of arguments from an environment file:
service [2345789] <!> env:-/etc/default/dropbear dropbear -F -R $DROPBEAR_ARGS -- Dropbear SSH daemon service dropbear {
description = "Dropbear SSH daemon"
runlevel = "2345789"
reload-signal = "none"
envfile = "-/etc/default/dropbear"
command = "dropbear -F -R $DROPBEAR_ARGS"
}
Finally, the weird "block runlevel changes" example. Here we see what Finally, the weird "block runlevel changes" example. Here we see what
happens when Finit receives `SIGPWR`, sent from a power daemon like happens when Finit receives `SIGPWR`, sent from a power daemon like
[powstatd(8)][]. A condition is asserted and a user can set up their [powstatd(8)][]. A condition is asserted and a user can set up their
own task to act on it. We do not want this task to block Finit from own task to act on it. We do not want this task to block Finit from
moving to the next runlevel after bootstrap, so we set `<!>`: moving to the next runlevel after bootstrap, so we set
`required = false`:
task [S0123456789] <!sys/pwr/fail> name:pwrfail initctl poweroff -- Power failure, shutting down task pwrfail {
description = "Power failure, shutting down"
runlevel = "S0123456789"
conditions = { "sys/pwr/fail" }
required = false
command = "initctl poweroff"
}
Propagating Reload in Dependencies Propagating Reload in Dependencies
@@ -65,13 +91,23 @@ not care if it reloads its config.
For services that need to react when their upstream reloads, the `~` For services that need to react when their upstream reloads, the `~`
prefix propagates the reload from the dependency: prefix propagates the reload from the dependency:
service <pid/svc_a> name:svc_b /sbin/svc_b -- Needs A (barrier) service svc_b {
service <!~pid/svc_b> name:svc_c /sbin/svc_c -- Propagate reload from B description = "Needs A (barrier)"
conditions = { "pid/svc_a" }
command = "/sbin/svc_b"
}
service svc_c {
description = "Propagate reload from B"
conditions = { "~pid/svc_b" }
reload-signal = "none"
command = "/sbin/svc_c"
}
Here, `<~pid/svc_b>` means: propagate a reload of `svc_b` to `svc_c`. Here, `<~pid/svc_b>` means: propagate a reload of `svc_b` to `svc_c`.
When `svc_b` reloads, `svc_c` will be restarted (because of `!`, When `svc_b` reloads, `svc_c` will be restarted, because of
noreload) instead of merely resumed. If `svc_c` supported `SIGHUP` `reload-signal = "none"`, instead of merely resumed. If `svc_c`
(no `!` prefix), it would be sent `SIGHUP` instead. supported `SIGHUP`, it would be sent `SIGHUP` instead.
This is similar to systemd's `PropagatesReloadTo=` directive, but This is similar to systemd's `PropagatesReloadTo=` directive, but
declared on the consumer side rather than the provider side. declared on the consumer side rather than the provider side.
@@ -103,7 +139,12 @@ services, or run/task jobs, on external site-dependent stimuli.
**Example:** **Example:**
service [2345] <usr/foo> alarm --arg foo -- Foo alarm service alarm {
description = "Foo alarm"
runlevel = "2345"
conditions = { "usr/foo" }
command = "alarm --arg foo"
}
For convenience, prefixing with `usr/` is allowed, but any other slashes For convenience, prefixing with `usr/` is allowed, but any other slashes
or period characters are disallowed. E.g., to trigger the `Foo alarm`, or period characters are disallowed. E.g., to trigger the `Foo alarm`,
@@ -126,8 +167,9 @@ Static (one-shot) conditions, like `usr/`, never enter the `flux` state.
> "touch" or recreate, their PID file on `SIGHUP`. This can be done by > "touch" or recreate, their PID file on `SIGHUP`. This can be done by
> calling `utimensat()` on the PID file. Provided, of course, that the > calling `utimensat()` on the PID file. Provided, of course, that the
> service supports reloading on `SIGHUP`, otherwise it will be restarted > service supports reloading on `SIGHUP`, otherwise it will be restarted
> by Finit when they instead exit on the signal. For such services, use > by Finit when they instead exit on the signal. For such services,
> `<!>` to tell Finit the service does not support `SIGHUP`. > set `reload-signal = "none"` to tell Finit the service does not
> support `SIGHUP`.
Built-in Conditions Built-in Conditions
@@ -145,15 +187,15 @@ The `devmon` (built-in) plugin monitors `/dev` and `/dev/dir` for device
nodes being created and removed. It is active only when a run, task, or nodes being created and removed. It is active only when a run, task, or
service has declared a `<dev/foo>` or `<dev/dir/bar>` condition. service has declared a `<dev/foo>` or `<dev/dir/bar>` condition.
The `pidfile` plugin (recursively) watches `/run/` (recursively) for PID The `pidfile` plugin recursively watches `/run/` for PID files created
files created by the monitored services, and sets a corresponding by the monitored services, and sets a corresponding condition in the
condition in the `pid/` namespace. `pid/` namespace.
Similarly, the `netlink` plugin provides basic conditions for when an Similarly, the `netlink` plugin provides basic conditions for when an
interface is brought up/down and when a default route (gateway) is set, interface is brought up/down and when a default route (gateway) is set,
in the `net/` namespace. in the `net/` namespace.
The `sys` and `usr` plugins monitor are passive condition monitors where The `sys` and `usr` plugins are passive condition monitors where
the action is provided by `keventd`, signal handlers, and in the case of the action is provided by `keventd`, signal handlers, and in the case of
`usr`, the end-user via the `initctl` tool. `usr`, the end-user via the `initctl` tool.
@@ -199,16 +241,16 @@ Composition
----------- -----------
The `pid/` conditions are generated by the Finit `pidfile.so` plugin and The `pid/` conditions are generated by the Finit `pidfile.so` plugin and
composed from a service's `name:` and `:id`. By default the basename of composed from a service's block title and its `:id`. By default the
the daemon and the empty string. basename of the daemon and the empty string.
| **service** | **condition** | | **service** | **condition** |
|----------------------------------------------------|------------------| |-------------------------------------------------------------------|------------------|
| /sbin/foo | pid/foo | | `service { command = "/sbin/foo" }` | pid/foo |
| /sbin/bar -p /run/baz.pid | pid/bar | | `service { command = "/sbin/bar -p /run/baz.pid" }` | pid/bar |
| name:lxc :foo lxc-start -n foo -p /run/lxc/foo.pid | pid/lxc:foo | | `service lxc:foo { command = "lxc-start -n foo -p /run/lxc/foo.pid" }` | pid/lxc:foo |
| /usr/bin/dbus-daemon | pid/dbus-daemon | | `service { command = "/usr/bin/dbus-daemon" }` | pid/dbus-daemon |
| :222 dropbear -p 222 | pid/dropbear:222 | | `service dropbear:222 { command = "dropbear -p 222" }` | pid/dropbear:222 |
The condition is asserted when `pidfile.so` receives an inotify event The condition is asserted when `pidfile.so` receives an inotify event
for a file matching `/run/*.pid`, `/run/**/*.pid`, or `/run/**/pid`, for a file matching `/run/*.pid`, `/run/**/*.pid`, or `/run/**/pid`,
@@ -223,8 +265,8 @@ its conditions are cleared and reasserted, ensuring dependent services
are properly updated. are properly updated.
Daemons that don't create PID files, or fail to touch them on reload, Daemons that don't create PID files, or fail to touch them on reload,
can be worked around by using the `pid:/path/to/file.pid` syntax in can be worked around by setting `pidfile` and `pidfile-create` in the
the service stanza for the daemon. It is far from optimal since any service block for the daemon. It is far from optimal since any
synchronization of depending services may fail due to the daemon not synchronization of depending services may fail due to the daemon not
having reinitialized/created their IPC sockets, or similar. having reinitialized/created their IPC sockets, or similar.
@@ -307,8 +349,8 @@ restarts that would otherwise occur because a depending service was sent
Services with the `~` prefix are an exception to this rule: when their Services with the `~` prefix are an exception to this rule: when their
conditions return to `on` after being in `flux`, the reload is propagated conditions return to `on` after being in `flux`, the reload is propagated
-- the service is reloaded (SIGHUP) or restarted (noreload `!`) instead -- the service is reloaded (SIGHUP), or restarted if it has
of simply being resumed. `reload-signal = "none"`, instead of simply being resumed.
Therefore, any plugin that supplies Finit with conditions must ensure Therefore, any plugin that supplies Finit with conditions must ensure
that their state is updated after each reconfiguration. This can be that their state is updated after each reconfiguration. This can be
+47 -32
View File
@@ -16,14 +16,17 @@ which is the same approach used by other modern service managers like dinit.
## Basic Usage ## Basic Usage
Capabilities are specified using the `caps:` directive in service configuration: Capabilities are specified with the `capabilities` key, alias `caps`:
```conf ```conf
service [2345] name:nginx \ service nginx {
@www-data:www-data \ description = "Web server"
caps:^cap_net_bind_service \ runlevel = "2345"
/usr/sbin/nginx -g 'daemon off;' \ user = "www-data"
-- Web server group = "www-data"
capabilities = { "^cap_net_bind_service" }
command = "/usr/sbin/nginx -g 'daemon off;'"
}
``` ```
This example allows nginx to bind to privileged ports (like 80 and 443) while This example allows nginx to bind to privileged ports (like 80 and 443) while
@@ -52,7 +55,7 @@ with the following prefixes:
Multiple capabilities can be specified as a comma-separated list: Multiple capabilities can be specified as a comma-separated list:
```conf ```conf
caps:^cap_net_raw,^cap_net_admin,^cap_net_bind_service capabilities = { "^cap_net_raw", "^cap_net_admin", "^cap_net_bind_service" }
``` ```
## Common Use Cases ## Common Use Cases
@@ -62,10 +65,13 @@ caps:^cap_net_raw,^cap_net_admin,^cap_net_bind_service
Allow a web server to bind to ports 80 and 443 without running as root: Allow a web server to bind to ports 80 and 443 without running as root:
```conf ```conf
service [2345] name:webserver \ service webserver {
@www-data:www-data \ runlevel = "2345"
caps:^cap_net_bind_service \ user = "www-data"
/usr/sbin/nginx -g 'daemon off;' group = "www-data"
capabilities = { "^cap_net_bind_service" }
command = "/usr/sbin/nginx -g 'daemon off;'"
}
``` ```
### Network Monitoring (Raw Sockets) ### Network Monitoring (Raw Sockets)
@@ -73,10 +79,12 @@ service [2345] name:webserver \
Allow packet capture without root privileges: Allow packet capture without root privileges:
```conf ```conf
service [2345] name:tcpdump \ service tcpdump {
@tcpdump \ runlevel = "2345"
caps:^cap_net_raw,^cap_net_admin \ user = "tcpdump"
/usr/sbin/tcpdump -i eth0 -w /var/log/capture.pcap capabilities = { "^cap_net_raw", "^cap_net_admin" }
command = "/usr/sbin/tcpdump -i eth0 -w /var/log/capture.pcap"
}
``` ```
### NTP Daemon (System Time) ### NTP Daemon (System Time)
@@ -84,10 +92,12 @@ service [2345] name:tcpdump \
Allow time synchronization without full root: Allow time synchronization without full root:
```conf ```conf
service [2345] name:ntpd \ service ntpd {
@ntp \ runlevel = "2345"
caps:^cap_sys_time,^cap_sys_nice \ user = "ntp"
/usr/sbin/ntpd -n capabilities = { "^cap_sys_time", "^cap_sys_nice" }
command = "/usr/sbin/ntpd -n"
}
``` ```
## Available Capabilities ## Available Capabilities
@@ -116,9 +126,9 @@ Common capabilities include (see `man 7 capabilities` for the complete list):
- Don't grant `cap_sys_admin` unless absolutely necessary - Don't grant `cap_sys_admin` unless absolutely necessary
2. **Specify a user (preferably non-root)** 2. **Specify a user (preferably non-root)**
- The `@user` directive is **required** for `caps:` to take effect - The `user` setting is **required** for `capabilities` to take effect
- For ambient capabilities (`^`), use a non-root user (not `@root`) - For ambient capabilities (`^`), use a non-root user (not `"root"`)
- Example: `@www-data`, `@nginx`, `@tcpdump` - Example: `user = "www-data"`, `user = "nginx"`, `user = "tcpdump"`
3. **Use ambient capabilities (`^`)** 3. **Use ambient capabilities (`^`)**
- The `^` prefix ensures capabilities survive exec() - The `^` prefix ensures capabilities survive exec()
@@ -159,21 +169,26 @@ ps -o user,pid,cmd -p $(pidof nginx)
- Linux kernel 4.3+ (for ambient capabilities support) - Linux kernel 4.3+ (for ambient capabilities support)
- libcap library installed - libcap library installed
- Finit built with `--enable-libcap` - Finit built with `--enable-libcap`, otherwise a `capabilities` list is
ignored, with a warning
## Limitations ## Limitations
- The `caps:` directive requires `@user` to be specified for it to take effect - `capabilities` requires `user` to be set for it to take effect
- Without `@user`, the service runs as root with full capabilities and - Without `user`, the service runs as root with full capabilities and
the `caps:` configuration is silently ignored the `capabilities` list is silently ignored
- You can use `@root` with `caps:`, but see below about ambient capabilities - You can use `user = "root"`, but see below about ambient capabilities
- For ambient capabilities (`^`, recommended), the user **must be non-root** - For ambient capabilities (`^`, recommended), the user **must be non-root**
- Using `@root` with `caps:^...` will not work effectively, as ambient - Using `user = "root"` with `^` capabilities will not work effectively, as ambient
capabilities are only added to the effective set when euid ≠ 0 capabilities are only added to the effective set when euid ≠ 0
- Use inheritable (`%`) or bounding (`!`) capabilities with `@root` if needed - Use inheritable (`%`) or bounding (`!`) capabilities with `user = "root"` if needed
- Services without `caps:` use standard privilege dropping: - Finit warns about this when reading the .conf file:
- Services with `@user` (non-root) have no special capabilities
- Services without `@user` run as root with full capabilities nginx: ambient capabilities ('^') have no effect as root, use a
non-root user, or '%' and '!' entries
- Services without `capabilities` use standard privilege dropping:
- Services with a non-root `user` have no special capabilities
- Services without `user` run as root with full capabilities
- Some very old binaries may not work correctly with ambient capabilities - Some very old binaries may not work correctly with ambient capabilities
- File system capabilities are not managed by Finit (use `setcap` for that) - File system capabilities are not managed by Finit (use `setcap` for that)
+126 -99
View File
@@ -1,19 +1,17 @@
Finit provides three different cgroup directives for controlling resource allocation: Finit has two `cgroup` blocks for controlling resource allocation:
1. **Top-level cgroup definition**: `cgroup NAME settings` 1. **Top-level definition**, at file scope: declares a group such as
- Defines a top-level cgroup (e.g., `init`, `system`, `user`) with default settings `init`, `system`, or `user`, and its default settings.
- Space-separated syntax
- Example: `cgroup system cpu.weight:9700`
2. **Global cgroup selector**: `cgroup.NAME[,options]` (standalone directive) cgroup system { cpu.weight = 9700 }
- Sets the default cgroup for subsequent services in a `.conf` file
- Dot-separated with optional comma-separated options
- Example: `cgroup.maint` or `cgroup.system,delegate`
3. **Per-service cgroup option**: `cgroup.NAME[,options]` or `cgroup:options` 2. **Joining a group**, inside a service block: names the group this
- Overrides the cgroup for a specific service service runs in, and may override settings for itself alone.
- Part of the service directive line
- Example: `service [...] cgroup.maint,mem.max:1G /path/to/cmd` service foo {
cgroup maint { memory.max = 1G }
command = "/path/to/cmd"
}
> [!NOTE] > [!NOTE]
> Linux cgroups and details surrounding values are not explained in the > Linux cgroups and details surrounding values are not explained in the
@@ -23,83 +21,72 @@ Finit provides three different cgroup directives for controlling resource alloca
Top-level Cgroup Definition Top-level Cgroup Definition
---------------------------- ----------------------------
**Syntax:** `cgroup NAME settings` **Syntax:** `cgroup NAME { settings }`
# Top-level cgroups and their default settings. All groups mandatory # Top-level cgroups and their default settings. All groups mandatory
# but more can be added, max 8 groups in total currently. The cgroup # but more can be added, max 8 groups in total currently. The cgroup
# 'root' is also available, reserved for RT processes. Settings are # 'root' is also available, reserved for RT processes. Settings are
# as-is, only one shorthand 'mem.' exists, other than that it's the # as-is, only one shorthand 'mem.' exists, other than that it's the
# cgroup v2 controller default names. # cgroup v2 controller default names.
cgroup init cpu.weight:100 cgroup init { cpu.weight = 100 }
cgroup user cpu.weight:100 cgroup user { cpu.weight = 100 }
cgroup system cpu.weight:9800 cgroup system { cpu.weight = 9800 }
Adding an extra cgroup `maint/` will require you to adjust the weight of Adding an extra cgroup `maint/` will require you to adjust the weight of
the above three. We leave `init/` and `user/` as-is reducing weight of the above three. We leave `init/` and `user/` as-is reducing weight of
`system/` to 9700. `system/` to 9700.
cgroup system cpu.weight:9700 cgroup system { cpu.weight = 9700 }
# Example extra cgroup 'maint' # Example extra cgroup 'maint'
cgroup maint cpu.weight:100 cgroup maint { cpu.weight = 100 }
By default, the `system/` cgroup is selected for almost everything. The By default, the `system/` cgroup is selected for almost everything. The
`init/` cgroup is reserved for PID 1 itself and its closest relatives. `init/` cgroup is reserved for PID 1 itself and its closest relatives.
The `user/` cgroup is for local TTY logins spawned by getty. The `user/` cgroup is for local TTY logins spawned by getty.
Global Cgroup Selector Joining a Cgroup
---------------------- ----------------
**Syntax:** `cgroup.NAME[,options]` (standalone directive) **Syntax:** `cgroup NAME { settings }` (inside a service block)
To select a different top-level cgroup, e.g. `maint/`, for a group of Every block says which group it joins, by name. An empty block joins
run/task/service directives in a `.conf` file, use the `cgroup.NAME` without changing anything:
directive as a standalone line:
cgroup.maint service foo {
service [...] <...> /path/to/foo args -- description cgroup maint {}
service [...] <...> /path/to/bar args -- description command = "/path/to/foo args"
}
Both services will run in the `maint/` cgroup. service bar {
cgroup maint {}
command = "/path/to/bar args"
}
You can also include options with the global selector: Both services run in the `maint/` cgroup. Settings inside the block
apply to that service alone:
cgroup.system,delegate service foo {
service [...] <...> /path/to/foo args -- description cgroup maint {
cpu.max = 10000
memory.max = 655360
}
command = "/path/to/foo args"
}
Per-Service Cgroup Option > [!NOTE]
-------------------------- > The legacy format has a standalone `cgroup.NAME` line that selects a
> group for every stanza following it in the file, and a `cgroup:options`
> form that applies settings to whichever group is current without
> naming it. Neither has an equivalent here, by design: a block that
> joins a group says so itself, so the group cannot depend on what came
> earlier in the file.
**Syntax:** `cgroup.NAME[,options]` or `cgroup:options` (service option) Every cgroup setting maps directly to cgroup v2 syntax, so `cpu.max`
maps to the file `/sys/fs/cgroup/maint/foo/cpu.max`. There is no
To override the cgroup for a specific service, use the `cgroup.NAME` filtering, the one exception being the shorthand `mem.`, which expands
option within the service directive: to `memory.`. If the file is not available, either the controller is
missing from your Linux kernel, or the name is misspelled.
service [...] <...> cgroup.maint /path/to/foo args -- description
This form also allows per-service limits. Two syntaxes are supported:
**New comma-separated syntax (recommended):**
service [...] <...> cgroup.maint,cpu.max:10000,mem.max:655360 /path/to/foo args -- description
**Old colon-separated syntax (legacy):**
service [...] <...> cgroup.maint:cpu.max:10000,mem.max:655360 /path/to/foo args -- description
You can also apply options to the current default cgroup (without changing it)
using the `cgroup:options` syntax:
service [...] <...> cgroup:cpu.max:10000,mem.max:655360 /path/to/foo args -- description
Both syntaxes work identically. The new comma-separated syntax is recommended
as it's more consistent with other option parsing in Finit.
Note the `mem.` exception to the rule: every cgroup setting maps directly to
cgroup v2 syntax. I.e., `cpu.max` maps to the file `/sys/fs/cgroup/maint/foo/cpu.max`.
There is no filtering, except for expanding the shorthand `mem.` to `memory.`.
If the file is not available, either the cgroup controller is not available
in your Linux kernel, or the name is misspelled.
### Overriding Cgroup Leaf Names ### Overriding Cgroup Leaf Names
@@ -108,40 +95,66 @@ configuration filename (without the `.conf` extension). For example, a
service defined in `system/10-hotplug.conf` would create a cgroup at service defined in `system/10-hotplug.conf` would create a cgroup at
`/sys/fs/cgroup/system/10-hotplug/` by default. `/sys/fs/cgroup/system/10-hotplug/` by default.
To use a more descriptive name (recommended for clarity), you can specify To use a more descriptive name (recommended for clarity), set `name`
`name:` in the cgroup directive: inside the cgroup block:
service [...] <...> cgroup.system,name:udevd /lib/systemd/systemd-udevd -- Device event daemon service udevd {
description = "Device event daemon"
cgroup system { name = "udevd" }
command = "/lib/systemd/systemd-udevd"
}
This creates the cgroup at `/sys/fs/cgroup/system/udevd/` instead. This creates the cgroup at `/sys/fs/cgroup/system/udevd/` instead.
The syntax supports combining the name override with other options: `name` combines with any other setting:
service [...] <...> cgroup.system,name:udevd,cpu.max:10000 /lib/systemd/systemd-udevd -- Device event daemon service udevd {
description = "Device event daemon"
cgroup system {
name = "udevd"
cpu.max = 10000
}
command = "/lib/systemd/systemd-udevd"
}
Or with delegation: Or with delegation:
service [2345] @podman:podman \ service podman {
cgroup.containers,name:podman,delegate,mem.max:4G \ description = "Podman API"
/usr/bin/podman system service -- Podman API runlevel = "2345"
user = "podman"
group = "podman"
cgroup containers {
name = "podman"
delegate = true
memory.max = 4G
}
command = "/usr/bin/podman system service"
}
A daemon using `SCHED_RR` currently needs to run outside the default cgroups. A daemon using `SCHED_RR` currently needs to run outside the default
cgroups.
service [...] <...> cgroup.root /path/to/daemon arg -- Real-Time process service rt {
description = "Real-Time process"
cgroup root {}
command = "/path/to/daemon arg"
}
Cgroup Delegation Cgroup Delegation
----------------- -----------------
For services that need to create their own child cgroups (container runtimes For services that need to create their own child cgroups (container runtimes
like Docker, Podman, systemd-nspawn, LXC), use the `delegate` option: like Docker, Podman, systemd-nspawn, LXC), set `delegate`:
service [2345] @dockerd:dockerd \ service dockerd {
cgroup.system,delegate /usr/bin/dockerd -- Docker daemon description = "Docker daemon"
runlevel = "2345"
Or with the old colon syntax: user = "dockerd"
group = "dockerd"
service [2345] @dockerd:dockerd \ cgroup system { delegate = true }
cgroup.system:delegate /usr/bin/dockerd -- Docker daemon command = "/usr/bin/dockerd"
}
This allows the container runtime to: This allows the container runtime to:
@@ -159,32 +172,46 @@ When delegation is enabled, Finit:
**Requirements:** **Requirements:**
- The service should specify `@user:group` for proper ownership - The service should specify `user` and `group` for proper ownership
- Controllers are delegated from the parent cgroup - Controllers are delegated from the parent cgroup
**Example with additional config (new syntax):** **Example with additional config:**
service [2345] @podman:podman \ service podman {
cgroup.containers,delegate,mem.max:4G \ description = "Podman API"
/usr/bin/podman system service -- Podman API runlevel = "2345"
user = "podman"
group = "podman"
cgroup containers {
delegate = true
memory.max = 4G
}
command = "/usr/bin/podman system service"
}
**Or with old syntax:** This delegates the cgroup while also setting a 4GB memory limit.
service [2345] @podman:podman \
cgroup.containers:delegate,mem.max:4G \
/usr/bin/podman system service -- Podman API
Both examples delegate the cgroup while also setting a 4GB memory limit.
**Container template example:** **Container template example:**
Here's a real-world example from [Infix OS](https://github.com/kernelkit/infix) Here's a real-world example from [Infix OS](https://github.com/kernelkit/infix)
for running rootful podman container instances using delegation: for running rootful podman container instances using delegation:
sysv log:prio:local1,tag:%i kill:30 pid:!/run/container:%i.pid \ sysv container:%i {
pre:0,/usr/sbin/container cleanup:0,/usr/sbin/container \ description = "container %i"
cgroup.system,delegate \ runlevel = "2345"
[2345] <!> :%i container -n %i -- container %i reload-signal = "none"
pidfile = "/run/container:%i.pid"
stop-timeout = 30
log { priority = "local1" identity = "%i" }
exec-start-pre = "/usr/sbin/container"
exec-start-pre-timeout = 0
exec-cleanup = "/usr/sbin/container"
exec-cleanup-timeout = 0
cgroup system { delegate = true }
command = "container -n %i"
}
This template uses `sysv` type with delegation, demonstrating that cgroup This template uses `sysv` type with delegation, demonstrating that cgroup
delegation works with different service types, not just `service`. delegation works with different service types, not just `service`.
@@ -200,7 +227,7 @@ Initially, the service process runs directly in the cgroup root:
Once the container runtime creates child cgroups (e.g., `libpod-*/`), cgroups v2 Once the container runtime creates child cgroups (e.g., `libpod-*/`), cgroups v2
enforces the "no internal processes" rule. When Finit detects this (`EBUSY` error), enforces the "no internal processes" rule. When Finit detects this (`EBUSY` error),
it automatically creates an `supervisor/` subdirectory and moves service-related it automatically creates a `supervisor/` subdirectory and moves service-related
processes there: processes there:
/sys/fs/cgroup/system/container@web/ /sys/fs/cgroup/system/container@web/
+7 -6
View File
@@ -6,15 +6,16 @@ and any `*.conf`, was added. It is worth noting that these are global
and *shared with all* services -- the only way to have a service-local and *shared with all* services -- the only way to have a service-local
environment is detailed in [Services Environment](service-env.md). environment is detailed in [Services Environment](service-env.md).
The syntax for global environment variables is straight forward. In Global environment variables go in an `environment` block, `env` for
Finit v4.4 the `set` keyword was added for completeness, but the old short:
syntax (without the `set ` prefix) is still honored:
set foo=bar environment {
set baz="qux" foo = "bar"
baz = "qux"
}
On reload of .conf files, all tracked environment variables are cleared On reload of .conf files, all tracked environment variables are cleared
so if `foo=bar` is removed from `finit.conf`, or any `finit.d/*.conf` so if `foo` is removed from `finit.conf`, or any `finit.d/*.conf`
file, it will no longer be used by Finit or any new (!) started file, it will no longer be used by Finit or any new (!) started
run/tasks or services. The environment of already started processes can run/tasks or services. The environment of already started processes can
not be changed. not be changed.
+8 -8
View File
@@ -70,12 +70,12 @@ unique group, where files within each group are sorted alphabetically.
/etc/finit.d/enabled/1-aaa.conf /etc/finit.d/enabled/1-aaa.conf
/etc/finit.d/enabled/1-abc.conf /etc/finit.d/enabled/1-abc.conf
The resulting combined configuration is read line by line, each `run`, The resulting combined configuration is read in order, each `run`,
`task`, and `service` added to an ordered list that ensures they are `task`, and `service` added to an ordered list that ensures they are
started in the same order. This is important because of the blocking started in the same order. This is important because of the blocking
properties of the `run` statement. For an example on the relation of properties of `run`. For an example on the relation of `service` and
`service` and `run` statements, and dependency handling between them, `run`, and dependency handling between them, see
see [Conditional Loading](services.md#conditional-loading), below. [Conditional Loading](services.md#conditional-loading), below.
> [!NOTE] > [!NOTE]
> The names `finit.conf` and `finit.d/` are only defaults. They can be > The names `finit.conf` and `finit.d/` are only defaults. They can be
@@ -84,7 +84,7 @@ see [Conditional Loading](services.md#conditional-loading), below.
> >
> They can also be overridden from the [kernel command line](../cmdline.md) > They can also be overridden from the [kernel command line](../cmdline.md)
> using: `-- finit.config=/etc/bar.conf` and in that file use the > using: `-- finit.config=/etc/bar.conf` and in that file use the
> top-level configuration directive `rcsd /path/to/finit.d`. > top-level setting `rcsd = "/path/to/finit.d"`.
Filesystem Layout Filesystem Layout
----------------- -----------------
@@ -180,7 +180,7 @@ document [Finit Services](../service.md).
Alternate finit.d/ Alternate finit.d/
------------------ ------------------
**Syntax:** `rcsd /path/to/finit.d` **Syntax:** `rcsd = "/path/to/finit.d"`
The Finit rcS.d directory is set at compile time with: The Finit rcS.d directory is set at compile time with:
@@ -194,7 +194,7 @@ configurations, starting with the kernel command line option:
This file in turn can use the `rcsd` directive to tell Finit to use This file in turn can use the `rcsd` directive to tell Finit to use
another set of .conf files, e.g.: another set of .conf files, e.g.:
rcsd /etc/factory.d rcsd = "/etc/factory.d"
> [!NOTE] > [!NOTE]
> This directive is only available from the top-level bootstrap .conf > This directive is only available from the top-level bootstrap .conf
@@ -203,6 +203,6 @@ another set of .conf files, e.g.:
Including Finit Configs Including Finit Configs
------------------------ ------------------------
**Syntax:** `include <CONF>` **Syntax:** `include("CONF")`
Include another configuration file. Absolute path required. Include another configuration file. Absolute path required.
+98 -13
View File
@@ -6,24 +6,109 @@ the navigation menu.
Configuration File Syntax Configuration File Syntax
-------------------------- --------------------------
The file format is line based, empty lines and comments, lines starting A `.conf` file is a series of blocks. Every setting is a key inside
with `#`, are ignored. A configuration directive starts with a keyword one, so nothing has to be remembered by position:
followed by a space and the rest of the line is treated as the value.
As of Finit v4.4, configuration directives can be broken up in multiple
lines using the continuation character `\`, and trailing comments are
also allowed. Example:
```aconf ```aconf
# Escape \# chars if you want them literal in, e.g., descriptions service sysklogd {
service name:sysklogd [S123456789] \ description = "System log daemon"
env:-/etc/default/sysklogd \ runlevel = "S123456789"
syslogd -F $SYSLOGD_ARGS \ envfile = "-/etc/default/sysklogd"
-- System log daemon \# 1 # Comments allowed command = "syslogd -F $SYSLOGD_ARGS"
}
```
Values are quoted strings, bare words, or integers. Lists use braces,
and a block may carry a title, which becomes the service identity:
```aconf
# shell comment
// C++ comment
/* C comment */
key = "value" # string
number = 20 # integer
flag = true # boolean
list = { "one", "two" } # string list
block title { key = "value" } # titled section
path = "${HOME}/thing" # environment expansion
include("/etc/finit.d/extra.conf") # include another file
```
Bare words work wherever a string is expected, so `memory.max = 65M`
and `restart = always` need no quotes.
### Two conventions
Keys are kebab-case, never `snake_case` or `CamelCase`: `restart-sec`,
`stop-timeout`, `reboot-watchdog`.
Keys that take a list are plural: `conditions`, `conflicts`,
`capabilities`, `modules`, `extra-groups`. Two imperatives keep their
singular form because they are verbs rather than nouns: `mknod` and
`include`.
### Short forms
Nine keys have an alias. An alias may abbreviate the canonical name,
or preserve the spelling the line-based format used; it never renames.
| Canonical | Alias | Canonical | Alias |
|---|---|---|---|
| `description` | `desc` | `manual-start` | `manual` |
| `conditions` | `cond` | `remain-after-exit` | `remain` |
| `capabilities` | `caps` | `stop-signal` | `halt` |
| `modules` | `mod` | `stop-timeout` | `kill` |
| `envfile` | `env` | | |
### Optional paths
A leading `-` on a path means carry on if it is not there:
```aconf
service foo {
envfile = "-/etc/default/foo" # skip the file if missing
command = "-/usr/sbin/foo" # skip the whole block if missing
}
```
That is the only mark meaning "optional". Two others mean something
else: `!` on `runlevel` inverts the set, e.g. `runlevel = "!12345"`,
and `~` on a condition propagates a reload from the service it names.
Both are covered where they apply.
Both Formats Are Read
---------------------
Finit also reads the line-based format it has always used, where a
stanza is a keyword followed by values whose meaning comes from their
position:
```aconf
service [S123456789] env:-/etc/default/sysklogd syslogd -F $SYSLOGD_ARGS -- System log daemon
```
That format still works and is not going away. It is frozen at the
Finit 4.x feature set, so new settings appear only in the block format,
and the documentation is written in blocks throughout.
There is no new file extension and no marker line. Every file is still
`*.conf`, and Finit decides which format a file is in by reading it: if
it parses as blocks it is a block file, otherwise it goes to the
line-based parser. A file is one format or the other, never a mix.
Because the two are told apart before either parser commits, a typo
reports its own file and line instead of being mistaken for the other
format:
```
parse error: /etc/finit.d/foo.conf:3: no such option 'commnad'
``` ```
The .conf files `/etc/finit.conf` and `/etc/finit.d/*` support many The .conf files `/etc/finit.conf` and `/etc/finit.d/*` support many
directives. Some are restricted, e.g., only available at bootstrap, settings. Some are restricted, e.g., only available at bootstrap,
runlevel `S`. Read on in [Files & Layout](files.md) for more on how runlevel `S`. Read on in [Files & Layout](files.md) for more on how
to structure your .conf files. to structure your .conf files.
+36 -17
View File
@@ -1,13 +1,13 @@
General Logging General Logging
=============== ===============
**Syntax:** `log size:200k count:5` **Syntax:** `log { size = 200k count = 5 }`
Log rotation for run/task/services using the `log` sub-option with Log rotation for run/task/services that redirect output to a log file
redirection to a log file. Global setting, applies to all services. with their own `log` block. Global setting, applies to all services.
The size can be given as bytes, without a specifier, or in `k`, `M`, The size can be given as bytes, without a specifier, or in `k`, `M`,
or `G`, e.g. `size:10M`, or `size:3G`. A value of `size:0` disables or `G`, e.g. `size = 10M`, or `size = 3G`. A value of `size = 0` disables
log rotation. The default is `200k`. log rotation. The default is `200k`.
The count value is recommended to be between 1-5, with a default 5. The count value is recommended to be between 1-5, with a default 5.
@@ -17,33 +17,48 @@ size limit is reached.
Redirecting Output Redirecting Output
------------------ ------------------
The `run`, `task`, and `service` stanzas also allow the keyword `log` to The `run`, `task`, and `service` blocks take a `log` block of their own,
redirect `stderr` and `stdout` of the application to a file or syslog redirecting `stderr` and `stdout` of the application to a file or syslog
using the native `logit` tool. This is useful for programs that do not using the native `logit` tool. This is useful for programs that do not
support syslog on their own, which is sometimes the case when running support syslog on their own, which is sometimes the case when running
in the foreground. in the foreground.
The full syntax is: An empty block means syslog with the defaults, and three keys adjust it:
log:/path/to/file | Setting | Description |
log:prio:facility.level,tag:ident |---|---|
log:console | `file` | Write to this path instead of syslog |
log:null | `priority` | Syslog `facility.level`, default `daemon.info` |
log | `identity` | Syslog tag, default the basename of the command |
Default `prio` is `daemon.info` and default `tag` is the basename of the `/dev/console` and `/dev/null` are spelled as the paths they are:
service or run/task command.
service foo { log { } command = "foo" } # syslog
service foo { log { file = "/var/log/foo" } command = "foo" } # a file
service foo { log { file = "/dev/console" } command = "foo" } # console
service foo { log { file = "/dev/null" } command = "foo" } # discard
> [!NOTE]
> A `log` block at file scope is a different setting -- that one is the
> global rotation above, and it takes only `size` and `count`.
Log rotation is controlled using the global `log` setting. Log rotation is controlled using the global `log` setting.
**Example:** **Example:**
service log:prio:user.warn,tag:ntpd /sbin/ntpd pool.ntp.org -- NTP daemon service ntpd {
description = "NTP daemon"
log {
priority = "user.warn"
identity = "ntpd"
}
command = "/sbin/ntpd pool.ntp.org"
}
Output Buffering Output Buffering
---------------- ----------------
When using the `log` directive, Finit redirects the service's stdout and When using the `log` block, Finit redirects the service's stdout and
stderr to a pipe connected to a logger process. Programs detect this as stderr to a pipe connected to a logger process. Programs detect this as
non-interactive output (i.e., `isatty()` returns false) and typically non-interactive output (i.e., `isatty()` returns false) and typically
switch from line-buffered to fully-buffered mode. switch from line-buffered to fully-buffered mode.
@@ -53,7 +68,11 @@ directly, so this is rarely an issue. However, if a service's log
messages appear delayed or batched, you can force line-buffered output messages appear delayed or batched, you can force line-buffered output
by wrapping the command with `stdbuf`: by wrapping the command with `stdbuf`:
service log /usr/bin/stdbuf -oL /path/to/command -- My service service myservice {
description = "My service"
log { }
command = "/usr/bin/stdbuf -oL /path/to/command"
}
The `-oL` option forces line-buffered output, and `-o0` forces unbuffered The `-oL` option forces line-buffered output, and `-o0` forces unbuffered
output. See `stdbuf(1)` for details. output. See `stdbuf(1)` for details.
+13 -6
View File
@@ -47,16 +47,23 @@ system administrator.
The bundled default `rescue.conf` contains nothing more than: The bundled default `rescue.conf` contains nothing more than:
runlevel 1 runlevel = 1
tty [12345] rescue tty rescue {
runlevel = "12345"
rescue = true
}
The `tty` has the `rescue` option set, which works similar to the board The `tty` block has `rescue` set, which works similar to the board
bring-up tty option `notty`. The major difference being that `sulogin` bring-up setting `notty`. The major difference being that `sulogin`
is started to query for root/admin password. If `sulogin` is not found, is started to query for root/admin password. If `sulogin` is not found,
`rescue` behaves like `notty` and gives a plain root shell prompt. `rescue` behaves like `notty` and gives a plain root shell prompt.
If Finit cannot find `/lib/finit/rescue.conf` it defaults to: If Finit cannot find `/lib/finit/rescue.conf` it falls back to a
built-in equivalent, which runs in every runlevel it can:
tty [12345] rescue tty rescue {
runlevel = "12345789"
rescue = true
}
There is no way to exit the *fallback* rescue mode. There is no way to exit the *fallback* rescue mode.
+36 -25
View File
@@ -26,12 +26,20 @@ the lifetime of your system.
Example: Example:
task [S] /lib/console-setup/console-setup.sh task console-setup {
service [S12345] env:-/etc/default/rsyslog rsyslogd -n $RSYSLOGD_ARGS runlevel = "S"
command = "/lib/console-setup/console-setup.sh"
}
service rsyslogd {
runlevel = "S12345"
envfile = "-/etc/default/rsyslog"
command = "rsyslogd -n $RSYSLOGD_ARGS"
}
When bootstrap has completed, Finit moves to runlevel 2. This can be When bootstrap has completed, Finit moves to runlevel 2. This can be
changed in `/etc/finit.conf` using the `runlevel N` directive, or by a changed in `/etc/finit.conf` with `runlevel = N`, or by a script running
script running in runlevel S that calls, e.g., `initctl runlevel 9`. in runlevel S that calls, e.g., `initctl runlevel 9`.
The latter is useful if startup scripts detect problems outside of The latter is useful if startup scripts detect problems outside of
Finit's control, e.g., critical services/devices missing or hardware Finit's control, e.g., critical services/devices missing or hardware
problems. problems.
@@ -44,7 +52,7 @@ complete before proceeding to 2.
Finit first stops everything that is not allowed to run in 2, and then Finit first stops everything that is not allowed to run in 2, and then
brings up networking. Networking is expected to be available in all brings up networking. Networking is expected to be available in all
runlevels except: S, 1 (single user level), 6, and 0. Networking is runlevels except: S, 1 (single user level), 6, and 0. Networking is
enabled either by the `network script` directive, or if you have an enabled either by `network = "script"`, or if you have an
`/etc/network/interfaces` file, Finit calls `ifup -a` -- at the very `/etc/network/interfaces` file, Finit calls `ifup -a` -- at the very
least the loopback interface is brought up. least the loopback interface is brought up.
@@ -58,7 +66,7 @@ least the loopback interface is brought up.
Runlevel Configuration Runlevel Configuration
---------------------- ----------------------
**Syntax:** `runlevel <N>` **Syntax:** `runlevel = N`
The system runlevel to go to after bootstrap (S) has completed. `N` is The system runlevel to go to after bootstrap (S) has completed. `N` is
the runlevel number 0-9, where 6 is reserved for reboot and 0 for halt. the runlevel number 0-9, where 6 is reserved for reboot and 0 for halt.
@@ -76,11 +84,11 @@ Finit disables networking in this mode.
Networking Networking
---------- ----------
**Syntax:** `network <PATH>` **Syntax:** `network = "PATH"`
Script or program to bring up networking, with optional arguments. Script or program to bring up networking, with optional arguments.
Deprecated. We recommend using dedicated task/run stanzas per runlevel, Deprecated. We recommend using dedicated task/run blocks per runlevel,
or `/etc/network/interfaces` if you have a system with `ifupdown`, like or `/etc/network/interfaces` if you have a system with `ifupdown`, like
Debian, Ubuntu, Linux Mint, or an embedded BusyBox system. Debian, Ubuntu, Linux Mint, or an embedded BusyBox system.
@@ -90,7 +98,7 @@ Debian, Ubuntu, Linux Mint, or an embedded BusyBox system.
System Hostname System Hostname
--------------- ---------------
**Syntax:** `host <NAME>`, or `hostname <NAME>` **Syntax:** `hostname = "NAME"`
Set system hostname to NAME, unless `/etc/hostname` exists in which case Set system hostname to NAME, unless `/etc/hostname` exists in which case
the contents of that file is used. the contents of that file is used.
@@ -103,10 +111,16 @@ Deprecated. We recommend using `/etc/hostname` instead.
Kernel Modules Kernel Modules
-------------- --------------
**Syntax:** `module <MODULE> [ARGS]` **Syntax:** `modules = { "MODULE [ARGS]", ... }`, alias `mod`
Load a kernel module, with optional arguments. Similar to `insmod` Load kernel modules, each with optional arguments. Similar to the
command line tool. `insmod` command line tool.
modules = { "button", "evdev", "softdog" }
> [!NOTE]
> A list cannot hold comments; the lexer reads the entries after a `#`
> regardless. Put commented-out candidates above the list.
Deprecated, there is both a `modules-load.so` and a `modprobe.so` plugin Deprecated, there is both a `modules-load.so` and a `modprobe.so` plugin
that can handle module loading better. The former supports loading from that can handle module loading better. The former supports loading from
@@ -122,9 +136,9 @@ BusyBox mdev tool, add to `/etc/mdev.conf`:
Resource Limits Resource Limits
--------------- ---------------
**Syntax:** `rlimit [hard|soft] RESOURCE <LIMIT|unlimited>` **Syntax:** `rlimit { RESOURCE = LIMIT }`, with `soft.` or `hard.` prefix
Set the hard or soft limit for a resource, or both if that argument is Set the hard or soft limit for a resource, or both if the prefix is
omitted. `RESOURCE` is the lower-case `RLIMIT_` string constants from omitted. `RESOURCE` is the lower-case `RLIMIT_` string constants from
`setrlimit(2)`, without prefix. E.g. to set `RLIMIT_CPU`, use `cpu`. `setrlimit(2)`, without prefix. E.g. to set `RLIMIT_CPU`, use `cpu`.
@@ -134,23 +148,20 @@ or the kernel `/proc/PID/limits` file, for details. Finit versions
before v3.1 used `infinity` for `unlimited`, which is still supported, before v3.1 used `infinity` for `unlimited`, which is still supported,
albeit deprecated. albeit deprecated.
# No process is allowed more than 8MB of address space rlimit {
rlimit hard as 8388608 hard.as = 8388608 # no more than 8MB of address space
soft.core = unlimited # core dumps may be arbitrarily large
# Core dumps may be arbitrarily large cpu = 10 # soft & hard = 10 sec
rlimit soft core infinity }
# CPU limit for all services, soft & hard = 10 sec
rlimit cpu 10
`rlimit` can be set globally, in `/etc/finit.conf`, or locally per `rlimit` can be set globally, in `/etc/finit.conf`, or locally per
each `/etc/finit.d/*.conf` read. I.e., a set of task/run/service each `/etc/finit.d/*.conf` read. I.e., a set of task/run/service
stanzas can share the same rlimits if they are in the same .conf. blocks can share the same rlimits if they are in the same .conf.
Miscellaneous Settings Miscellaneous Settings
---------------------- ----------------------
**Syntax:** `reboot-delay <0-60>` **Syntax:** `reboot-delay = 0-60`
Optional delay at reboot (or shutdown or halt) to allow kernel Optional delay at reboot (or shutdown or halt) to allow kernel
filesystem threads to complete after calling `sync(2)` before filesystem threads to complete after calling `sync(2)` before
@@ -168,7 +179,7 @@ sync(2) has been called, twice.
> writing; it can actually take a short time before all the blocks are > writing; it can actually take a short time before all the blocks are
> finally written. > finally written.
**Syntax:** `reboot-watchdog <on|off|true|false|1|0>` **Syntax:** `reboot-watchdog = true|false`
Controls whether the system should reboot via the watchdog timer (WDT) Controls whether the system should reboot via the watchdog timer (WDT)
or directly via the SoC/kernel. When enabled, Finit will: or directly via the SoC/kernel. When enabled, Finit will:
+8 -7
View File
@@ -1,7 +1,7 @@
Run-parts Scripts Run-parts Scripts
----------------- -----------------
**Syntax:** `runparts [progress] [sysv] <DIR>` **Syntax:** `runparts = "DIR"`
Call [run-parts(8)][] on `DIR` to run start scripts. All executable Call [run-parts(8)][] on `DIR` to run start scripts. All executable
files in the directory are called, in alphabetic order. The scripts in files in the directory are called, in alphabetic order. The scripts in
@@ -14,9 +14,10 @@ whatever the next runlevel is set to be (default 2). E.g., generate a
**Options:** **Options:**
- `progress`: display the progress of each script being executed - `runparts-progress = true`: display the progress of each script being
- `sysv`: run only SysV style scripts, i.e., `SNNfoo`, or `KNNbar`, executed
where `NN` is a number (0-99). - `runparts-sysv = true`: run only SysV style scripts, i.e., `SNNfoo`,
or `KNNbar`, where `NN` is a number (0-99).
If global debug mode is enabled, the `runparts` program is also called If global debug mode is enabled, the `runparts` program is also called
with the debug flag. with the debug flag.
@@ -24,7 +25,7 @@ with the debug flag.
**Limitations:** **Limitations:**
Scripts called from `runparts`, or hook scripts (see below), are limited Scripts called from `runparts`, or hook scripts (see below), are limited
in their interaction with Finit. Like the standalone `run` stanza and in their interaction with Finit. Like a standalone `run` block and
the `/etc/rc.local` shell script, Finit waits for their completion the `/etc/rc.local` shell script, Finit waits for their completion
before continuing. None of them can issue commands to start, stop, or before continuing. None of them can issue commands to start, stop, or
restart other services. Also, ensure all your services and programs restart other services. Also, ensure all your services and programs
@@ -39,13 +40,13 @@ either terminate or start in the background or you will block Finit.
It can be beneficial to use `01-name`, `02-othername`, etc., to ensure It can be beneficial to use `01-name`, `02-othername`, etc., to ensure
the scripts are started in that order, e.g., if there is a dependency the scripts are started in that order, e.g., if there is a dependency
order between scripts. Symlinks to existing daemons can talso be used, order between scripts. Symlinks to existing daemons can also be used,
but make sure they daemonize (background) themselves properly, otherwise but make sure they daemonize (background) themselves properly, otherwise
Finit will lock up. Finit will lock up.
If `S[0-9]foo` and `K[0-9]bar` style naming is used, the executable will If `S[0-9]foo` and `K[0-9]bar` style naming is used, the executable will
be called with an extra argument, `start` and `stop`, respectively. be called with an extra argument, `start` and `stop`, respectively.
E.g., `S01foo` will be called as `S01foo start`. Of course, `S01foo` E.g., `S01foo` will be called as `S01foo start`. Of course, `S01foo`
and `K01foo` may be a symlink to to `another/directory/foo`. and `K01foo` may be a symlink to `another/directory/foo`.
[run-parts(8)]: http://manpages.debian.org/cgi-bin/man.cgi?query=run-parts [run-parts(8)]: http://manpages.debian.org/cgi-bin/man.cgi?query=run-parts
+11 -4
View File
@@ -13,7 +13,12 @@ file `/etc/default/foo`. Like this:
* `/etc/finit.conf`: * `/etc/finit.conf`:
service [2345] env:-/etc/default/foo foo -n $FOO_OPTIONS -- Example foo daemon service foo {
description = "Example foo daemon"
runlevel = "2345"
envfile = "-/etc/default/foo"
command = "foo -n $FOO_OPTIONS"
}
Here the service `foo` is started with `-n`, to make sure it runs in the Here the service `foo` is started with `-n`, to make sure it runs in the
foreground, and the with the options found in the environment file. With foreground, and the with the options found in the environment file. With
@@ -22,6 +27,8 @@ the `ps` command we can see that the process is started with:
foo -n --extra-arg=bar -s -x foo -n --extra-arg=bar -s -x
> [!NOTE] > [!NOTE]
> The leading `-` in `env:` determines if Finit should treat a missing > The leading `-` on `envfile` determines if Finit should treat a
> environment file as blocking the start of the service or not. When > missing environment file as blocking the start of the service or not.
> `-` is used, a missing environment file does *not* block the start. > When `-` is used, a missing environment file does *not* block the
> start. The same mark works on `command`, where it means carry on if
> the binary is missing.
+186 -96
View File
@@ -1,21 +1,22 @@
Service Options Service Options
=============== ===============
The run/task/tty/service/sysv directives take modifiers, or options, to The `run`, `task`, `tty`, `service` and `sysv` blocks take settings as
control their behavior. This section lists them with their limitations. keys. This section lists them with their limitations.
All modifiers must be placed between the directive and its command.
The name of a service, shown by the `initctl` tool, defaults to the The name of a service, shown by the `initctl` tool, is the block title.
basename of the service executable. It can be changed with the `name:` It defaults to the basename of the executable if the title is omitted:
option:
name:<service-name> service sshd {
command = "/usr/sbin/sshd -D"
}
For multiple instances of a service, with the same `name`, set the For multiple instances of a service, with the same name, add an
identifier `:ID` to prevent Finit from replacing previous instances: identifier after a colon to prevent Finit from replacing previous
instances:
service name:ssdp :eth1 ssdpd eth1 -- Windows discovery on eth1 service ssdp:eth1 { command = "ssdpd eth1" }
service name:ssdp :eth2 ssdpd eth2 -- Windows discovery on eth2 service ssdp:eth2 { command = "ssdpd eth2" }
The [`initctl`](../initctl.md) tool will list these two services as: The [`initctl`](../initctl.md) tool will list these two services as:
@@ -23,107 +24,194 @@ The [`initctl`](../initctl.md) tool will list these two services as:
- ssdp:eth2 - ssdp:eth2
Conflicting services that must be prevented from starting, use the Conflicting services that must be prevented from starting, use the
`conflict:` option: `conflicts` key:
service [S12345789] udevd -- Device event management daemon service udevd { command = "udevd" }
run [S] conflict:udevd mdev -s -- Populating device tree run mdev {
runlevel = "S"
conflicts = { "udevd" }
command = "mdev -s"
}
Multiple conflicting services can be separated using `,`: The list may name several, and an instance is named the same way it is
declared:
service :1 abc service abc:1 { command = "abc 1" }
service :2 abc service abc:2 { command = "abc 2" }
service conflict:abc:1,abc:2 cde service cde { conflicts = { "abc:1", "abc:2" } command = "cde" }
If a service should not be automatically started, it can be configured If a service should not be automatically started, set `manual-start`.
as manual with the optional `manual` argument. The service can then be It can then be started at any time with `initctl start <service>`:
started at any time by running `initctl start <service>`.
manual:yes service lldpd {
manual-start = true
command = "/usr/sbin/lldpd -d"
}
Other run/task/service options are: Other run/task/service settings are:
* `caps:...` -- see the [Linux Capabilities](capabilities.md) section * `capabilities` -- see the [Linux Capabilities](capabilities.md) section
* `cgroup.NAME[,opts]` or `cgroup:opts` -- see the [Cgroups](cgroups.md) section * `cgroup NAME {}` -- see the [Cgroups](cgroups.md) section
* `env:[-]/path/to/env` -- see the [Service Environment](service-env.md) section * `envfile` -- see the [Service Environment](service-env.md) section
* `log:...` -- see [Redirecting Output](logging.md#redirecting-output) * `log {}` -- see [Redirecting Output](logging.md#redirecting-output)
* `tty:<dev>` -- see [Controlling TTY](tty.md#controlling-tty) * `tty` -- see [Controlling TTY](tty.md#controlling-tty)
* `nowarn` -- see [Conditional Loading](services.md#conditional-loading) * `notify` -- see [Service Synchronization](service-sync.md)
* `notify:...` -- see [Service Synchronization](service-sync.md) * `if` -- see [Conditional Execution](services.md#conditional-execution)
* `if:...` -- see [Conditional Execution](services.md#conditional-execution) * `type = "forking"` -- see description of the [service](services.md) block
* `type:forking` -- see description of the [service](services.md) directive * a leading `-` on `command` -- see
[Conditional Loading](services.md#conditional-loading)
As mentioned previously, services are automatically started, restarted, Restarting
and stopped, depending on the configuration and conditions. Within the ----------
confines of that the following options are available:
* `restart:NUM` -- number of times Finit tries to restart a crashing Services are automatically started, restarted, and stopped, depending
service, default: 10, max: 255. When this limit is reached the on the configuration and conditions. Within the confines of that the
following settings are available:
* `restart-max = NUM` -- number of times Finit tries to restart a
crashing service, default: 10. When this limit is reached the
service is marked *crashed* and must be restarted manually with service is marked *crashed* and must be restarted manually with
`initctl restart NAME` `initctl restart NAME`
* `restart_sec:SEC` -- number of seconds before Finit tries to restart * `restart-sec = SEC` -- number of seconds before Finit tries to
a crashing service, default: 2 seconds for the first five retries, restart a crashing service. The default is 2 seconds for the first
then back-off to 5 seconds. The maximum of this configured value half of `restart-max` attempts, then a back-off to 5 seconds -- with
and the above (2 and 5) will be used the default `restart-max` that is the first five retries. The
* `restart:always` -- no upper limit on the number of times Finit greater of this configured value and the back-off is used
tries to restart a crashing service. Same as `restart:-1` * `restart = "always"` -- no upper limit on the number of times Finit
* `norestart` -- dont restart on failures, same as `restart:0` tries to restart a crashing service
* `respawn` -- bypasses the `restart` mechanism completely, allows * `restart = "never"` -- do not restart on failures. `false` is
endless restarts. Useful in many use-cases, but not what `service` accepted as a synonym, and `true` selects the default policy
was originally designed for so not the default behavior * `respawn = true` -- bypasses the `restart` mechanism completely,
* `remain:yes` -- for `run` and `task` only. Prevents the task from allowing endless restarts. Exiting is treated as normal work
re-running on runlevel re-entry and ensures the `post:` script runs rather than failure, so the service is restarted at once instead of
when the task is explicitly stopped or leaves its valid runlevels. being counted and delayed. This is how a `tty` behaves; it is not
Similar to systemd's `RemainAfterExit=yes`. See [Task and Run](task-and-run.md) what `service` was designed for, so it is not the default
for more details * `remain-after-exit = true` -- for `run` and `task` only. Prevents
* `oncrash:reboot` -- when all retries have failed, and the service the task from re-running on runlevel re-entry and ensures the
has *crashed*, if this option is set the system is rebooted `exec-stop-post` script runs when the task is explicitly stopped or
* `oncrash:script` -- similarly, but instead of rebooting, call the leaves its valid runlevels. This is systemd's `RemainAfterExit=`.
`post:script` action with exit code `crashed`, see below See [Task and Run](task-and-run.md) for more details
* `reload:'script [args]'` -- some services do not support `SIGHUP` but * `oncrash = "reboot"` -- when all retries have failed, and the
may have other ways to update the configuration of a running daemon. service has *crashed*, the system is rebooted
When `reload:script` is defined it is preferred over `SIGHUP`. Like * `oncrash = "script"` -- similarly, but instead of rebooting, call
systemd, Finit sets `$MAINPID` as a convenience to scripts, which in the `exec-stop-post` script with exit code `crashed`, see below
effect also allow `reload:'kill -HUP $MAINPID'`
* `stop:'script [args]'` -- some services may require alternate methods
to be stopped. If a `stop:script` is defined it is preferred over
`SIGTERM` and `stop`, for `service` and `sysv`, respectively.
Similar to `reload:script`, Finit sets `$MAINPID`
> [!CAUTION] Service directories
> Both `reload:script` and `stop:script` are called as PID 1, without -------------------
> any timeout! Meaning, it is up to you to ensure the script is not
> blocking for seconds at a time or never terminates.
When stopping a service (run/task/sysv/service), either manually or when Five settings ask Finit to create a directory for the service before it
moving to another runlevel, Finit starts by sending `SIGTERM`, to allow starts, owned by its `user` and `group`, mode 0755. The value is a
the process to shut down gracefully (unless a `stop:'script'` is used). directory name, resolved under a fixed base -- absolute paths and `..`
However, if the process has not been collected within 3 seconds, Finit are refused:
will send `SIGKILL`. To stop the process using a different signal than
`SIGTERM`, use `halt:SIGNAL`, e.g., `halt:SIGPWR`. To change the delay
between the stop signal and KILL, use the option `kill:<1-60>`, e.g.,
`kill:10` to wait 10 seconds before sending `SIGKILL`.
Services, including the `sysv` variant, support pre/post/ready and | Setting | Base | Environment variable |
cleanup scripts: |---|---|---|
| `runtime-dir` | `/run` | `RUNTIME_DIRECTORY` |
| `state-dir` | `/var/lib` | `STATE_DIRECTORY` |
| `cache-dir` | `/var/cache` | `CACHE_DIRECTORY` |
| `logs-dir` | `/var/log` | `LOGS_DIRECTORY` |
| `config-dir` | `/etc` | `CONFIGURATION_DIRECTORY` |
* `pre:[0-3600,]script` -- called before the sysv/service is stated Each resolved path is exported to the process environment under the
* `post:[0-3600,]script` -- called after the sysv/service has stopped listed name, the same names systemd uses for `RuntimeDirectory=` and
* `ready:[0-3600,]script` -- called when the sysv/service is ready friends. As in systemd, `config-dir` is the odd one out: it is created
* `cleanup:[0-3600,]script` -- called when run/task/sysv/service is removed but never chowned.
The optional number (0-3600) is the timeout before Finit kills the Each takes a matching `-mode`, e.g. `runtime-dir-mode = 0700`, default
script, it defaults to the kill delay value and can be disabled by 0755. Modes are octal, with the leading zero.
setting it to zero. These scripts run as the same `@USER:GROUP` as the
service itself, with any `env:file` sourced. The scripts are executed The mode of the named directory is locked down again on every start.
from the `$HOME` of the given user. The scripts are not called with any Its contents are left alone as long as the owner is right; if the owner
argument, but get a set of environment variables: has drifted, everything under it is chowned back.
The runtime directory is removed again when the service stops, after
any `exec-stop-post` script has run; `/run` is a tmpfs so it would not
survive a reboot anyway. The other four persist. A completed `run` or
`task` counts as stopped, unless `remain-after-exit` keeps it alive
until stopped for real. `runtime-dir-preserve` adjusts this, same
values as systemd's `RuntimeDirectoryPreserve=`:
* `"no"` -- removed when the service stops, the default
* `"restart"` -- kept across restarts, removed on a real stop
* `"yes"` -- never removed
This is what lets a service drop privileges and still create, and
later touch, its own PID file:
service ntpd {
user = "ntp"
group = "ntp"
runtime-dir = "ntpd"
pidfile = "/run/ntpd/ntpd.pid"
command = "/usr/sbin/ntpd -n -p /run/ntpd/ntpd.pid"
}
These settings exist only in the block format.
Stopping and reloading
----------------------
When stopping a service, either manually or when moving to another
runlevel, Finit starts by sending `SIGTERM` to let the process shut
down gracefully. If it has not been collected within 3 seconds,
`SIGKILL` follows. Both ends of that are configurable:
* `stop-signal = "SIGPWR"` -- send this instead of `SIGTERM`
* `stop-timeout = 10` -- seconds to wait before `SIGKILL`, 1-300
Some services need more than a signal:
* `exec-stop = "script [args]"` -- run instead of sending the stop
signal, and instead of `stop` for `sysv`
* `exec-reload = "script [args]"` -- run instead of sending `SIGHUP`.
Like systemd, Finit sets `$MAINPID` as a convenience to scripts,
which in effect also allows `exec-reload = "kill -HUP $MAINPID"`
If a daemon cannot be reloaded with a signal at all, say so and Finit
restarts it instead:
service dropbear {
reload-signal = "none"
command = "/usr/sbin/dropbear -R -F"
}
`SIGHUP` is the default and the only other accepted value, with or
without the `SIG` prefix and in any case.
Scripts
-------
Services, including the `sysv` variant, support six lifecycle scripts:
* `exec-start-pre` -- called before the sysv/service is started
* `exec-start-ready` -- called when the sysv/service is ready
* `exec-stop` -- called instead of the stop signal
* `exec-stop-post` -- called after the sysv/service has stopped
* `exec-reload` -- called instead of `SIGHUP`
* `exec-cleanup` -- called when run/task/sysv/service is removed
Each takes a matching `-timeout`, in seconds, 0-3600, after which Finit
kills the script. It defaults to the `stop-timeout` value and can be
disabled by setting it to zero:
service foo {
exec-start-pre = "/etc/foo/pre.sh"
exec-start-pre-timeout = 10
command = "/usr/sbin/foo"
}
These scripts run as the same user and group as the service itself,
with any `envfile` sourced. They are executed from the `$HOME` of the
given user. The scripts are not called with any argument, but get a
set of environment variables:
* `SERVICE_IDENT=foo:1` * `SERVICE_IDENT=foo:1`
* `SERVICE_NAME=foo` * `SERVICE_NAME=foo`
* `SERVICE_ID=1` * `SERVICE_ID=1`
The `post:script` is called with an additional set of environment The `exec-stop-post` script is called with an additional set of
variables. Yes, the text is correct, the naming was an accident: environment variables. Yes, the text is correct, the naming was an
accident:
- `EXIT_CODE=[exited,signal,crashed]`: normal exit, signaled, or - `EXIT_CODE=[exited,signal,crashed]`: normal exit, signaled, or
crashed crashed
@@ -131,11 +219,13 @@ variables. Yes, the text is correct, the naming was an accident:
the program, if it exited normally, or the signal name (`HUP`, the program, if it exited normally, or the signal name (`HUP`,
`TERM`, etc.) if it exited due to signal `TERM`, etc.) if it exited due to signal
When a run/task/sys/service is removed (disable + reload) it is first When a run/task/sysv/service is removed (disable + reload) it is first
stopped and then removed from the runlevel. The `post:script` always stopped and then removed from the runlevel. The `exec-stop-post`
runs when the process has stopped, and the `cleanup:script` runs when script always runs when the process has stopped, and `exec-cleanup`
the the stanza has been removed from the runlevel. runs when the block has been removed from the runlevel.
> [!IMPORTANT] > [!IMPORTANT]
> These script actions are intended for setup, cleanup, and readiness > These script actions are intended for setup, cleanup, and readiness
> notification. It is up to the user to ensure the scripts terminate. > notification. A script that outlives its timeout is killed, so pick
> one that suits the work, or set it to zero to opt out and take
> responsibility for the script terminating.
+20 -13
View File
@@ -32,28 +32,35 @@ notification is available, and the native PID file mode of operation is,
as of Finit v4.6 optional, by default it is still enabled, but this can as of Finit v4.6 optional, by default it is still enabled, but this can
be changed in `finit.conf`: be changed in `finit.conf`:
readiness none readiness = "none"
This will be made the default in Finit 5.0. In this mode of operation, This will be made the default in Finit 5.0. In this mode of operation,
every service needs to explicitly declare their readiness notification, every service needs to explicitly declare their readiness notification,
like this: like this:
service notify:pid watchdogd service watchdogd { notify = "pid" command = "watchdogd" }
service notify:systemd foo service foo { notify = "systemd" command = "foo" }
service notify:s6 bar service bar { notify = "s6" command = "bar" }
service notify:none qux service qux { notify = "none" command = "qux" }
The `notify:none` syntax is for completeness in systems which run in The `notify = "none"` setting is for completeness in systems which run
`readiness pid` mode (default). Services declared with `notify:none` in `readiness = "pid"` mode (default). Services declared with
will transition to ready as soon as Finit has started them, e.g., `notify = "none"` will transition to ready as soon as Finit has started
`service/qux/ready`. them, e.g., `service/qux/ready`.
To synchronize two services the following condition can be used: To synchronize two services the following condition can be used:
service notify:pid watchdogd service watchdogd {
service <service/watchdogd/ready> stress-ng --cpu 8 notify = "pid"
command = "watchdogd"
}
For details on the syntax and options, see below. service stress-ng {
conditions = { "service/watchdogd/ready" }
command = "stress-ng --cpu 8"
}
For the full list of conditions, see [Finit Conditions](../conditions.md).
> [!NOTE] > [!NOTE]
> On `initctl reload` conditions are set in "flux", while figuring out > On `initctl reload` conditions are set in "flux", while figuring out
@@ -66,7 +73,7 @@ For details on the syntax and options, see below.
> However, the s6 notify mode does not support this because in s6 you > However, the s6 notify mode does not support this because in s6 you
> are expected to close your notify descriptor after having written > are expected to close your notify descriptor after having written
> `\n`. This means s6 style daemons currently must be stop-started. > `\n`. This means s6 style daemons currently must be stop-started.
> (Declare the service with `<!>` in its condition statement.) > (Declare the service with `reload-signal = "none"`.)
> >
> For default, PID file style readiness notification, daemons are > For default, PID file style readiness notification, daemons are
> expected to either create their PID files, or touch it using > expected to either create their PID files, or touch it using
+10 -5
View File
@@ -7,14 +7,19 @@ use a wrapper shell script to start your service.
The Finit service `.conf` file can be put into `/etc/finit.d/available`, The Finit service `.conf` file can be put into `/etc/finit.d/available`,
so you can control the service using `initctl`. Then use the path to so you can control the service using `initctl`. Then use the path to
the wrapper script in the Finit `.conf` service stanza. The following the wrapper script in the Finit `.conf` service block. The following
example employs a wrapper script in `/etc/start.d`. example employs a wrapper script in `/etc/start.d`.
**Example:** **Example:**
* `/etc/finit.d/available/program.conf`: * `/etc/finit.d/available/program.conf`:
service [235] <!> /etc/start.d/program -- Example Program service program {
description = "Example Program"
runlevel = "235"
reload-signal = "none"
command = "/etc/start.d/program"
}
* `/etc/start.d/program:` * `/etc/start.d/program:`
@@ -26,6 +31,6 @@ example employs a wrapper script in `/etc/start.d`.
exec /usr/bin/program $OPTIONS exec /usr/bin/program $OPTIONS
> [!NOTE] > [!NOTE]
> The example sets `<!>` to denote that it doesn't support `SIGHUP`. > The example sets `reload-signal = "none"` to say the program does not
> That way Finit will stop/start the service instead of sending SIGHUP > support `SIGHUP`. Finit then stop/starts the service instead of
> at restart/reload events. > signalling it at restart/reload events.
+205 -90
View File
@@ -1,16 +1,17 @@
Services Services
======== ========
**Syntax:** `service [LVLS] <COND> /path/to/daemon ARGS -- Optional description` **Syntax:** `service NAME { command = "/path/to/daemon ARGS" }`
Service, or daemon, to be monitored and automatically restarted if it Service, or daemon, to be monitored and automatically restarted if it
exits prematurely. Finit tries to restart services that die, by default exits prematurely. Finit tries to restart services that die, by default
10 times before giving up and marking them as *crashed*. After which 10 times before giving up and marking them as *crashed*. After which
they have to be manually restarted with `initctl restart NAME`. The they have to be manually restarted with `initctl restart NAME`. The
limits controlling this are configurable, see the options below. limits controlling this are configurable, see
[Service Options](service-opts.md).
> [!TIP] > [!TIP]
> To allow endless restarts, see the [`respawn` option](service-opts.md) > To allow endless restarts, see [`respawn`](service-opts.md#restarting)
For daemons that support it, we recommend appending `--foreground`, For daemons that support it, we recommend appending `--foreground`,
`--no-background`, `-n`, `-F`, or similar command line argument to `--no-background`, `-n`, `-F`, or similar command line argument to
@@ -19,20 +20,25 @@ the most reliable way to monitor a service.
However, not all daemons support running in the foreground, or they may However, not all daemons support running in the foreground, or they may
start logging to the foreground as well, these are forking daemons and start logging to the foreground as well, these are forking daemons and
are supported using the same syntax as forking `sysv` services, using are supported using the same syntax as forking `sysv` services, by
the `pid:!/path/to/pidfile.pid` syntax. There is an alternative syntax naming the file to watch with `pidfile`. There is an alternative that
that may be more intuitive, where Finit can also guess the PID file may be more intuitive, where Finit can also guess the PID file based on
based on the daemon's command name: the daemon's command name:
service type:forking ntpd -- NTP daemon service ntpd {
description = "NTP daemon"
type = "forking"
command = "ntpd"
}
This example lets BusyBox `ntpd` daemonize itself. Finit uses the This example lets BusyBox `ntpd` daemonize itself. Finit uses the
basename of the binary to guess the PID file to watch for the PID: basename of the binary to guess the PID file to watch for the PID:
`/var/run/ntpd.pid`. If Finit guesses wrong, you have to submit the `/var/run/ntpd.pid`. If Finit guesses wrong, name the file yourself
full `pid:!/path/to/file.pid`. with `pidfile = "/path/to/file.pid"`.
With `pid:!/path`, the file belongs to the service: Finit reads it The file belongs to the service: Finit reads it but does not create or
but does not create or remove it. The one exception is *stale* remove it. That is the default, and `pidfile-create = true` is what
asks Finit to write the file instead. The one exception is *stale*
cleanup — if the service dies without removing its own pidfile cleanup — if the service dies without removing its own pidfile
(SIGKILL, OOM, segfault), and the file still names the just-reaped (SIGKILL, OOM, segfault), and the file still names the just-reaped
PID, Finit removes it before the next retry. This prevents daemons PID, Finit removes it before the next retry. This prevents daemons
@@ -44,56 +50,75 @@ from getting stuck in a crash-restart loop.
In the case of `ospfd` (below), we omit the `-d` flag (daemonize) to In the case of `ospfd` (below), we omit the `-d` flag (daemonize) to
prevent it from forking to the background: prevent it from forking to the background:
service [2345] <pid/zebra> /sbin/ospfd -- OSPF daemon service ospfd {
description = "OSPF daemon"
runlevel = "2345"
conditions = { "pid/zebra" }
command = "/sbin/ospfd"
}
`[2345]` denote the runlevels `ospfd` is allowed to run in, they are `runlevel` denotes the runlevels `ospfd` is allowed to run in, it is
optional and default to level 2-5 if omitted. optional and defaults to level 2-4 if omitted.
`<...>` is the condition for starting `ospfd`. In this example Finit `conditions` lists what must be asserted before starting `ospfd`. In
waits for another service, `zebra`, to have created its PID file in this example Finit waits for another service, `zebra`, to have created
`/var/run/quagga/zebra.pid` before starting `ospfd`. Finit watches its PID file in `/var/run/quagga/zebra.pid`. Finit watches *all* files
*all* files in `/var/run`, for each file named `*.pid`, or `*/pid`, in `/var/run`, for each file named `*.pid`, or `*/pid`, Finit opens it
Finit opens it and find the matching `NAME:ID` using the PID. and finds the matching `NAME:ID` using the PID.
The condition can be prefixed with `!` and/or `~`: A condition may be prefixed with `~` to propagate a reload of the
upstream service to this one, rather than merely pausing and resuming
it:
- `<!pid/zebra>` -- `ospfd` does not support `SIGHUP` (noreload) conditions = { "~pid/zebra" }
- `<~pid/zebra>` -- propagate reload from `zebra` to `ospfd`
- `<!~pid/zebra>` -- both: noreload and propagate reload
For details, see the [Finit Conditions](../conditions.md) document. If `ospfd` cannot be reloaded with `SIGHUP` at all, that is a property
of `ospfd` and not of the condition, so it is said directly:
reload-signal = "none"
The legacy format spells that second one as a `!` leading the condition
list, which is not accepted here. For details, see the
[Finit Conditions](../conditions.md) document.
Some services do not maintain a PID file and rather than patching each Some services do not maintain a PID file and rather than patching each
application Finit provides a workaround. A `pid` keyword can be set application Finit provides a workaround. With `pidfile-create` Finit
to have Finit automatically create (when starting) and later remove creates the file when starting and removes it when stopping. The path
(when stopping) the PID file. The file is created in the `/var/run` comes from `pidfile`, which takes three forms:
directory using the `basename(1)` of the service. The default can be
modified with an optional `pid:`-argument:
pid[:[/path/to/]filename[.pid]] pidfile = true # /var/run/<command basename>.pid
pidfile = "bar" # a bare name, /var/run/bar.pid
pidfile = "/run/bar.pid" # an explicit path
For example, by adding `pid:/run/bar.pid` to the service `/sbin/bar`, Such a file is also used by the Finit condition subsystem, so another
that PID file will, not only be created and removed automatically, but service, run or task can depend on `pid/bar`. Here foo is not started
also be used by the Finit condition subsystem. So a service/run/task until bar has:
can depend on `<pid/bar>`, like this foo will not be started until bar
has started:
service pid:/run/bar.pid bar -- Bar Service service bar {
service <pid/bar> foo -- Foo Service description = "Bar Service"
pidfile = "/run/bar.pid"
pidfile-create = true
command = "bar"
}
service foo {
description = "Foo Service"
conditions = { "pid/bar" }
command = "foo"
}
Needless to say, it is better if `bar` creates its own PID file when it Needless to say, it is better if `bar` creates its own PID file when it
has completed starting up and is ready for service. has completed starting up and is ready for service.
As an alternative "readiness" notification, Finit supports both systemd As an alternative "readiness" notification, Finit supports both systemd
and s6 style notification. This can be enabled by using the `notify` and s6 style notification. This is enabled with the `notify` key:
option:
* `notify:systemd` -- tells Finit the service uses the `sd_notify()` * `notify = "systemd"` -- tells Finit the service uses the `sd_notify()`
API to signal PID 1 when it has completed its startup and is ready API to signal PID 1 when it has completed its startup and is ready
to service events. The [sd_notify()][] API expects `NOTIFY_SOCKET` to service events. The [sd_notify()][] API expects `NOTIFY_SOCKET`
to be set to the socket where the application can send `"READY=1\n"` to be set to the socket where the application can send `"READY=1\n"`
when it is starting up or has processed a `SIGHUP`. when it is starting up or has processed a `SIGHUP`.
* `notify:s6` -- puts Finit in s6 compatibility mode. Compared to the * `notify = "s6"` -- puts Finit in s6 compatibility mode. Compared to the
systemd notification, [s6 expect][] compliant daemons to send `"\n"` systemd notification, [s6 expect][] compliant daemons to send `"\n"`
and then close their socket. Finit takes care of "hard-wiring" the and then close their socket. Finit takes care of "hard-wiring" the
READY state as long as the application is running, events across any READY state as long as the application is running, events across any
@@ -101,7 +126,11 @@ option:
(must be >3) on then command line, Finit provides the following (must be >3) on then command line, Finit provides the following
syntax (`%n` is replaced by Finit with then descriptor number): syntax (`%n` is replaced by Finit with then descriptor number):
service [S12345789] notify:s6 mdevd -O 4 -D %n service mdevd {
runlevel = "S12345789"
notify = "s6"
command = "mdevd -O 4 -D %n"
}
[sd_notify()]: https://www.freedesktop.org/software/systemd/man/sd_notify.html [sd_notify()]: https://www.freedesktop.org/software/systemd/man/sd_notify.html
[s6 expect]: https://skarnet.org/software/s6/notifywhenup.html [s6 expect]: https://skarnet.org/software/s6/notifywhenup.html
@@ -115,7 +144,13 @@ then service's ready condition which other services can depend on:
This can be used to synchronize the start of another run/task/service: This can be used to synchronize the start of another run/task/service:
task [S] <service/mdevd/ready> @root:root mdevd-coldplug task mdevd-coldplug {
runlevel = "S"
conditions = { "service/mdevd/ready" }
user = "root"
group = "root"
command = "mdevd-coldplug"
}
Finit waits for `mdevd` to notify it, before starting `mdevd-coldplug`. Finit waits for `mdevd` to notify it, before starting `mdevd-coldplug`.
Notice how both start in runlevel S, and the coldplug task only runs in Notice how both start in runlevel S, and the coldplug task only runs in
@@ -131,20 +166,29 @@ Non-privileged Services
----------------------- -----------------------
Every `run`, `task`, or `service` can also list the privileges the Every `run`, `task`, or `service` can also list the privileges the
`/path/to/cmd` should be executed with. Prefix the command with command should be executed with, using `user`, `group` and
`@USR[:GRP[,SUPP,...]]`, where group and supplementary groups are `extra-groups`, all optional:
optional, like this:
run [2345] @joe:users logger "Hello world" run hello {
runlevel = "2345"
user = "joe"
group = "users"
command = "logger \"Hello world\""
}
Finit reads the user's supplementary group membership from `/etc/group` Finit reads the user's supplementary group membership from `/etc/group`
automatically. Any groups the user belongs to will be inherited by automatically. Any groups the user belongs to will be inherited by
the service. the service.
To specify additional supplementary groups beyond those in `/etc/group`, To specify additional supplementary groups beyond those in
append them after the primary group, separated by commas: `/etc/group`, list them in `extra-groups`:
service @caddy:caddy,ssl-cert /usr/bin/caddy run service caddy {
user = "caddy"
group = "caddy"
extra-groups = { "ssl-cert" }
command = "/usr/bin/caddy run"
}
This runs the `caddy` service as user `caddy`, with primary group This runs the `caddy` service as user `caddy`, with primary group
`caddy`, inheriting any groups `caddy` is a member of in `/etc/group`, `caddy`, inheriting any groups `caddy` is a member of in `/etc/group`,
@@ -152,81 +196,152 @@ plus the additional `ssl-cert` group. This is useful when a service
needs access to resources owned by groups not listed in `/etc/group`. needs access to resources owned by groups not listed in `/etc/group`.
For multiple instances of the same command, e.g. a DHCP client or For multiple instances of the same command, e.g. a DHCP client or
multiple web servers, add `:ID` somewhere between the `run`, `task`, multiple web servers, add `:ID` to the block title, like this:
`service` keyword and the command, like this:
service :80 [2345] httpd -f -h /http -p 80 -- Web server service httpd:80 {
service :8080[2345] httpd -f -h /http -p 8080 -- Old web server description = "Web server"
runlevel = "2345"
command = "httpd -f -h /http -p 80"
}
Without the `:ID` to the service the latter will overwrite the former service httpd:8080 {
and only the old web server would be started and supervised. description = "Old web server"
runlevel = "2345"
command = "httpd -f -h /http -p 8080"
}
Without the `:ID` the latter will overwrite the former and only the old
web server would be started and supervised.
> [!NOTE]
> The line-based format also accepts a bare ID, `service :80 ...`,
> deriving the name from the command. There is no block equivalent:
> the title carries both name and ID.
Conditional Loading Conditional Loading
------------------- -------------------
Finit support conditional loading of stanzas. The following example is Finit supports conditional loading of blocks. The following example is
take from the `system/hotplug.conf` file in the Finit distribution. taken from the `system/10-hotplug.conf` file in the Finit distribution.
Here we only show a simplified subset. Here we only show a simplified subset.
Starting with the `nowarn` option. Starting with the leading `-` on `command`.
service nowarn name:udevd pid:udevd /lib/systemd/systemd-udevd service udevd {
service nowarn name:udevd pid:udevd udevd pidfile = "udevd"
command = "-/lib/systemd/systemd-udevd"
}
When loading the .conf file Finit looks for `/lib/systemd/systemd-udevd` service udevd {
if that is not found Finit automatically logs a warning. The `nowarn` pidfile = "udevd"
option disables this warning so that the second line can be evaluated, command = "-udevd"
}
When loading the .conf file Finit looks for
`/lib/systemd/systemd-udevd`, and if that is not found it logs a
warning. The leading `-` says a missing binary is expected here, so
the block is skipped quietly and the second one can be evaluated,
which also provides a service named `udevd`. which also provides a service named `udevd`.
run nowarn if:udevd <pid/udevd> :1 udevadm settle -t 0 run udevadm:1 {
runlevel = "S"
if = "udevd"
conditions = { "pid/udevd" }
command = "-udevadm settle -t 0"
}
This line is only loaded if we know of a service named `udevd`. Again, This block is only loaded if we know of a service named `udevd`. Again,
we do not warn if `udevadm` is not found, execution will also stop here we do not warn if `udevadm` is not found, execution will also stop here
until the PID condition is asserted, i.e., Finit detecting udevd has until the PID condition is asserted, i.e., Finit detecting udevd has
started. started.
run nowarn conflict:udevd [S] mdev -s -- Populating device tree run mdev {
description = "Populating device tree"
runlevel = "S"
conflicts = { "udevd" }
command = "-mdev -s"
}
If `udevd` is not available, we try to run `mdev`, but if that is not If `udevd` is not available, we try to run `mdev`, but if that is not
found, again we do not warn. found, again we do not warn.
Conditional loading statements can also be negated, so the previous stanza Conditional loading can also be negated, so the previous block can be
can also be written as: written as:
run nowarn if:!udevd [S] mdev -s -- Populating device tree run mdev {
description = "Populating device tree"
runlevel = "S"
if = "!udevd"
command = "-mdev -s"
}
The reason for using `conflict` in this example is that a conflict can be The reason for using `conflicts` in this example is that a conflict can
resolved. Stanzas marked with `conflict:foo` are rechecked at runtime. be resolved. Blocks naming a conflict are rechecked at runtime.
Conditional Execution Conditional Execution
--------------------- ---------------------
Similar to conditional loading of stanzas there is conditional runtime Similar to conditional loading of blocks there is conditional runtime
execution. This can be confusing at first, since Finit already has a execution. This can be confusing at first, since Finit already has a
condition subsystem, but this is more akin to the qualification to a condition subsystem, but this is more akin to the qualification to a
runlevel. E.g., a `task [123]` is qualified to run only in runlevel 1, runlevel. E.g., a task with `runlevel = "123"` is qualified to run
2, and 3. It is not considered for other runlevels. only in runlevel 1, 2, and 3. It is not considered for other
runlevels.
Conditional execution qualify a run/task/service based on a condition. Conditional execution qualify a run/task/service based on a condition.
Consider this (simplified) example from the Infix operating system: Consider this (simplified) example from the Infix operating system:
run [S] name:startup <pid/sysrepo> confd -b --load startup-config run startup {
run [S] if:<usr/fail-startup> name:failure <pid/sysrepo> confd --load failure-config runlevel = "S"
conditions = { "pid/sysrepo" }
command = "confd -b --load startup-config"
}
The two run statements reside in the same .conf file so Finit runs them run failure {
in true sequence. If loading the file `startup-config` fails confd sets runlevel = "S"
the condition `usr/fail-startup`, thus allowing the next run statement if = "usr/fail-startup"
to load `failure-config`. conditions = { "pid/sysrepo" }
command = "confd --load failure-config"
}
Notice the critical difference between the `<pid/sysrepo>` condition and The two run blocks reside in the same .conf file so Finit runs them in
`if:<usr/fail-startup>`. The former is a condition for starting and the true sequence. If loading the file `startup-config` fails confd sets
latter is a condition to check if a run/task/service is qualified to the condition `usr/fail-startup`, thus allowing the next one to load
even be considered. `failure-config`.
Conditional execution statements can also be negated, so provided the Notice the critical difference between the `conditions` list and `if`.
file loaded did the opposite, i.e., set a condition on success, the The former is a condition for starting; the latter is a condition to
previous stanza can also be written as: check whether a run/task/service is qualified to even be considered.
`if` has a negation of its own, `!`, which is unrelated to anything in
the `conditions` list.
run [S] if:<!usr/startup-ok> name:failure <pid/sysrepo> confd ... What `if` compares against depends on the value. A namespace
separator makes it a condition, anything else is a service name:
| `if` | Asks |
|---|---|
| `"udevd"` | is a service by this name known? |
| `"usr/foo"` | was this condition set? |
Both are questions about whether the block belongs in the running
configuration at all, usually answered from what bootstrap established.
A statement is all of one kind or the other, so the block is rejected
if you mix them.
> [!NOTE]
> `if` qualifies, it does not track. A condition asserted or cleared
> later does not start or stop the service by itself -- that is what
> the `conditions` list is for.
Conditional execution can also be negated, so provided the file loaded
did the opposite, i.e., set a condition on success, the previous block
can be written as:
run failure {
runlevel = "S"
if = "!usr/startup-ok"
conditions = { "pid/sysrepo" }
command = "confd ..."
}
+26 -17
View File
@@ -9,42 +9,45 @@ from a serialized boot process.
SysV Init Scripts SysV Init Scripts
----------------- -----------------
**Syntax:** `sysv [LVLS] <COND> /path/to/init-script -- Optional description` **Syntax:** `sysv NAME { command = "/path/to/init-script" }`
> `<COND>` is described in the [Services](services.md) section. > [!NOTE]
> Conditions, runlevels, and the other settings a `sysv` block takes
> are described in [Service Options](service-opts.md).
Similar to `task` is the `sysv` stanza, which can be used to call SysV A `sysv` block is a supervised daemon, like `service`, but started and
style scripts. The primary intention for this command is to be able to stopped through a SysV style init script instead of a command line. The
reuse much of existing setup and init scripts in Linux distributions. intention is to reuse existing setup and init scripts from Linux
distributions.
When entering an allowed runlevel, Finit calls `init-script start`, when When entering an allowed runlevel, Finit calls `init-script start`, when
entering a disallowed runlevel, Finit calls `init-script stop`, and if entering a disallowed runlevel, Finit calls `init-script stop`, and if
the Finit .conf, where `sysv` stanza is declared, is modified, Finit the Finit .conf, where the `sysv` block is declared, is modified, Finit
calls `init-script restart` on `initctl reload`. Similar to how calls `init-script restart` on `initctl reload`. Similar to how
`service` stanzas work. `service` blocks work.
Forking services started with `sysv` scripts can be monitored by Finit Forking services started with `sysv` scripts can be monitored by Finit
by declaring the PID file to look for: `pid:!/path/to/pidfile.pid`. by declaring the PID file to look for: `pidfile = "/path/to/file.pid"`.
Notice the leading `!`, it signifies Finit should not try to create the Finit does not create that file, it watches it for the resulting
file, but rather watch that file for the resulting forked-off PID. This forked-off PID. That is the default; `pidfile-create = true` asks Finit
syntax also works for forking daemons that do not have a command line to write it instead. The same applies to forking daemons with no way to
option to run it in the foreground, more on this below in `service`. run in the foreground, see [Services](services.md).
> [!TIP] > [!TIP]
> See also [SysV Init Compatibility](#sysv-init-compatibility). > See also [SysV Init Compatibility](#sysv-init-compatibility).
`runparts DIRECTORY` Run-parts
-------------------- ---------
For a directory with traditional start/stop scripts that should run, in For a directory with traditional start/stop scripts that should run, in
order, at bootstrap, Finit provides the `runparts` directive. It runs order, at bootstrap, Finit provides the `runparts` setting. It runs
in runlevel S, at the very end of it (before calling `/etc/rc.local`) in runlevel S, at the very end of it (before calling `/etc/rc.local`)
making it perfect for most scenarios. making it perfect for most scenarios.
For syntax details, see the [Run-parts Scripts](runparts.md) section. For syntax details, see the [Run-parts Scripts](runparts.md) section.
Here is an example take from a Debian installation: Here is an example take from a Debian installation:
runparts /etc/rc2.d runparts = "/etc/rc2.d"
Files in these directories are usually named `SNNfoo` and `KNNfoo`, Files in these directories are usually named `SNNfoo` and `KNNfoo`,
which Finit knows about and automatically appends the correct argument: which Finit knows about and automatically appends the correct argument:
@@ -64,7 +67,12 @@ Start/Stop Scripts
For syntax details, see [SysV Init Scripts](#sysv-init-scripts), above. For syntax details, see [SysV Init Scripts](#sysv-init-scripts), above.
Here follows an example taken from a Debian installation: Here follows an example taken from a Debian installation:
sysv [2345] <pid/syslogd> /etc/init.d/openbsd-inetd -- OpenBSD inet daemon sysv inetd {
description = "OpenBSD inet daemon"
runlevel = "2345"
conditions = { "pid/syslogd" }
command = "/etc/init.d/openbsd-inetd"
}
The init script header could be parsed to extract `Default-Start:` and The init script header could be parsed to extract `Default-Start:` and
other parameters for the `sysv` command to Finit. There is currently no other parameters for the `sysv` command to Finit. There is currently no
@@ -83,6 +91,7 @@ it exists, and is executable. It is called very late in the boot process
when the system has left runlevel S, stopped all old and started all new when the system has left runlevel S, stopped all old and started all new
services in the target runlevel (default 2). services in the target runlevel (default 2).
> [!NOTE]
> In Finit releases before v4.5 this script blocked Finit execution and > In Finit releases before v4.5 this script blocked Finit execution and
> made it as good as impossible to call `initctl` during that time. > made it as good as impossible to call `initctl` during that time.
+34 -20
View File
@@ -1,9 +1,10 @@
run (sequence) run (sequence)
-------------- --------------
**Syntax:** `run [LVLS] <COND> /path/to/cmd ARGS -- Optional description` **Syntax:** `run NAME { command = "/path/to/cmd ARGS" }`
> `<COND>` is described in the [Services](services.md) section. > Conditions, runlevels, and the other settings a `run` block takes are
> described in [Service Options](service-opts.md).
One-shot command to run in sequence when entering a runlevel, with One-shot command to run in sequence when entering a runlevel, with
optional arguments and description. `run` commands are guaranteed to be optional arguments and description. `run` commands are guaranteed to be
@@ -31,36 +32,45 @@ also the `--quiet` and `--batch` options.
task (parallel) task (parallel)
--------------- ---------------
**Syntax:** `task [LVLS] <COND> /path/to/cmd ARGS -- Optional description` **Syntax:** `task NAME { command = "/path/to/cmd ARGS" }`
> `<COND>` is described in the [Services](services.md) section. > A `task` block takes the same settings as `run`, see
> [Service Options](service-opts.md).
One-shot like 'run', but starts in parallel with the next command. One-shot like 'run', but starts in parallel with the next command.
Both `run` and `task` commands are run in a shell, so basic pipes and Both `run` and `task` commands are run in a shell, so basic pipes and
redirects can be used: redirects can be used:
task [s] echo "foo" | cat >/tmp/bar task bar {
runlevel = "S"
command = "echo \"foo\" | cat >/tmp/bar"
}
Please note, `;`, `&&`, `||`, and similar are *not supported*. Any Please note, `;`, `&&`, `||`, and similar are *not supported*. Any
non-trivial constructs are better placed in a separate shell script. non-trivial constructs are better placed in a separate shell script.
remain:yes remain-after-exit
---------- -----------------
By default, a `run` or `task` will re-run each time its runlevel is By default, a `run` or `task` will re-run each time its runlevel is
entered, and its `post:` script does not run on completion. entered, and its `exec-stop-post` script does not run on completion.
With `remain:yes`, the task runs once and does not re-run on runlevel With `remain-after-exit`, the task runs once and does not re-run on
re-entry: runlevel re-entry:
task [2345] remain:yes /usr/sbin/setup-firewall -- Firewall setup task firewall {
description = "Firewall setup"
runlevel = "2345"
remain-after-exit = true
command = "/usr/sbin/setup-firewall"
}
This has the following effects: This has the following effects:
* The task does not re-run on runlevel re-entry * The task does not re-run on runlevel re-entry
* The `post:` script runs when: * The `exec-stop-post` script runs when:
- The task is explicitly stopped (`initctl stop NAME`) - The task is explicitly stopped (`initctl stop NAME`)
- The task leaves its valid runlevels (e.g., runlevel change) - The task leaves its valid runlevels (e.g., runlevel change)
@@ -72,16 +82,20 @@ This is useful for tasks that set up persistent state where:
**Example:** Setting up firewall rules with cleanup on shutdown: **Example:** Setting up firewall rules with cleanup on shutdown:
``` ```
task [2345] remain:yes \ task firewall {
post:/usr/sbin/teardown-firewall \ description = "Firewall setup"
/usr/sbin/setup-firewall -- Firewall setup runlevel = "2345"
remain-after-exit = true
exec-stop-post = "/usr/sbin/teardown-firewall"
command = "/usr/sbin/setup-firewall"
}
``` ```
The firewall rules are created once. The `post:` script runs when The firewall rules are created once. The `exec-stop-post` script runs when
entering runlevel 0 (halt) or 6 (reboot), or on explicit stop. entering runlevel 0 (halt) or 6 (reboot), or on explicit stop.
> [!NOTE] > [!NOTE]
> The `remain:yes` option is not supported for bootstrap-only tasks > The `remain-after-exit` setting is not supported for bootstrap-only
> (tasks with only runlevel S). Bootstrap tasks are deleted immediately > tasks (tasks with only runlevel S). Bootstrap tasks are deleted
> after completion, and their `post:` scripts never run. A warning is > immediately after completion, and their `exec-stop-post` scripts never
> logged if `remain:yes` is used on such tasks. > run. A warning is logged if `remain-after-exit` is used on such tasks.
+8 -4
View File
@@ -5,16 +5,20 @@ Finit comes with rudimentary support for templating, similar to that of
systemd. Best illustrated with an example: systemd. Best illustrated with an example:
$ initctl show avahi-autoipd@ $ initctl show avahi-autoipd@
service :%i avahi-autoipd --syslog %i -- ZeroConf for %i service avahi-autoipd:%i {
description = "ZeroConf for %i"
command = "avahi-autoipd --syslog %i"
}
To enable ZeroConf for, e.g., `eth0`, use To enable ZeroConf for, e.g., `eth0`, use
$ initctl enable avahi-autoipd@eth0.conf $ initctl enable avahi-autoipd@eth0.conf
The enabled symlink will be set up to `avahi-autoipd@.conf` and every The enabled symlink will be set up to `avahi-autoipd@.conf` and every
instance of `%i` will in the instantiated directive be replaced with instance of `%i` will be replaced with `eth0` before the file is
`eth0`. Inspect the resulting instantiated template with `initctl show parsed, so it works in the block title, in any value, and in the
avahi-autoipd:eth0` and check the status of a running instance with: command line alike. Inspect the result with `initctl show
avahi-autoipd:eth0`, and check a running instance with:
``` ```
$ initctl status avahi-autoipd:eth0 $ initctl status avahi-autoipd:eth0
+94 -45
View File
@@ -1,67 +1,94 @@
TTYs and Consoles TTYs and Consoles
================= =================
**Syntax:** `tty [LVLS] <COND> DEV [BAUD] [noclear] [nowait] [nologin] [TERM]` **Syntax:** `tty NAME { device = DEV }` -- built-in getty
`tty [LVLS] <COND> CMD <ARGS> [noclear] [nowait]` `tty NAME { command = "CMD ARGS" }` -- external getty
`tty [LVLS] <COND> [notty] [rescue]` `tty NAME { notty = true }`, or `{ rescue = true }` -- bare shell, no device
The first variant of this option uses the built-in getty on the given The block title NAME is what `initctl` shows. The three variants differ
TTY device DEV, in the given runlevels. DEV may be the special keyword in what they open: `device` runs the built-in getty on that TTY, and DEV
`@console`, which is expanded from `/sys/class/tty/console/active`, may be the special keyword `@console`, expanded from
useful on embedded systems. `/sys/class/tty/console/active`, useful on embedded systems. `command`
hands the TTY to an external getty. `notty` opens nothing at all.
The default baud rate is 0, i.e., keep kernel default. Settings common to all three:
> The `tty` stanza inherits runlevel, condition (and other feature) | Setting | Alias | Description |
> parsing from the `service` stanza. So TTYs can run in one or many |---|---|---|
| `runlevel` | | Runlevels to run in, e.g. `"12345"` |
| `conditions` | `cond` | Conditions to wait for |
| `noclear` | | Do not clear the TTY after each session |
| `nowait` | | Do not wait for Enter before the login prompt |
| `nologin` | | Skip login, give a shell straight away |
The `device` variant takes two more:
| Setting | Description |
|---|---|
| `baud` | Baud rate, default 0, i.e., keep kernel default |
| `term` | `$TERM` value, e.g. `"vt220"` |
> A `tty` block inherits runlevel, condition (and other feature)
> parsing from the `service` block. So TTYs can run in one or many
> runlevels and depend on any condition supported by Finit. This is > runlevels and depend on any condition supported by Finit. This is
> useful e.g. to depend on `<pid/elogind>` before starting a TTY. > useful e.g. to depend on `<pid/elogind>` before starting a TTY.
**Example:** **Example:**
tty [12345] /dev/ttyAMA0 115200 noclear vt220 tty ttyAMA0 {
runlevel = "12345"
device = "/dev/ttyAMA0"
baud = 115200
term = "vt220"
noclear = true
}
The second `tty` syntax variant is for using an external getty, like The second `tty` syntax variant is for using an external getty, like
agetty or the BusyBox getty. agetty or the BusyBox getty.
The third variant is for board bringup and the `rescue` boot mode. No The third variant is for board bringup and the `rescue` boot mode. No
device node is required in this variant, the same output that the kernel device node is required, the same output the kernel uses is reused for
uses is reused for stdio. If the `rescue` option is omitted, a shell is stdio. With `notty` a shell is started (`nologin`, `noclear`, and
started (`nologin`, `noclear`, and `nowait` are implied), if the rescue `nowait` are implied); with `rescue` the bundled
option is set the bundled `/libexec/finit/sulogin` is started to present `/libexec/finit/sulogin` presents a bare-bones root login prompt. If
a bare-bones root login prompt. If the root (uid:0, gid:0) user does the root (uid:0, gid:0) user does not have a password set, no rescue is
not have a password set, no rescue is possible. For more information, possible. For more information, see the [Rescue Mode](rescue.md)
see the [Rescue Mode](rescue.md) section. section.
By default, the first two syntax variants *clear* the TTY and *wait* for By default, the first two syntax variants *clear* the TTY and *wait* for
the user to press enter before starting getty. the user to press enter before starting getty.
**Example:** **Example:**
tty [12345] /sbin/getty -L 115200 /dev/ttyAMA0 vt100 tty getty {
tty [12345] /sbin/agetty -L ttyAMA0 115200 vt100 nowait runlevel = "12345"
command = "/sbin/getty -L 115200 /dev/ttyAMA0 vt100"
}
The `noclear` option disables clearing the TTY after each session. tty agetty {
runlevel = "12345"
command = "/sbin/agetty -L ttyAMA0 115200 vt100"
nowait = true
}
The `noclear` setting disables clearing the TTY after each session.
Clearing the TTY when a user logs out is usually preferable. Clearing the TTY when a user logs out is usually preferable.
The `nowait` option disables the `press Enter to activate console` The `nowait` setting disables the `press Enter to activate console`
message before actually starting the getty program. On small and message before actually starting the getty program. On small and
embedded systems running multiple unused getty wastes both memory embedded systems running multiple unused getty wastes both memory
and CPU cycles, so `wait` is the preferred default. and CPU cycles, so `wait` is the preferred default.
The `nologin` option disables getty and `/bin/login`, and gives the The `nologin` setting disables getty and `/bin/login`, and gives the
user a root (login) shell on the given TTY `<DEV>` immediately. user a root (login) shell on the given TTY immediately.
Needless to say, this is a rather insecure option, but can be very Needless to say, this is a rather insecure option, but can be very
useful for developer builds, during board bringup, or similar. useful for developer builds, during board bringup, or similar.
Notice the ordering, the `TERM` option to the built-in getty must be
the last argument.
Embedded systems may want to enable automatic `DEV` by supplying the Embedded systems may want to enable automatic `DEV` by supplying the
special `@console` device. This works regardless weather the system special `@console` device. This works regardless whether the system
uses `ttyS0`, `ttyAMA0`, `ttyMXC0`, or anything else. Finit figures uses `ttyS0`, `ttyAMA0`, `ttyMXC0`, or anything else. Finit figures
it out by querying sysfs: `/sys/class/tty/console/active`. The speed it out by querying sysfs: `/sys/class/tty/console/active`. Leave
can be omitted to keep the kernel default. `baud` out to keep the kernel default.
> Most systems get by fine by just using `console`, which will evaluate > Most systems get by fine by just using `console`, which will evaluate
> to `/dev/console`. If you have to use `@console` to get any output, > to `/dev/console`. If you have to use `@console` to get any output,
@@ -69,45 +96,67 @@ can be omitted to keep the kernel default.
**Example:** **Example:**
tty [12345] @console noclear vt220 tty console {
runlevel = "12345"
device = "@console"
term = "vt220"
noclear = true
}
On really bare bones systems, or for board bringup, Finit can give you a On really bare bones systems, or for board bringup, Finit can give you a
shell prompt as soon as bootstrap is done, without opening any device shell prompt as soon as bootstrap is done, without opening any device
node: node:
tty [12345789] notty tty board {
runlevel = "12345789"
notty = true
}
This should of course not be enabled on production systems. Because it This should of course not be enabled on production systems. Because it
may give a user root access without having to log in. However, for may give a user root access without having to log in. However, for
board bringup and system debugging it can come in handy. board bringup and system debugging it can come in handy.
One can also use the `service` stanza to start a stand-alone shell: One can also use a `service` block to start a stand-alone shell:
service [12345] /bin/sh -l service shell {
runlevel = "12345"
command = "/bin/sh -l"
}
Controlling TTY for Services Controlling TTY for Services
---------------------------- ----------------------------
The `tty:<dev>` option gives a `run`, `task`, or `service` a controlling The `tty` setting gives a `run`, `task`, or `service` a controlling
terminal on the given device. The device is opened, set as the terminal on the given device. The device is opened, set as the
controlling terminal for the session (after `setsid()`), and connected to controlling terminal for the session (after `setsid()`), and connected to
the process's stdin, stdout, and stderr. A default `TERM` environment the process's stdin, stdout, and stderr. A default `TERM` environment
variable is set based on the device type: `vt102` for serial lines and variable is set based on the device type: `vt102` for serial lines and
`linux` for virtual terminals. `linux` for virtual terminals.
`<dev>` may be a device node like `/dev/ttyS0`, or the special keyword The value may be a device node like `/dev/ttyS0`, or the special
`@console` (see above). Note that `@console` expands only to the keyword `@console` (see above). Note that `@console` expands only to
first console, not all. the first console, not all.
When `tty:` is combined with `log:`, stdout and stderr are redirected When `tty` is combined with a `log` block, stdout and stderr are
to the log sink instead of the TTY, but stdin remains connected to the redirected to the log sink instead of the TTY, but stdin remains
TTY device. connected to the TTY device.
> The `tty:<dev>` option is for `run`, `task`, and `service` stanzas only. > The `tty` setting is for `run`, `task`, and `service` blocks only.
> The `tty` directive itself (for getty/login) has its own syntax, see > A `tty` block (for getty/login) is a different thing entirely, see
> above. > above.
**Example:** **Example:**
service [2345] tty:/dev/ttyS0 /usr/sbin/foo -- Foo on serial console service foo {
task [S] tty:@console my-setup-script -- Board bringup on console description = "Foo on serial console"
runlevel = "2345"
tty = "/dev/ttyS0"
command = "/usr/sbin/foo"
}
task setup {
description = "Board bringup on console"
runlevel = "S"
tty = "@console"
command = "my-setup-script"
}
+135 -58
View File
@@ -10,117 +10,194 @@ See the [contrib/][contrib] directory on GitHub for examples, or take a
peek at systems using Finit, like [Infix OS][infix] and [myLinux][]. peek at systems using Finit, like [Infix OS][infix] and [myLinux][].
> [!TIP] > [!TIP]
> As of Finit v4.4, `.conf` lines can be broken up using the standard UNIX > A block spans as many lines as it needs, so no continuation character is
> continuation character (`\`), trailing comments are also supported. The > called for. For the full syntax, see the [finit.conf(5)][] manual or the
> latter means you must escape any hashes used in directives and descriptions > [Configuration](config/index.md) section.
> (`\#`). For more on this and examples, see the [finit.conf(5)][] manual or
> the [Configuration](config/index.md) section.
```ApacheConf ```ApacheConf
# Fallback if /etc/hostname is missing # Fallback if /etc/hostname is missing
host default hostname = "default"
# Runlevel to start after bootstrap, 'S', default: 2 # Runlevel to start after bootstrap, 'S', default: 2
#runlevel 2 #runlevel = 2
# Support for setting global environment variables, using foo=bar syntax # Global environment variables, be careful though with variables like
# be careful though with variables like PATH, SHELL, LOGNAME, etc. # PATH, SHELL, LOGNAME, etc.
#PATH=/usr/bin:/bin:/usr/sbin:/sbin #environment {
# PATH = "/usr/bin:/bin:/usr/sbin:/sbin"
#}
# Max file size for each log file: 100 kiB, rotate max 4 copies: # Max file size for each log file: 100 kiB, rotate max 4 copies:
# log => log.1 => log.2.gz => log.3.gz => log.4.gz # log => log.1 => log.2.gz => log.3.gz => log.4.gz
log size=100k count=4 log {
size = 100k
count = 4
}
# Services to be monitored and respawned as needed # Services to be monitored and respawned as needed
service [S12345] env:-/etc/conf.d/watchdog watchdog $WATCHDOG_OPTS $WATCHDOG_DEV -- System watchdog daemon service watchdog {
service [S12345] env:-/etc/conf.d/syslog syslogd -n $SYSLOGD_OPTS -- System log daemon description = "System watchdog daemon"
service [S12345] <pid/syslogd> env:-/etc/conf.d/klogd klogd -n $KLOGD_OPTS -- Kernel log daemon runlevel = "S12345"
service [2345] env:-/etc/conf.d/lldpd lldpd -d $LLDPD_OPTS -- LLDP daemon (IEEE 802.1ab) envfile = "-/etc/conf.d/watchdog"
command = "watchdog $WATCHDOG_OPTS $WATCHDOG_DEV"
}
service syslogd {
description = "System log daemon"
runlevel = "S12345"
envfile = "-/etc/conf.d/syslog"
command = "syslogd -n $SYSLOGD_OPTS"
}
service klogd {
description = "Kernel log daemon"
runlevel = "S12345"
conditions = { "pid/syslogd" }
envfile = "-/etc/conf.d/klogd"
command = "klogd -n $KLOGD_OPTS"
}
service lldpd {
description = "LLDP daemon (IEEE 802.1ab)"
runlevel = "2345"
envfile = "-/etc/conf.d/lldpd"
command = "lldpd -d $LLDPD_OPTS"
}
# The BusyBox ntpd does not use syslog when running in the foreground # The BusyBox ntpd does not use syslog when running in the foreground
# So we use this trick to redirect stdout/stderr to a log file. The # So we use this trick to redirect stdout/stderr to a log file. The
# log file is rotated with the above settings. The condition declares # log file is rotated with the above settings. The condition declares
# a dependency on a system default route (gateway) to be set. A single # a dependency on a system default route (gateway) to be set. ntpd
# <!> at the beginning means ntpd does not respect SIGHUP for restart. # does not respect SIGHUP, so Finit restarts it on reload instead.
service [2345] log:/var/log/ntpd.log <!net/route/default> ntpd -n -l -I eth0 -- NTP daemon service ntpd {
description = "NTP daemon"
runlevel = "2345"
conditions = { "net/route/default" }
reload-signal = "none"
log { file = "/var/log/ntpd.log" }
command = "ntpd -n -l -I eth0"
}
# For multiple instances of the same service, add :ID somewhere between # For multiple instances of the same service, add :ID to the block title.
# the service/run/task keyword and the command. service merecat:80 {
service :80 [2345] merecat -n -p 80 /var/www -- Web server description = "Web server"
service :8080 [2345] merecat -n -p 8080 /var/www -- Old web server runlevel = "2345"
command = "merecat -n -p 80 /var/www"
}
service merecat:8080 {
description = "Old web server"
runlevel = "2345"
command = "merecat -n -p 8080 /var/www"
}
# Alternative method instead of below runparts, can also use /etc/rc.local # Alternative method instead of below runparts, can also use /etc/rc.local
#sysv [S] /etc/init.d/keyboard-setup -- Setting up preliminary keymap #sysv keyboard-setup {
#sysv [S] /etc/init.d/acpid -- Starting ACPI Daemon # description = "Setting up preliminary keymap"
#task [S] /etc/init.d/kbd -- Preparing console # runlevel = "S"
# command = "/etc/init.d/keyboard-setup"
#}
# Hidden from boot progress, using empty `--` description # Hidden from boot progress, using an empty description
#sysv [S] /etc/init.d/keyboard-setup -- #sysv acpid {
#sysv [S] /etc/init.d/acpid -- # description = ""
#task [S] /etc/init.d/kbd -- # runlevel = "S"
# command = "/etc/init.d/acpid"
#}
# Run start scripts from this directory # Run start scripts from this directory
# runparts /etc/start.d #runparts = "/etc/start.d"
# Virtual consoles run BusyBox getty, keep kernel default speed # Virtual consoles run BusyBox getty, keep kernel default speed
tty [12345] /sbin/getty -L 0 /dev/tty1 linux nowait noclear tty tty1 {
tty [2345] /sbin/getty -L 0 /dev/tty2 linux nowait noclear runlevel = "12345"
tty [2345] /sbin/getty -L 0 /dev/tty3 linux nowait noclear command = "/sbin/getty -L 0 /dev/tty1 linux"
nowait = true
noclear = true
}
tty tty2 {
runlevel = "2345"
command = "/sbin/getty -L 0 /dev/tty2 linux"
nowait = true
noclear = true
}
tty tty3 {
runlevel = "2345"
command = "/sbin/getty -L 0 /dev/tty3 linux"
nowait = true
noclear = true
}
# Use built-in getty for serial port and USB serial # Use built-in getty for serial port and USB serial
#tty [12345] /dev/ttyAMA0 noclear nowait #tty ttyAMA0 { runlevel = "12345" device = "/dev/ttyAMA0" noclear = true nowait = true }
#tty [12345] /dev/ttyUSB0 noclear #tty ttyUSB0 { runlevel = "12345" device = "/dev/ttyUSB0" noclear = true }
# Just give me a shell, I need to debug this embedded system! # Just give me a shell, I need to debug this embedded system!
#tty [12345] console noclear nologin #tty console { runlevel = "12345" device = "@console" noclear = true nologin = true }
``` ```
The `service` stanza, as well as `task`, `run` and others are described in The `service` block, as well as `task`, `run` and others are described in
full in the [Services Syntax](config/services.md) section. full in the [Services Syntax](config/services.md) section.
Here's a quick overview of some of the most common components needed to start Here's a quick overview of some of the most common components needed to start
a UNIX daemon: a UNIX daemon:
``` ```
service [LVLS] <COND> log env:[-]/etc/default/daemon daemon ARGS -- Example daemon service NAME { <-- Supervised program (daemon)
^ ^ ^ ^ ^ ^ ^ ^ description = "Example daemon" <-- Optional description
| | | | | | | `---------- Optional description runlevel = "2345" <-- Optional runlevels
| | | | | | `------------------ Daemon arguments conditions = { "net/route/default" } <-- Optional conditions
| | | | | `------------------------- Path to daemon envfile = "-/etc/default/daemon" <-- Optional env. file
| | | | `---------------------------------------------------- Optional env. file log { } <-- Redirect output to log
| | | `-------------------------------------------------------- Redirect output to log command = "daemon ARGS" <-- Path to daemon, and its arguments
| | `--------------------------------------------------------------- Optional conditions }
| `---------------------------------------------------------------------- Optional Runlevels
`------------------------------------------------------------------------------ Supervised program (daemon)
``` ```
Some components are optional: runlevel(s), condition(s) and description, Only `command` is required, which makes simple cases short while leaving
making it easy to create simple start scripts and still possible for more room for more advanced uses:
advanced uses as well:
service /usr/sbin/sshd -D service sshd {
command = "/usr/sbin/sshd -D"
}
Dependencies are handled using [conditions](conditions.md). One of Dependencies are handled using [conditions](conditions.md). One of
the most common conditions is to wait for basic networking to become the most common conditions is to wait for basic networking to become
available: available:
service <net/route/default> nginx -- High performance HTTP server service nginx {
description = "High performance HTTP server"
conditions = { "net/route/default" }
command = "nginx"
}
Here is another example where we instruct Finit to not start BusyBox Here is another example where we instruct Finit to not start BusyBox
`ntpd` until `syslogd` has started properly. Finit waits for `syslogd` `ntpd` until `syslogd` has started properly. Finit waits for `syslogd`
to create its PID file, by default `/var/run/syslogd.pid`. to create its PID file, by default `/var/run/syslogd.pid`.
service [2345] log <!pid/syslogd> ntpd -n -N -p pool.ntp.org service ntpd {
service [S12345] syslogd -n -- Syslog daemon runlevel = "2345"
conditions = { "pid/syslogd" }
reload-signal = "none"
log { }
command = "ntpd -n -N -p pool.ntp.org"
}
Notice the `log` keyword, BusyBox `ntpd` uses `stderr` for logging when service syslogd {
run in the foreground. With `log` Finit redirects `stdout` + `stderr` description = "Syslog daemon"
to the system log daemon using the command line `logger(1)` tool. runlevel = "S12345"
command = "syslogd -n"
}
Notice the empty `log` block, BusyBox `ntpd` uses `stderr` for logging
when run in the foreground. With it Finit redirects `stdout` +
`stderr` to the system log daemon using the command line `logger(1)`
tool.
A service, or task, can have multiple dependencies listed. Here we wait A service, or task, can have multiple dependencies listed. Here we wait
for *both* `syslogd` to have started and basic networking to be up: for *both* `syslogd` to have started and basic networking to be up:
service [2345] log <pid/syslogd,net/route/default> ntpd -n -N -p pool.ntp.org service ntpd {
runlevel = "2345"
conditions = { "pid/syslogd", "net/route/default" }
log { }
command = "ntpd -n -N -p pool.ntp.org"
}
If either condition fails, e.g. loss of networking, `ntpd` is stopped If either condition fails, e.g. loss of networking, `ntpd` is stopped
and as soon as it comes back up again `ntpd` is restarted automatically. and as soon as it comes back up again `ntpd` is restarted automatically.
+41 -25
View File
@@ -18,9 +18,9 @@ waits for user input before handing over to `/bin/login`, which is
responsible for handling the actual authentication. responsible for handling the actual authentication.
```conf ```conf
tty [12345] /dev/tty1 nowait linux tty tty1 { runlevel = "12345" device = "/dev/tty1" term = "linux" nowait = true }
tty [12345] /dev/ttyAMA0 noclear vt100 tty ttyAMA0 { runlevel = "12345" device = "/dev/ttyAMA0" term = "vt100" noclear = true }
tty [12345] /sbin/getty -L /dev/ttyAMA0 vt100 tty getty { runlevel = "12345" command = "/sbin/getty -L /dev/ttyAMA0 vt100" }
``` ```
Users of embedded systems may want to enable automatic serial console Users of embedded systems may want to enable automatic serial console
@@ -29,7 +29,7 @@ system uses `ttyS0`, `ttyAMA0`, `ttyMXC0`, or anything else. Finit
figures it out by querying sysfs: `/sys/class/tty/console/active`. figures it out by querying sysfs: `/sys/class/tty/console/active`.
```conf ```conf
tty [12345] @console linux noclear tty console { runlevel = "12345" device = "@console" term = "linux" noclear = true }
``` ```
Notice the optional `noclear`, `nowait`, and `nologin` flags. The Notice the optional `noclear`, `nowait`, and `nologin` flags. The
@@ -40,21 +40,21 @@ see the [TTY and Consoles](config/tty.md) section.
**Runlevels** **Runlevels**
Support for SysV init-style [runlevels][5] is available, in the same Support for SysV init-style [runlevels][5] is available, in the same
minimal style as everything else in Finit. The `[2345]` syntax can be minimal style as everything else in Finit. The `runlevel` setting
applied to service, task, run, and TTY stanzas. applies to service, task, run, and tty blocks alike.
Reserved runlevels are 0 and 6, halt and reboot, respectively just like Reserved runlevels are 0 and 6, halt and reboot, respectively just like
SysV init. Runlevel 1 can be configured freely, but is recommended to SysV init. Runlevel 1 can be configured freely, but is recommended to
be kept as the system single-user runlevel since Finit will not start be kept as the system single-user runlevel since Finit will not start
networking here. The configured `runlevel NUM` from `/etc/finit.conf` networking here. The configured `runlevel` from `/etc/finit.conf`
is what Finit changes to after bootstrap, unless 'single' (or 'S') is is what Finit changes to after bootstrap, unless 'single' (or 'S') is
given on the kernel cmdline, in which case runlevel 1 is started. given on the kernel cmdline, in which case runlevel 1 is started.
All services in runlevel S) are started first, followed by the desired All services in runlevel S are started first, followed by the desired
run-time runlevel. Run tasks in runlevel S can be started in sequence run-time runlevel. Run tasks in runlevel S can be started in sequence
by using `run [S] cmd`. Changing runlevels at runtime is done like any by using a `run` block with `runlevel = "S"`. Changing runlevels at
other init, e.g. <kbd>init 4</kbd>, but also using the more advanced runtime is done like any other init, e.g. <kbd>init 4</kbd>, but also
[`initctl`](initctl.md) tool. using the more advanced [`initctl`](initctl.md) tool.
**Conditions** **Conditions**
@@ -86,8 +86,17 @@ the condition `<hw/model/foo>` when starting other scripts. Here is an
example: example:
``` ```
run [S] /path/to/ident -- run ident {
task [2] <hw/model/foo> /path/to/foo-init -- Initializing Foo board description = ""
runlevel = "S"
command = "/path/to/ident"
}
task foo-init {
description = "Initializing Foo board"
runlevel = "2"
conditions = { "hw/model/foo" }
command = "/path/to/foo-init"
}
``` ```
> [!TIP] > [!TIP]
@@ -141,17 +150,20 @@ required privileges instead of running as root. This improves security by
following the principle of least privilege. following the principle of least privilege.
```conf ```conf
service [2345] name:nginx \ service nginx {
www-data:www-data \ runlevel = "2345"
caps:^cap_net_bind_service \ user = "www-data"
/usr/sbin/nginx -g 'daemon off;' group = "www-data"
capabilities = { "^cap_net_bind_service" }
command = "/usr/sbin/nginx -g 'daemon off;'"
}
``` ```
In this example, nginx runs as the unprivileged `www-data` user but retains In this example, nginx runs as the unprivileged `www-data` user but retains
the ability to bind to privileged ports (80, 443) through the the ability to bind to privileged ports (80, 443) through the
`cap_net_bind_service` capability. `cap_net_bind_service` capability.
The `caps:` directive uses the IAB (Inheritable, Ambient, Bounding) format: The `capabilities` list uses the IAB (Inheritable, Ambient, Bounding) format:
- `^` = Ambient (recommended) - capabilities survive exec() - `^` = Ambient (recommended) - capabilities survive exec()
- `%` = Inheritable only - requires file capabilities - `%` = Inheritable only - requires file capabilities
- `!` = Bounding - block from acquiring capability - `!` = Bounding - block from acquiring capability
@@ -159,7 +171,7 @@ The `caps:` directive uses the IAB (Inheritable, Ambient, Bounding) format:
Multiple capabilities can be specified as comma-separated: Multiple capabilities can be specified as comma-separated:
```conf ```conf
caps:^cap_net_raw,^cap_net_admin,!cap_sys_admin capabilities = { "^cap_net_raw", "^cap_net_admin", "!cap_sys_admin" }
``` ```
See the [Linux Capabilities](config/capabilities.md) section for detailed See the [Linux Capabilities](config/capabilities.md) section for detailed
@@ -210,15 +222,19 @@ The name of each sub-group is taken from the username.
A fourth group also exists, the `root` group. It is also _reserved_ and A fourth group also exists, the `root` group. It is also _reserved_ and
primarily intended for RT tasks. If you have RT tasks they need to be primarily intended for RT tasks. If you have RT tasks they need to be
declared as such in their service stanza like this: declared as such in their service block like this:
service [...] <...> cgroup.root /path/to/foo args -- description service foo {
cgroup root {}
command = "/path/to/foo args"
}
or Every block names the group it joins, so a second RT task says so too:
cgroup.root service bar {
service [...] <...> /path/to/foo args -- description cgroup root {}
service [...] <...> /path/to/bar args -- description command = "/path/to/bar args"
}
See the [Cgroups](config/cgroups.md) section for more information, e.g., See the [Cgroups](config/cgroups.md) section for more information, e.g.,
how to configure per-group limits. how to configure per-group limits.
+2 -2
View File
@@ -70,8 +70,8 @@ For your convenience a set of *optional* plugins are available:
name of the module to load. Any line starting with the standard UNIX name of the module to load. Any line starting with the standard UNIX
comment character, `#`, or `;`, is skipped. comment character, `#`, or `;`, is skipped.
Modules are by default loaded in runlevel `S` using the `task` stanza. Modules are by default loaded in runlevel `S` using a `task` block.
Each module gets a unique `name:modprobe.foo`, and optional`:ID`. The Each module is named `modprobe.foo`, with an optional `:ID`. The
runlevel can be changed per file using: runlevel can be changed per file using:
set runlevel 2345 set runlevel 2345
+5 -1
View File
@@ -15,7 +15,11 @@ done slightly differently and on systems with udev you might want to add
the following one-shot task early in your `/etc/finit.conf`: the following one-shot task early in your `/etc/finit.conf`:
```conf ```conf
run [S] udevadm settle --timeout=120 -- Waiting for udev run udevadm {
description = "Waiting for udev"
runlevel = "S"
command = "udevadm settle --timeout=120"
}
``` ```
Finit has a built-in Getty for TTYs, but requires a working `/bin/login` Finit has a built-in Getty for TTYs, but requires a working `/bin/login`
+36 -13
View File
@@ -13,20 +13,37 @@ more of a policy for the user to define. Normally only runlevels 1-6
are used, and even more commonly, only the default runlevel is used. are used, and even more commonly, only the default runlevel is used.
To specify an allowed set of runlevels for a `service`, `run` command, To specify an allowed set of runlevels for a `service`, `run` command,
`task`, or `tty`, add `[NNN]` to your `/etc/finit.conf`, like this: `task`, or `tty`, set `runlevel` in your `/etc/finit.conf`, like this:
``` ```
service [S12345] syslogd -n -x -- System log daemon service syslogd {
run [S] /etc/init.d/acpid start -- Starting ACPI Daemon description = "System log daemon"
task [S] /etc/init.d/kbd start -- Preparing console runlevel = "S12345"
service [S12345] <pid/syslogd> klogd -n -x -- Kernel log daemon command = "syslogd -n -x"
}
run acpid {
description = "Starting ACPI Daemon"
runlevel = "S"
command = "/etc/init.d/acpid start"
}
task kbd {
description = "Preparing console"
runlevel = "S"
command = "/etc/init.d/kbd start"
}
service klogd {
description = "Kernel log daemon"
runlevel = "S12345"
conditions = { "pid/syslogd" }
command = "klogd -n -x"
}
tty [12345] /dev/tty1 tty tty1 { runlevel = "12345" device = "/dev/tty1" }
tty [2] /dev/tty2 tty tty2 { runlevel = "2" device = "/dev/tty2" }
tty [2] /dev/tty3 tty tty3 { runlevel = "2" device = "/dev/tty3" }
tty [2] /dev/tty4 tty tty4 { runlevel = "2" device = "/dev/tty4" }
tty [2] /dev/tty5 tty tty5 { runlevel = "2" device = "/dev/tty5" }
tty [2] /dev/tty6 tty tty6 { runlevel = "2" device = "/dev/tty6" }
``` ```
In this example syslogd is first started, in parallel, and then acpid is In this example syslogd is first started, in parallel, and then acpid is
@@ -46,8 +63,14 @@ are also removed when they have completed, `initctl show` will not list
them. them.
``` ```
task [S] echo "foo" | cat >/tmp/bar task foo {
run [S] echo "$HOME" >/tmp/secret runlevel = "S"
command = "echo \"foo\" | cat >/tmp/bar"
}
run secret {
runlevel = "S"
command = "echo \"$HOME\" >/tmp/secret"
}
``` ```
Switching between runlevels can be done by calling init with a single Switching between runlevels can be done by calling init with a single
+4 -4
View File
@@ -3,18 +3,18 @@ Runparts & `/etc/rc.local`
At the end of the boot, when all bootstrap (`S`) tasks and services have At the end of the boot, when all bootstrap (`S`) tasks and services have
started, but not networking, Finit calls its built-in [run-parts(8)][] started, but not networking, Finit calls its built-in [run-parts(8)][]
command on any configured `runparts <DIR>` directory. This happens just command on any configured `runparts = "DIR"` directory. This happens just
before changing to the configured runlevel (default 2). (Networking is before changing to the configured runlevel (default 2). (Networking is
enabled just prior to changing from single user mode.) enabled just prior to changing from single user mode.)
```shell ```aconf
runparts /etc/rc.d/ runparts = "/etc/rc.d/"
``` ```
Right after the runlevel change when all services have started properly, Right after the runlevel change when all services have started properly,
`/etc/rc.local` is called. `/etc/rc.local` is called.
No configuration stanza in `/etc/finit.conf` is required for `rc.local`. No setting in `/etc/finit.conf` is required for `rc.local`.
If it exists and is an executable shell script Finit calls it at the very If it exists and is an executable shell script Finit calls it at the very
end of the boot, before calling the `HOOK_SYSTEM_UP`. See more in the end of the boot, before calling the `HOOK_SYSTEM_UP`. See more in the
[Hook Scripts](plugins.md#hooks) section. [Hook Scripts](plugins.md#hooks) section.
+30 -9
View File
@@ -27,13 +27,22 @@ Finit can *not* start and monitor a daemon that:
### Forks to bg w/ PID file ### Forks to bg w/ PID file
There are two syntax variants, type 1 and type 2. The former is the There are two variants. The former names the pidfile to watch, as for
traditional one used also for `sysv` start/stop scripts, and the latter `sysv` start/stop scripts, and the latter is inspired by systemd, with a
is inspired by systemd, with a twist -- it lets Finit guess the pifdile twist -- it lets Finit guess the pidfile based on the standard path and
to look for based on the standard path and the basename of the command. the basename of the command.
service pid:!/run/serv.pid serv -- Forking service, type 1 service serv {
service type:forking serv -- Forking service, type 2 description = "Forking service, type 1"
pidfile = "/run/serv.pid"
command = "serv"
}
service serv {
description = "Forking service, type 2"
type = "forking"
command = "serv"
}
In this example the resulting files to watch for are `/run/serv.pid` and In this example the resulting files to watch for are `/run/serv.pid` and
`/var/run/serv.pid`, respectively. On most modern Linux systems this is `/var/run/serv.pid`, respectively. On most modern Linux systems this is
@@ -41,17 +50,29 @@ the same directory (`/var/run` is a symlink to `../run`).
### Runs in fg w/ PID file ### Runs in fg w/ PID file
service serv -n -p -- Foreground service w/ PID file service serv {
description = "Foreground service w/ PID file"
command = "serv -n -p"
}
### Runs in fg w/o PID file ### Runs in fg w/o PID file
Same as previous, but we tell Finit to create the PID file, because we Same as previous, but we tell Finit to create the PID file, because we
need it to synchronize start/stop of a dependent service. need it to synchronize start/stop of a dependent service.
service pid:/run/serv.pid serv -n -- Foreground service w/o PID file service serv {
description = "Foreground service w/o PID file"
pidfile = "/run/serv.pid"
pidfile-create = true
command = "serv -n"
}
### Runs in fg w/ custom PID file ### Runs in fg w/ custom PID file
service pid:/run/servy.pid serv -n -p -P /run/servy.pid -- Foreground service w/ custom PID file service serv {
description = "Foreground service w/ custom PID file"
pidfile = "/run/servy.pid"
command = "serv -n -p -P /run/servy.pid"
}
[1]: config/service-sync.md [1]: config/service-sync.md
+55 -11
View File
@@ -51,27 +51,52 @@ Configuration file `/etc/finit.conf` in the initramfs:
# /etc/finit.conf in initramfs # /etc/finit.conf in initramfs
# Mount the real root filesystem # Mount the real root filesystem
run [S] name:mount-root /bin/mount /dev/sda1 /mnt/root -- Mounting root filesystem run mount-root {
description = "Mounting root filesystem"
runlevel = "S"
command = "/bin/mount /dev/sda1 /mnt/root"
}
# Switch to real root after mount completes # Switch to real root after mount completes
run [S] name:switch-root /sbin/initctl switch-root /mnt/root -- Switching to real root run switch-root {
description = "Switching to real root"
runlevel = "S"
command = "/sbin/initctl switch-root /mnt/root"
}
``` ```
For more complex setups (LUKS, LVM, etc.): For more complex setups (LUKS, LVM, etc.):
``` ```
# Unlock LUKS volume # Unlock LUKS volume
# The tty:@console stanza is required so cryptsetup can prompt for a passphrase # The tty setting is required so cryptsetup can prompt for a passphrase
run [S] name:cryptsetup tty:@console /sbin/cryptsetup open /dev/sda2 cryptroot -- Unlocking encrypted root run cryptsetup {
description = "Unlocking encrypted root"
runlevel = "S"
tty = "@console"
command = "/sbin/cryptsetup open /dev/sda2 cryptroot"
}
# Activate LVM # Activate LVM
run [S] name:lvm /sbin/lvm vgchange -ay -- Activating LVM volumes run lvm {
description = "Activating LVM volumes"
runlevel = "S"
command = "/sbin/lvm vgchange -ay"
}
# Mount root # Mount root
run [S] name:mount-root /bin/mount /dev/vg0/root /mnt/root -- Mounting root run mount-root {
description = "Mounting root"
runlevel = "S"
command = "/bin/mount /dev/vg0/root /mnt/root"
}
# Switch root # Switch root
run [S] name:switch-root /sbin/initctl switch-root /mnt/root -- Switching to real root run switch-root {
description = "Switching to real root"
runlevel = "S"
command = "/sbin/initctl switch-root /mnt/root"
}
``` ```
@@ -85,15 +110,34 @@ difficult in runlevel S, you can perform the switch-root in runlevel 1:
# /etc/finit.conf in initramfs # /etc/finit.conf in initramfs
# Start mdevd for device handling # Start mdevd for device handling
service [S] name:mdevd notify:s6 /sbin/mdevd -D %n -- Device event daemon service mdevd {
run [S] name:coldplug <service/mdevd/ready> /sbin/mdevd-coldplug -- Coldplug devices description = "Device event daemon"
runlevel = "S"
notify = "s6"
command = "/sbin/mdevd -D %n"
}
run coldplug {
description = "Coldplug devices"
runlevel = "S"
conditions = { "service/mdevd/ready" }
command = "/sbin/mdevd-coldplug"
}
# Mount the real root filesystem (after devices are ready) # Mount the real root filesystem (after devices are ready)
run [S] name:mount-root <run/coldplug/success> /bin/mount /dev/sda1 /mnt/root -- Mounting root run mount-root {
description = "Mounting root"
runlevel = "S"
conditions = { "run/coldplug/success" }
command = "/bin/mount /dev/sda1 /mnt/root"
}
# Transition to runlevel 1 after all S tasks complete # Transition to runlevel 1 after all S tasks complete
# The switch-root runs cleanly in runlevel 1 # The switch-root runs cleanly in runlevel 1
run [1] name:switch-root /sbin/initctl switch-root /mnt/root -- Switching to real root run switch-root {
description = "Switching to real root"
runlevel = "1"
command = "/sbin/initctl switch-root /mnt/root"
}
``` ```
This approach separates the initramfs setup (runlevel S) from the This approach separates the initramfs setup (runlevel S) from the
+3 -2
View File
@@ -59,6 +59,7 @@ finit_SOURCES = api.c cgroup.c cgroup.h \
clone3.c clone3.h \ clone3.c clone3.h \
cond.c cond-w.c cond.h \ cond.c cond-w.c cond.h \
conf.c conf.h \ conf.c conf.h \
legacy.c legacy.h \
devmon.c devmon.h \ devmon.c devmon.h \
exec.c finit.c finit.h \ exec.c finit.c finit.h \
stty.c \ stty.c \
@@ -86,8 +87,8 @@ pkginclude_HEADERS = cgroup.h cond.h conf.h finit.h helpers.h log.h \
finit_CPPFLAGS = $(AM_CPPFLAGS) -D__FINIT__ finit_CPPFLAGS = $(AM_CPPFLAGS) -D__FINIT__
finit_CFLAGS = -W -Wall -Wextra -Wno-unused-parameter -std=gnu99 finit_CFLAGS = -W -Wall -Wextra -Wno-unused-parameter -std=gnu99
finit_CFLAGS += $(lite_CFLAGS) $(uev_CFLAGS) finit_CFLAGS += $(lite_CFLAGS) $(uev_CFLAGS) $(confuse_CFLAGS)
finit_LDADD = $(lite_LIBS) $(uev_LIBS) finit_LDADD = $(lite_LIBS) $(uev_LIBS) $(confuse_LIBS)
if STATIC if STATIC
finit_LDADD += ../plugins/libplug.la finit_LDADD += ../plugins/libplug.la
else else
+3
View File
@@ -26,6 +26,9 @@
#include <uev/uev.h> #include <uev/uev.h>
#define CGROUP_NAME_SIZE 16
#define CGROUP_SETTINGS_SIZE 128
/* Forward declaration */ /* Forward declaration */
typedef struct svc svc_t; typedef struct svc svc_t;
+15 -54
View File
@@ -34,6 +34,7 @@
#include "cgutil.h" #include "cgutil.h"
#include "initctl.h" #include "initctl.h"
#include "log.h" #include "log.h"
#include "util.h"
#define CDIM plain ? "" : "\e[2m" #define CDIM plain ? "" : "\e[2m"
#define CRST plain ? "" : "\e[0m" #define CRST plain ? "" : "\e[0m"
@@ -54,42 +55,18 @@ int cgroup_avail(void)
return fismnt(FINIT_CGPATH); return fismnt(FINIT_CGPATH);
} }
static size_t flen(FILE *fp)
{
size_t total = 0, sz;
char buf[512];
while ((sz = fread(buf, 1, sizeof(buf), fp)) > 0)
total += sz;
rewind(fp);
return total;
}
char *pid_cmdline(int pid) char *pid_cmdline(int pid)
{ {
size_t i, len; size_t i, len;
char *buf; char *buf;
FILE *fp;
fp = fopenf("r", "/proc/%d/cmdline", pid); buf = fslurp(&len, "/proc/%d/cmdline", pid);
if (!fp) if (!buf)
return strdup(""); /* regular process */ return strdup(""); /* regular process */
len = flen(fp);
if (len == 0) { if (len == 0) {
fail:
fclose(fp);
return NULL; /* kernel thread */
}
buf = calloc(len + 1, 1);
if (!buf)
goto fail;
if (fread(buf, 1, len, fp) < len) {
free(buf); free(buf);
goto fail; return NULL; /* kernel thread */
} }
/* replace all NUL chars with space */ /* replace all NUL chars with space */
@@ -98,46 +75,30 @@ char *pid_cmdline(int pid)
buf[i] = ' '; buf[i] = ' ';
} }
fclose(fp);
return buf; return buf;
} }
char *pid_cgroup(int pid) char *pid_cgroup(int pid)
{ {
char *buf, *ptr = NULL; char *buf, *ptr;
size_t len;
FILE *fp;
fp = fopenf("r", "/proc/%d/cgroup", pid); buf = fslurp(NULL, "/proc/%d/cgroup", pid);
if (!fp) if (!buf)
return NULL; return NULL;
len = flen(fp); ptr = strchr(buf, '\n'); /* first line only */
if (len == 0) { if (ptr)
fclose(fp); *ptr = 0;
return NULL; chomp(buf);
}
len++;
buf = calloc(1, len);
if (!buf) {
fclose(fp);
return NULL;
}
if (fgets(buf, len, fp))
ptr = chomp(buf);
fclose(fp);
ptr = strchr(buf, '/');
if (ptr) { if (ptr) {
ptr = strchr(ptr, '/'); memmove(buf, ptr, strlen(ptr) + 1);
if (ptr) { return buf;
memmove(buf, ptr, strlen(ptr) + 1);
return buf;
}
} }
free(buf); free(buf);
return NULL; return NULL;
} }
+1367 -559
View File
File diff suppressed because it is too large. Load diff
+33 -21
View File
@@ -27,6 +27,10 @@
#include "cgroup.h" #include "cgroup.h"
#include "svc.h" #include "svc.h"
#include <sys/resource.h>
#define BOOTSTRAP (runlevel == INIT_LEVEL)
extern int runlevel; extern int runlevel;
extern int cfglevel; extern int cfglevel;
extern int cmdlevel; extern int cmdlevel;
@@ -46,31 +50,39 @@ extern char *hostname;
extern char *runparts; extern char *runparts;
extern char *osheading; extern char *osheading;
extern int logfile_size_max; extern int runparts_progress;
extern int logfile_count_max; extern int runparts_sysv;
extern struct rlimit global_rlimit[]; extern int logfile_size_max;
extern char cgroup_current[]; extern int logfile_count_max;
extern char cgroup_settings_current[];
extern int cgroup_delegate_current;
int str2rlim(char *str); extern char cgroup_current[CGROUP_NAME_SIZE];
char *rlim2str(int rlim); extern char cgroup_settings_current[CGROUP_SETTINGS_SIZE];
extern int cgroup_delegate_current;
int conf_init (uev_ctx_t *ctx); extern struct rlimit initial_rlimit[RLIMIT_NLIMITS];
void conf_reload (void); extern struct rlimit global_rlimit[RLIMIT_NLIMITS];
int conf_any_change (void);
int conf_changed (char *file); int conf_init (uev_ctx_t *ctx);
int conf_monitor (void); int conf_reload (void);
int conf_any_change (void);
int conf_changed (char *file);
int conf_monitor (void);
void conf_saverc (void);
void conf_save_exec_order (svc_t *svc, char *cmdline, int result);
void conf_save_service (int type, char *cfg, char *file);
void conf_parse_cmdline (int argc, char *argv[]);
void conf_reset_env (void);
void conf_set_env (char *line);
char *conf_parse_env (char *line, char **val);
void conf_parse_rlimit (char *line, struct rlimit arr[]);
int conf_parse_runlevels (const char *runlevels);
void conf_parse_cond (svc_t *svc, char *cond);
int conf_parse_file (char *file, int is_rcsd);
void conf_reset_env (void);
void conf_saverc (void);
void conf_save_exec_order (svc_t *svc, char *cmdline, int result);
void conf_save_service (int type, char *cfg, char *file);
void conf_parse_cmdline (int argc, char *argv[]);
char *conf_parse_env (char *line, char **val);
int conf_parse_runlevels (char *runlevels);
void conf_parse_cond (svc_t *svc, char *cond);
#endif /* FINIT_CONF_H_ */ #endif /* FINIT_CONF_H_ */
+19
View File
@@ -65,6 +65,25 @@ uev_ctx_t *ctx; /* Main loop context */
svc_t *wdog; /* No watchdog by default */ svc_t *wdog; /* No watchdog by default */
char *arg0; /* Saved for setprocnm() */ char *arg0; /* Saved for setprocnm() */
/*
* --enable-fastboot => fsck_mode: NULL => no fsck by default
* --enable-fsckfix => fsck_mode: "-f" + fsck_repair: "y"
*/
#ifdef FSCK_FIX
# ifdef FAST_BOOT
char *fsck_mode = NULL;
# else
char *fsck_mode = "-f";
# endif
char *fsck_repair = "-y";
#else
# ifdef FAST_BOOT
char *fsck_mode = NULL;
# else
char *fsck_mode = "";
# endif
char *fsck_repair = "-p";
#endif
/* /*
* Show user configured banner before service bootstrap progress * Show user configured banner before service bootstrap progress
+46
View File
@@ -470,6 +470,52 @@ done:
} }
} }
static int kmod_exists(const char *mod)
{
char buf[256];
int found = 0;
FILE *fp;
fp = fopen("/proc/modules", "r");
if (!fp)
return 0;
while (!found && fgets(buf, sizeof(buf), fp)) {
const char *kmod = strtok(buf, " \t");
if (kmod && !strcmp(kmod, mod))
found = 1;
}
fclose(fp);
return found;
}
/*
* Load a kernel module during bootstrap
*/
void kmod_load(char *mod)
{
char module[64] = { 0 };
char cmd[CMD_SIZE];
if (runlevel != INIT_LEVEL)
return;
/* Strip args for progress below and kmod_exists() */
strlcpy(module, mod, sizeof(module));
if (!strtok(module, " \t"))
return;
if (kmod_exists(module))
return;
strcpy(cmd, "modprobe ");
strlcat(cmd, mod, sizeof(cmd));
run_interactive(cmd, "Loading kernel module %s", module);
}
/* /*
* Bring up networking, but only if not single-user or rescue mode * Bring up networking, but only if not single-user or rescue mode
*/ */
+14
View File
@@ -71,6 +71,7 @@ int print_result (int fail);
void print_exit (void); void print_exit (void);
void set_hostname (char **hostname); void set_hostname (char **hostname);
void kmod_load (char *mod);
void networking (int updown); void networking (int updown);
int in_container (void); int in_container (void);
void setprocnm (const char *name); void setprocnm (const char *name);
@@ -113,6 +114,19 @@ static inline int dprint(int fd, const char *s, size_t len)
return rc; return rc;
} }
static inline int istrue(char *arg, int defval)
{
if (!arg)
goto fallback;
if (string_compare(arg, "true") || string_compare(arg, "on") || string_compare(arg, "1"))
return 1;
if (string_compare(arg, "false") || string_compare(arg, "off") || string_compare(arg, "0"))
return 0;
fallback:
return defval;
}
static inline char *fgetval(const char *line, const char *key, char *sep) static inline char *fgetval(const char *line, const char *key, char *sep)
{ {
char *ptr, *str, *copy; char *ptr, *str, *copy;
+374
View File
@@ -0,0 +1,374 @@
/* Legacy one-liner parser for /etc/finit.conf and the finit.d hierarchy
*
* NOTE: this parser is frozen at the Finit 4.x feature set. It is
* kept, indefinitely, for backwards compatibility -- existing
* configurations must keep working. All new directives and
* options land in the libconfuse-based format, see conf.c
*
* Copyright (c) 2012-2025 Joachim Wiberg <troglobit@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "config.h" /* Generated by configure script */
#include <ctype.h>
#include <dirent.h>
#include <string.h>
#include <sys/resource.h>
#ifdef _LIBITE_LITE
# include <libite/lite.h>
# include <libite/queue.h> /* BSD sys/queue.h API */
#else
# include <lite/lite.h>
# include <lite/queue.h> /* BSD sys/queue.h API */
#endif
#include "finit.h"
#include "conf.h"
#include "legacy.h"
#include "private.h"
#include "service.h"
#include "tty.h"
#include "helpers.h"
#include "util.h"
/* cgroup NAME ctrl.prop:value,ctrl.prop:value ... */
static void conf_parse_cgroup(char *line)
{
char config[strlen(line) + 1];
char *ptr, *name;
name = strtok(line, " \t");
if (!name)
return;
if (strstr(name, "..") || strchr(name, '/'))
return; /* illegal */
config[0] = 0;
while ((ptr = strtok(NULL, " \t"))) {
if (config[0])
strlcat(config, ",", sizeof(config));
strlcat(config, ptr, sizeof(config));
}
cgroup_add(name, config, 0);
}
static int parse_static(char *line, int is_rcsd)
{
char cmd[CMD_SIZE];
char *x;
if (BOOTSTRAP && (MATCH_CMD(line, "host ", x) || MATCH_CMD(line, "hostname ", x))) {
if (hostname) free(hostname);
hostname = strdup(strip_line(x));
return 0;
}
if (BOOTSTRAP && MATCH_CMD(line, "mknod ", x)) {
char *dev = strip_line(x);
strcpy(cmd, "mknod ");
strlcat(cmd, dev, sizeof(cmd));
run_interactive(cmd, "Creating device node %s", dev);
return 0;
}
/* Kernel module to load */
if (BOOTSTRAP && MATCH_CMD(line, "module ", x)) {
kmod_load(strip_line(x));
return 0;
}
if (BOOTSTRAP && MATCH_CMD(line, "network ", x)) {
if (network) free(network);
network = strdup(strip_line(x));
return 0;
}
if (BOOTSTRAP && MATCH_CMD(line, "rcsd ", x)) {
if (finit_rcsd) free(finit_rcsd);
finit_rcsd = strdup(strip_line(x));
return 0;
}
if (BOOTSTRAP && MATCH_CMD(line, "runparts ", x)) {
if (runparts) free(runparts);
runparts_progress = runparts_sysv = 0;
while (x) {
if (MATCH_CMD(x, "progress", x))
runparts_progress = 1;
else if (MATCH_CMD(x, "sysv", x))
runparts_sysv = 1;
else
break;
}
runparts = strdup(strip_line(x));
return 0;
}
if (BOOTSTRAP && MATCH_CMD(line, "set ", x)) {
conf_set_env(x);
return 0;
}
if (MATCH_CMD(line, "include ", x)) {
char *file = strip_line(x);
strlcpy(cmd, file, sizeof(cmd));
if (!fexist(cmd)) {
errx(1, "Cannot find include file %s, absolute path required!", x);
return 1;
}
return conf_parse_file(cmd, is_rcsd);
}
if (MATCH_CMD(line, "log ", x)) {
char *tok;
static int size = 200000, count = 5;
tok = strtok(x, ":= ");
while (tok) {
if (!strncmp(tok, "size", 4))
size = strtobytes(strtok(NULL, ":= "));
else if (!strncmp(tok, "count", 5))
count = strtobytes(strtok(NULL, ":= "));
tok = strtok(NULL, ":= ");
}
if (size >= 0)
logfile_size_max = size;
if (count >= 0)
logfile_count_max = count;
return 0;
}
if (MATCH_CMD(line, "shutdown ", x)) {
if (sdown) free(sdown);
sdown = strdup(strip_line(x));
return 0;
}
/*
* The desired runlevel to start when leaving bootstrap (S).
* Finit supports 1-9, but most systems only use 1-6, where
* 6 is reserved for reboot and 0 for halt/poweroff.
*/
if (BOOTSTRAP && MATCH_CMD(line, "runlevel ", x)) {
char *token = strip_line(x);
const char *err = NULL;
cfglevel = strtonum(token, 1, 9, &err);
if (err)
cfglevel = RUNLEVEL;
if (cfglevel < 1 || cfglevel > 9 || cfglevel == 6)
cfglevel = 2; /* Fallback */
return 0;
}
/*
* Before Finit 5 the default readiness notification is PID.
* This default can be changed by setting 'readiness none' in
* the file /etc/finit.conf, only read once at bootstrap.
*/
if (BOOTSTRAP && MATCH_CMD(line, "readiness ", x)) {
char *token = strip_line(x);
if (!strcmp(token, "none"))
readiness = SVC_NOTIFY_NONE;
}
if (MATCH_CMD(line, "reboot-delay ", x)) {
syncsec = strtonum(strip_line(x), 0, 60, NULL);
return 0;
}
if (MATCH_CMD(line, "reboot-watchdog ", x)) {
wdtreboot = istrue(strip_line(x), 0);
return 0;
}
/*
* Periodic check and instability index leveler, seconds
*/
if (MATCH_CMD(line, "service-interval ", x)) {
char *token = strip_line(x);
const char *err = NULL;
int val;
/* 0 min to 1 day, should check at least daily */
val = strtonum(token, 0, 1440, &err);
if (!err) {
int disabled = !service_interval;
service_interval = val * 1000; /* to milliseconds */
if (disabled)
service_init(NULL);
}
return 0;
}
return 1;
}
static int parse_dynamic(char *line, struct rlimit rlimit[], char *file)
{
char *x;
/* Monitored daemon, will be respawned on exit */
if (MATCH_CMD(line, "service ", x)) {
service_register(SVC_TYPE_SERVICE, x, rlimit, file);
return 0;
}
/* One-shot task, will not be respawned */
if (MATCH_CMD(line, "task ", x)) {
service_register(SVC_TYPE_TASK, x, rlimit, file);
return 0;
}
/* Like task but waits for completion, useful w/ [S] */
if (MATCH_CMD(line, "run ", x)) {
service_register(SVC_TYPE_RUN, x, rlimit, file);
return 0;
}
/* Similar to task but is treated like a SysV init script */
if (MATCH_CMD(line, "sysv ", x)) {
service_register(SVC_TYPE_SYSV, x, rlimit, file);
return 0;
}
/* Read resource limits */
if (MATCH_CMD(line, "rlimit ", x)) {
conf_parse_rlimit(x, rlimit);
return 0;
}
/* Read control group limits */
if (MATCH_CMD(line, "cgroup ", x)) {
conf_parse_cgroup(x);
return 0;
}
/* Set current cgroup for the following services/run/tasks */
if (MATCH_CMD(line, "cgroup.", x)) {
char *group, *token, *saveptr;
char settings[128] = {0};
/* Reset cgroup state */
cgroup_settings_current[0] = '\0';
cgroup_delegate_current = 0;
/* First token is the group name (system/user/init) */
group = strtok_r(x, ",", &saveptr);
if (group)
strlcpy(cgroup_current, group, sizeof(cgroup_current));
else
strlcpy(cgroup_current, x, sizeof(cgroup_current));
/* Parse any remaining comma-separated options */
while ((token = strtok_r(NULL, ",", &saveptr)) != NULL) {
if (strncmp(token, "name:", 5) == 0) {
/* Cgroup leaf name override - only valid in per-service directives */
warnx("cgroup %s: name: option not valid in global directive!", group);
} else if (strcmp(token, "delegate") == 0) {
cgroup_delegate_current = 1;
} else {
/* Other settings (cpu.weight:500, memory.max:1G, etc.) */
if (settings[0])
strlcat(settings, ",", sizeof(settings));
strlcat(settings, token, sizeof(settings));
}
}
if (settings[0])
strlcpy(cgroup_settings_current, settings, sizeof(cgroup_settings_current));
return 0;
}
/* Regular or serial TTYs to run getty */
if (MATCH_CMD(line, "tty ", x)) {
service_register(SVC_TYPE_TTY, strip_line(x), rlimit, file);
return 0;
}
return 1;
}
/*
* Parse FILE, or BUF when the frontend has already read and
* instantiated it, i.e. for a template. Either way the text arriving
* here is final, no %i is left to substitute.
*/
int legacy_parse_conf(char *file, char *buf, int is_rcsd)
{
struct rlimit rlimit[RLIMIT_NLIMITS];
FILE *fp;
if (buf)
fp = fmemopen(buf, strlen(buf), "r");
else
fp = fopen(file, "r");
if (!fp)
return 1;
/* Prepare default limits and group for each service in /etc/finit.d/ */
if (is_rcsd) {
memcpy(rlimit, global_rlimit, sizeof(rlimit));
cgroup_current[0] = 0;
}
dbg("*** Parsing %s", file);
while (!feof(fp)) {
char *line;
line = fparseln(fp, NULL, NULL, NULL, FPARSELN_UNESCCOMM);
if (!line)
continue;
tabstospaces(line);
if (!parse_static(line, is_rcsd))
;
else if (!parse_dynamic(line, is_rcsd ? rlimit : global_rlimit, file))
;
else
conf_set_env(line);
free(line);
}
fclose(fp);
return 0;
}
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+37
View File
@@ -0,0 +1,37 @@
/* Internal interface between conf.c and the legacy one-liner parser
*
* Copyright (c) 2012-2026 Joachim Wiberg <troglobit@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef FINIT_LEGACY_H_
#define FINIT_LEGACY_H_
int legacy_parse_conf (char *file, char *buf, int is_rcsd);
void legacy_parse_env (char *line);
#endif /* FINIT_LEGACY_H_ */
/**
* Local Variables:
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
+5 -2
View File
@@ -1,2 +1,5 @@
runlevel 1 runlevel = 1
tty [12345] rescue tty rescue {
runlevel = "12345"
rescue = true
}
+71 -45
View File
@@ -1,62 +1,88 @@
# sample.conf: template cgroup/run/task/service stanza for finit # sample.conf: template run/task/service stanza for finit
#
# A .conf file is a series of blocks. Every setting is a key inside
# one, so nothing has to be memorised by position:
#
# service NAME {
# description = "What it is"
# runlevel = "2345" # default: 234
# conditions = { "net/lo/up" } # wait for these
# envfile = "-/etc/default/daemon"
# command = "/usr/sbin/daemon ARGS"
# }
#
# A leading '-' on a path means carry on if it is missing, so the
# envfile above is optional. Debian and Buildroot keep those files in
# /etc/default, Alpine in /etc/conf.d.
#
# There are four kinds of stanza:
#
# - run : one-shot, wait for it before continuing with the next
# - task : one-shot, started in parallel with the next
# - service : supervised daemon, run in foreground, restarted if it crashes
# - sysv : /etc/init.d style script, start/stop/restart
# #
# The top-level cgroups can be defined anywhere, but share the same # The top-level cgroups can be defined anywhere, but share the same
# namespace. It is up to the user to ensure groups are defined only # namespace. It is up to the user to ensure groups are defined only
# once, otherwise the last read .conf wins. See below for assigning # once, otherwise the last read .conf wins:
# run/task/services to cgroups. The below example creates the cgroup
# NAME with the following cpu and memory settings:
# #
# cgroup NAME cpu.weight:1000 mem.max:65 # cgroup NAME {
# cpu.weight = 1000
# memory.max = 65M
# }
# #
# The one-shot commands 'run' and 'task' are called only once per # A service joins one by naming it, and may override settings for
# runlevel. Finit does not restart them when they exit. # itself alone. Here foo and bar share the group foo, baz has its own:
# #
# - run : wait for completion before continuing with next stanza # service foo { cgroup foo {} command = "foo args" }
# - task : like run but started in background, parallel with other stanzas # service bar { cgroup foo { cpu.weight = 150 } command = "bar args" }
# - service : supervised daemon, run in foreground, restarted if it crashes # service baz { cgroup baz { cpu.weight = 300 } command = "baz args" }
#
# The env: is an optional path to a file with environment variables to
# adjust the behavior of daemons. Debian and Buildroot use /etc/default
# while Alpine use /etc/conf.d -- ensure your Finit is built correctly.
# The leading '-' determines if its OK to start the service even if the
# env file is missing.
#
# run [LVLS] <COND> log command ARGS -- Command
# task [LVLS] <COND> log command ARGS -- Command
# service [LVLS] <COND> log env:[-]/etc/default/daemon daemon ARGS -- Daemon daemon
# ^ ^ ^ ^ ^ ^ ^ ^
# | | | | | | | `-- Optional description
# | | | | | | `----------- Daemon arguments
# | | | | | `-------------------------- Path to daemon
# | | | | `---------------------------------------------------- Optional env. file
# | | | `-------------------------------------------------------- Redirect output to log
# | | `--------------------------------------------------------------- Optional conditions
# | `---------------------------------------------------------------------- Optional Runlevels
# `------------------------------------------------------------------------------ Monitored application
#
# Each stanza can also hold a 'cgroup' argument, or be prefixed with
# cgroup.NAME to place all following stanzas in the same group. In
# the following example, foo and bar share the cgroup foo but baz
# runs in its own cgroup baz:
#
# cgroup.foo
# service cgroup:cpu.weight:250,mem.max:655350 foo args -- foo desc
# service cgroup:cpu.weight:150,mem.max:655350 bar args -- bar desc
# service cgroup.baz:cpu.weight:300 baz args -- baz desc
# Debian GNU/Linux: start SSH daemon as soon as basic networking comes up # Debian GNU/Linux: start SSH daemon as soon as basic networking comes up
#service [2345] <net/lo/up> env:-/etc/default/ssh /usr/sbin/sshd -D $SSHD_OPTS -- OpenSSH daemon #service sshd {
# description = "OpenSSH daemon"
# runlevel = "2345"
# conditions = { "net/lo/up" }
# envfile = "-/etc/default/ssh"
# command = "/usr/sbin/sshd -D $SSHD_OPTS"
#}
# Alpine Linux: Oneshot task to run once at bootstrap, yes pipes are possible :) # Alpine Linux: Oneshot task to run once at bootstrap, yes pipes are possible :)
#task [S] env:/etc/conf.d/loadkmap zcat $KEYMAP | loadkmap -- Loading keymap #run kmap {
# description = "Loading keymap"
# runlevel = "S"
# envfile = "/etc/conf.d/loadkmap"
# command = "zcat $KEYMAP | loadkmap"
#}
# Alpine Linux: start SSH daemon with $DROPBEAR_OPTS from /etc/conf.d # Alpine Linux: start SSH daemon with $DROPBEAR_OPTS from /etc/conf.d
#service [2345] cgroup.user env:-/etc/conf.d/dropbear dropbear -R -F $DROPBEAR_OPTS -- Dropbear SSH daemon #service dropbear {
# description = "Dropbear SSH daemon"
# runlevel = "2345"
# envfile = "-/etc/conf.d/dropbear"
# cgroup user {}
# command = "dropbear -R -F $DROPBEAR_OPTS"
#}
# Handle PWR button to shutdown/reboot -- useful in Qemu (virt-manager) # Handle PWR button to shutdown/reboot -- useful in Qemu (virt-manager)
# Depends on syslogd having started. Redirect any output to log. # Depends on syslogd having started. Redirect any output to the log.
#service [2345] <pid/syslogd> cgroup:mem.max:32000,cpu.max:1000 log acpid -f -- ACPI daemon #service acpid {
# description = "ACPI daemon"
# runlevel = "2345"
# conditions = { "pid/syslogd" }
# cgroup system {
# memory.max = 32M
# cpu.max = 1000
# }
# log { }
# command = "acpid -f"
#}
# Start rsyslogd as soon as possible, should always run # Start rsyslogd as soon as possible, should always run
# Provides pid/syslogd condition # Provides pid/syslogd condition
#service [S12345] name:syslogd env:-/etc/default/rsyslog rsyslogd -n $RSYSLOGD_OPTIONS -- Reliable syslog daemon #service syslogd {
# description = "Reliable syslog daemon"
# runlevel = "S12345"
# envfile = "-/etc/default/rsyslog"
# command = "rsyslogd -n $RSYSLOGD_OPTIONS"
#}
+188 -26
View File
@@ -66,6 +66,8 @@
#define NOTIFY_PATH "@run/finit/notify/%d" #define NOTIFY_PATH "@run/finit/notify/%d"
int logfile_size_max = 200000; /* 200 kB */
int logfile_count_max = 5;
/* /*
* run tasks block other tasks/services from starting, we track the * run tasks block other tasks/services from starting, we track the
@@ -568,6 +570,118 @@ static void set_uid(uid_t uid, svc_t *svc)
err(1, "%s: failed setuid(%d)", svc_ident(svc, NULL, 0), uid); err(1, "%s: failed setuid(%d)", svc_ident(svc, NULL, 0), uid);
} }
/*
* systemd-style per-service directories: created before each start,
* owned by the service user, and exported to the environment. The
* runtime directory is removed again when the service stops, after
* any post: script has run. See issue #492.
*/
const struct svcdir svcdirs[NUM_SVCDIRS] = {
{ "runtime-dir", "/run", "RUNTIME_DIRECTORY", offsetof(svc_t, runtime_dir), 1 },
{ "state-dir", "/var/lib", "STATE_DIRECTORY", offsetof(svc_t, state_dir), 1 },
{ "cache-dir", "/var/cache", "CACHE_DIRECTORY", offsetof(svc_t, cache_dir), 1 },
{ "logs-dir", "/var/log", "LOGS_DIRECTORY", offsetof(svc_t, logs_dir), 1 },
{ "config-dir", "/etc", "CONFIGURATION_DIRECTORY", offsetof(svc_t, config_dir), 0 },
};
static char *svcdir_path(svc_t *svc, const struct svcdir *sd, char *path, size_t len)
{
char *name = (char *)svc + sd->off;
if (!name[0])
return NULL;
paste(path, len, sd->base, name);
return path;
}
/*
* Validate and set a per-service directory. The value is a name
* resolved under sd->base, so an absolute path or an escape is
* refused with -1 and errno set.
*/
int service_set_dir(svc_t *svc, const struct svcdir *sd, const char *name)
{
char *dir = (char *)svc + sd->off;
if (name[0] == '/' || strstr(name, "..")) {
errno = EINVAL;
return -1;
}
if (strlcpy(dir, name, MAX_ARG_LEN) >= MAX_ARG_LEN) {
dir[0] = 0;
errno = ENAMETOOLONG;
return -1;
}
return 0;
}
static void service_mkdirs(svc_t *svc, uid_t uid, gid_t gid)
{
char path[256];
size_t i;
for (i = 0; i < NELEMS(svcdirs); i++) {
uid_t u = svcdirs[i].chown ? uid : (uid_t)-1;
gid_t g = svcdirs[i].chown ? gid : 0;
struct stat st;
char *ptr;
ptr = svcdir_path(svc, &svcdirs[i], path, sizeof(path));
if (!ptr)
continue;
/*
* Same rules as systemd: the named directory has its mode
* locked down again on every start, but its contents are
* only touched when the owner has drifted, then everything
* under it is chowned back. Script forks land here too,
* so this must be idempotent.
*/
if (stat(ptr, &st) == 0) {
chmod(ptr, svc->dir_mode[i]);
if (u != (uid_t)-1 && (st.st_uid != u || st.st_gid != g))
chownr(ptr, u, g);
continue;
}
/* only the named directory is chowned, like systemd */
if (mksubsysd(ptr, svc->dir_mode[i], u, g))
logit(LOG_WARNING, "%s: failed creating %s", svc_ident(svc, NULL, 0), ptr);
}
}
static void service_dir_env(svc_t *svc)
{
char path[256];
size_t i;
for (i = 0; i < NELEMS(svcdirs); i++) {
if (svcdir_path(svc, &svcdirs[i], path, sizeof(path)))
setenv(svcdirs[i].env, path, 1);
}
}
static void service_rmdirs(svc_t *svc)
{
char path[256];
/* only the runtime directory, /run is tmpfs, the rest persist */
if (!svcdir_path(svc, &svcdirs[0], path, sizeof(path)))
return;
if (svc->dir_preserve == SVC_DIR_PRESERVE_YES)
return;
/* still qualified to run means this is a restart, not a stop */
if (svc->dir_preserve == SVC_DIR_PRESERVE_RESTART && svc_enabled(svc))
return;
rmrf(path);
}
static pid_t service_fork(svc_t *svc) static pid_t service_fork(svc_t *svc)
{ {
const char *cgnm; const char *cgnm;
@@ -608,6 +722,7 @@ static pid_t service_fork(svc_t *svc)
return -1; return -1;
} }
#endif #endif
service_mkdirs(svc, uid, gid);
if (svc_is_tty(svc)) if (svc_is_tty(svc))
setprocnm("getty"); setprocnm("getty");
@@ -662,6 +777,8 @@ static pid_t service_fork(svc_t *svc)
err(1, "%s: failed setgid(%d)", svc_ident(svc, NULL, 0), gid); err(1, "%s: failed setgid(%d)", svc_ident(svc, NULL, 0), gid);
} }
service_dir_env(svc);
if (uid >= 0) { if (uid >= 0) {
set_uid(uid, svc); set_uid(uid, svc);
@@ -1074,7 +1191,7 @@ static void service_kill(svc_t *svc)
* Called by service_stop() and service_reload() when alternate mechanisms * Called by service_stop() and service_reload() when alternate mechanisms
* for stopping and reloading have been specified by the user. * for stopping and reloading have been specified by the user.
*/ */
static int service_run_script(svc_t *svc, char *script) static int service_run_script(svc_t *svc, char *script, int tmo)
{ {
const char *id = svc_ident(svc, NULL, 0); const char *id = svc_ident(svc, NULL, 0);
pid_t pid = service_fork(svc); pid_t pid = service_fork(svc);
@@ -1104,7 +1221,7 @@ static int service_run_script(svc_t *svc, char *script)
} }
dbg("%s: script '%s' started as PID %d", id, script, pid); dbg("%s: script '%s' started as PID %d", id, script, pid);
return service_script_add(svc, pid, svc->killdelay); return service_script_add(svc, pid, tmo ? tmo : svc->killdelay);
} }
/* Ensure we don't have any notify socket lingering */ /* Ensure we don't have any notify socket lingering */
@@ -1266,7 +1383,7 @@ int service_stop(svc_t *svc)
print_desc("Stopping ", svc->desc); print_desc("Stopping ", svc->desc);
if (svc->stop_script[0]) { if (svc->stop_script[0]) {
rc = service_run_script(svc, svc->stop_script); rc = service_run_script(svc, svc->stop_script, svc->stop_tmo);
} else if (!svc_is_sysv(svc)) { } else if (!svc_is_sysv(svc)) {
if (svc->pid > 1) { if (svc->pid > 1) {
/* /*
@@ -1358,7 +1475,7 @@ static int service_reload(svc_t *svc)
if (svc->reload_script[0]) { if (svc->reload_script[0]) {
logit(LOG_CONSOLE | LOG_NOTICE, "Reloading %s[%d], calling reload:%s ...", id, svc->pid, svc->reload_script); logit(LOG_CONSOLE | LOG_NOTICE, "Reloading %s[%d], calling reload:%s ...", id, svc->pid, svc->reload_script);
rc = service_run_script(svc, svc->reload_script); rc = service_run_script(svc, svc->reload_script, svc->reload_tmo);
} else if (svc->sighup) { } else if (svc->sighup) {
if (svc->pid <= 1) { if (svc->pid <= 1) {
dbg("%s[%d]: bad PID, cannot reload service", id, svc->pid); dbg("%s[%d]: bad PID, cannot reload service", id, svc->pid);
@@ -1535,11 +1652,26 @@ static void parse_caps(svc_t *svc, char *caps)
return; return;
} }
if (!strcmp(svc->username, "root")) {
cap_value_t cap;
for (cap = 0; cap <= CAP_LAST_CAP; cap++) {
if (!cap_iab_get_vector(cap_iab, CAP_IAB_AMB, cap))
continue;
/* the ambient set only reaches effective when euid != 0 */
logit(LOG_WARNING, "%s: ambient capabilities ('^') have no effect"
" as root, use a non-root user, or '%%' and '!' entries",
svc_ident(svc, NULL, 0));
break;
}
}
cap_free(cap_iab); cap_free(cap_iab);
strlcpy(svc->capabilities, caps, sizeof(svc->capabilities)); strlcpy(svc->capabilities, caps, sizeof(svc->capabilities));
#else #else
(void)svc; logit(LOG_WARNING, "%s: capabilities require Finit built with --enable-libcap,"
(void)caps; " ignoring '%s'", svc_ident(svc, NULL, 0), caps);
#endif #endif
} }
@@ -1654,7 +1786,8 @@ static void parse_script(svc_t *svc, char *type, char *script, int *tmo, char *b
script, errstr); script, errstr);
goto err; goto err;
} }
*tmo = (int)(sec * 1000); if (tmo)
*tmo = (int)(sec * 1000);
} else { } else {
path = script; path = script;
if (tmo) if (tmo)
@@ -1857,9 +1990,11 @@ static void parse_cmdline_args(svc_t *svc, char *cmd, char **args)
* defaults to "" (empty string). * defaults to "" (empty string).
* *
* Returns: * Returns:
* POSIX OK(0) on success, or non-zero errno exit status on failure. * The registered svc, or %NULL with @errno set on failure. A block
* skipped on purpose -- conditional loading, bootstrap over, nowarn --
* also returns %NULL, with @errno zero.
*/ */
int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
{ {
char *cmd, *desc, *runlevels = NULL, *cond = NULL; char *cmd, *desc, *runlevels = NULL, *cond = NULL;
char *username = NULL, *log = NULL, *pid = NULL; char *username = NULL, *log = NULL, *pid = NULL;
@@ -1887,12 +2022,15 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
if (!cfg) { if (!cfg) {
errx(1, "Invalid input argument"); errx(1, "Invalid input argument");
return errno = EINVAL; errno = EINVAL;
return NULL;
} }
line = strdupa(cfg); line = strdupa(cfg);
if (!line) if (!line) {
return 1; errno = ENOMEM;
return NULL;
}
desc = strstr(line, "-- "); desc = strstr(line, "-- ");
if (desc) { if (desc) {
@@ -1917,7 +2055,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
if (!cmd) { if (!cmd) {
incomplete: incomplete:
errx(1, "Incomplete service '%s', cannot register", cfg); errx(1, "Incomplete service '%s', cannot register", cfg);
return errno = ENOENT; errno = ENOENT;
return NULL;
} }
while (cmd) { while (cmd) {
@@ -2017,14 +2156,17 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
strlcat(ident, id, sizeof(ident)); strlcat(ident, id, sizeof(ident));
} }
if (ifstmt && !svc_ifthen(1, ident, ifstmt, nowarn)) if (ifstmt && !svc_ifthen(1, ident, ifstmt, nowarn)) {
return 0; errno = 0;
return NULL;
}
levels = conf_parse_runlevels(runlevels); levels = conf_parse_runlevels(runlevels);
if (runlevel != INIT_LEVEL && !ISOTHER(levels, INIT_LEVEL)) { if (runlevel != INIT_LEVEL && !ISOTHER(levels, INIT_LEVEL)) {
dbg("Skipping %s%s%s, bootstrap is completed.", dbg("Skipping %s%s%s, bootstrap is completed.",
name, id[0] ? ":" : "", id[0] ? id : ""); name, id[0] ? ":" : "", id[0] ? id : "");
return 0; errno = 0;
return NULL;
} }
if (type == SVC_TYPE_TTY) { if (type == SVC_TYPE_TTY) {
@@ -2032,7 +2174,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
char *ptr; char *ptr;
if (tty_parse_args(&tty, cmd, &args)) if (tty_parse_args(&tty, cmd, &args))
return errno; return NULL;
/* NOTE: this may result in dev == NULL! */ /* NOTE: this may result in dev == NULL! */
if (tty_isatcon(tty.dev)) if (tty_isatcon(tty.dev))
@@ -2051,7 +2193,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
line = alloca(len); line = alloca(len);
if (!line) if (!line)
return errno; return NULL;
snprintf(line, len, "%s", tty.cmd ? tty.cmd : "tty"); snprintf(line, len, "%s", tty.cmd ? tty.cmd : "tty");
for (i = 0; i < tty.num; i++) { for (i = 0; i < tty.num; i++) {
@@ -2061,7 +2203,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
cmd = strtok_r(line, " \t", &args); cmd = strtok_r(line, " \t", &args);
if (!cmd) if (!cmd)
return errno; return NULL;
/* tty's always respawn, never incr. restart_cnt */ /* tty's always respawn, never incr. restart_cnt */
respawn = 1; respawn = 1;
@@ -2086,11 +2228,13 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
svc = svc_find(name, id); svc = svc_find(name, id);
if (!whichp(cmd)) { if (!whichp(cmd)) {
if (nowarn) if (nowarn) {
return 0; errno = 0;
return NULL;
}
warn("%s: skipping %s", file ? file : "static", cmd); warn("%s: skipping %s", file ? file : "static", cmd);
return errno; return NULL;
} }
if (!svc) { if (!svc) {
@@ -2098,7 +2242,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
svc = svc_new(cmd, name, id, type); svc = svc_new(cmd, name, id, type);
if (!svc) { if (!svc) {
errx(1, "Out of memory, cannot register service %s", cmd); errx(1, "Out of memory, cannot register service %s", cmd);
return errno = ENOMEM; errno = ENOMEM;
return NULL;
} }
if (manual) if (manual)
@@ -2208,12 +2353,12 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
memset(svc->cleanup_script, 0, sizeof(svc->cleanup_script)); memset(svc->cleanup_script, 0, sizeof(svc->cleanup_script));
if (reload_script) if (reload_script)
parse_script(svc, "reload", reload_script, NULL, svc->reload_script, sizeof(svc->reload_script)); parse_script(svc, "reload", reload_script, &svc->reload_tmo, svc->reload_script, sizeof(svc->reload_script));
else else
memset(svc->reload_script, 0, sizeof(svc->reload_script)); memset(svc->reload_script, 0, sizeof(svc->reload_script));
if (stop_script) if (stop_script)
parse_script(svc, "stop", stop_script, NULL, svc->stop_script, sizeof(svc->stop_script)); parse_script(svc, "stop", stop_script, &svc->stop_tmo, svc->stop_script, sizeof(svc->stop_script));
else else
memset(svc->stop_script, 0, sizeof(svc->stop_script)); memset(svc->stop_script, 0, sizeof(svc->stop_script));
@@ -2242,6 +2387,13 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
else else
memset(svc->capabilities, 0, sizeof(svc->capabilities)); memset(svc->capabilities, 0, sizeof(svc->capabilities));
/* block format only, set by conf.c after registration */
for (int i = 0; i < NUM_SVCDIRS; i++) {
memset((char *)svc + svcdirs[i].off, 0, MAX_ARG_LEN);
svc->dir_mode[i] = 0755;
}
svc->dir_preserve = SVC_DIR_PRESERVE_NO;
if (!svc_is_tty(svc) && ctty) { if (!svc_is_tty(svc) && ctty) {
char *dev = ctty; char *dev = ctty;
@@ -2357,7 +2509,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
} }
} }
return 0; return svc;
} }
/* /*
@@ -2864,6 +3016,16 @@ static void svc_set_state(svc_t *svc, svc_state_t new_state)
return; return;
*state = new_state; *state = new_state;
/*
* The unit has stopped: HALTED comes after any post:/cleanup:
* script, DONE is a completed run/task, where remain-after-exit
* keeps it alive until stopped for real. Same removal rules as
* systemd RuntimeDirectory with RuntimeDirectoryPreserve=no.
*/
if (new_state == SVC_HALTED_STATE ||
(new_state == SVC_DONE_STATE && !svc_is_remain(svc)))
service_rmdirs(svc);
if (svc_is_runtask(svc)) { if (svc_is_runtask(svc)) {
char success[MAX_COND_LEN], failure[MAX_COND_LEN]; char success[MAX_COND_LEN], failure[MAX_COND_LEN];
+17 -1
View File
@@ -27,7 +27,23 @@
#include "svc.h" #include "svc.h"
int service_register (int type, char *line, struct rlimit rlimit[], char *file); /*
* systemd-style per-service directories, block format only: the .conf
* key, the base the name resolves under, and the environment variable
* the path is exported as. Shared by service.c and conf.c so adding
* one means touching the table and svc.h alone.
*/
struct svcdir {
const char *key;
const char *base;
const char *env;
size_t off; /* offsetof() in svc_t */
int chown; /* all but config-dir, like systemd */
};
extern const struct svcdir svcdirs[NUM_SVCDIRS];
int service_set_dir (svc_t *svc, const struct svcdir *sd, const char *name);
svc_t *service_register (int type, char *line, struct rlimit rlimit[], char *file);
void service_unregister (svc_t *svc); void service_unregister (svc_t *svc);
void service_runtask_clean (void); void service_runtask_clean (void);
+16
View File
@@ -199,6 +199,20 @@ typedef struct svc {
int num_supgroups; int num_supgroups;
char capabilities[MAX_CMD_LEN]; char capabilities[MAX_CMD_LEN];
/* Directories set up for the service, block format only, the
* name is resolved under a fixed base, e.g. /run/NAME */
#define NUM_SVCDIRS 5
char runtime_dir[MAX_ARG_LEN];
char state_dir[MAX_ARG_LEN];
char cache_dir[MAX_ARG_LEN];
char logs_dir[MAX_ARG_LEN];
char config_dir[MAX_ARG_LEN];
mode_t dir_mode[NUM_SVCDIRS];
#define SVC_DIR_PRESERVE_NO 0 /* remove runtime-dir on stop */
#define SVC_DIR_PRESERVE_RESTART 1 /* keep it across restarts */
#define SVC_DIR_PRESERVE_YES 2 /* never remove it */
char dir_preserve;
/* Command, arguments and service description */ /* Command, arguments and service description */
char cmd[MAX_CMD_LEN]; char cmd[MAX_CMD_LEN];
char args[MAX_NUM_SVC_ARGS][MAX_CMD_LEN]; char args[MAX_NUM_SVC_ARGS][MAX_CMD_LEN];
@@ -221,9 +235,11 @@ typedef struct svc {
/* When set, used instead of SIGHUP or stop-start */ /* When set, used instead of SIGHUP or stop-start */
char reload_script[MAX_CMD_LEN]; char reload_script[MAX_CMD_LEN];
int reload_tmo;
/* When set, used instead of SIGTERM or sysv 'stop' */ /* When set, used instead of SIGTERM or sysv 'stop' */
char stop_script[MAX_CMD_LEN]; char stop_script[MAX_CMD_LEN];
int stop_tmo;
/* /*
* Used to forcefully kill services that won't shutdown on * Used to forcefully kill services that won't shutdown on
+1 -30
View File
@@ -130,33 +130,6 @@ static int parse_mm(char *arg, int *major, int *minor)
return 0; return 0;
} }
static int do_delete(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftw)
{
(void)sb;
(void)tflag;
if (ftw->level == 0)
return 1;
if (remove(fpath) && errno != EBUSY)
warn("Failed removing %s", fpath);
return 0;
}
static int rmrf(const char *path)
{
if (!fisdir(path))
return 0;
nftw(path, do_delete, 20, FTW_DEPTH | FTW_PHYS);
if (remove(path) && errno != ENOENT)
warn("Failed removing path %s", path);
return 0;
}
static void mkparent(char *path, mode_t mode) static void mkparent(char *path, mode_t mode)
{ {
mkpath(dirname(strdupa(path)), mode); mkpath(dirname(strdupa(path)), mode);
@@ -465,9 +438,7 @@ static void tmpfiles(char *line)
case 'd': case 'd':
break; break;
case 'D': case 'D':
if (fisdir(path)) { rmcontents(path);
nftw(path, do_delete, 20, FTW_DEPTH | FTW_PHYS);
}
break; break;
case 'e': case 'e':
case 'f': case 'f':
+256 -32
View File
@@ -33,6 +33,7 @@
#include <regex.h> #include <regex.h>
#include <string.h> #include <string.h>
#include <stdarg.h> #include <stdarg.h>
#include <sys/stat.h>
#include <stdlib.h> #include <stdlib.h>
#ifdef HAVE_TERMIOS_H #ifdef HAVE_TERMIOS_H
# include <termios.h> # include <termios.h>
@@ -43,8 +44,10 @@
#ifdef HAVE_SYS_IOCTL_H #ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h> # include <sys/ioctl.h>
#endif #endif
#include <sys/resource.h>
#include <ftw.h> /* rmrf() */
#include <sys/sysinfo.h> /* sysinfo() */ #include <sys/sysinfo.h> /* sysinfo() */
#include <sys/vfs.h> /* statfs */ #include <sys/vfs.h> /* statfs */
#include <linux/magic.h> #include <linux/magic.h>
#ifdef _LIBITE_LITE #ifdef _LIBITE_LITE
# include <libite/lite.h> # include <libite/lite.h>
@@ -108,6 +111,34 @@ static char *signames[] = {
"SYS", "SYS",
}; };
struct rlimit_name {
char *name;
int val;
};
static const struct rlimit_name rlimit_names[] = {
{ "as", RLIMIT_AS },
{ "core", RLIMIT_CORE },
{ "cpu", RLIMIT_CPU },
{ "data", RLIMIT_DATA },
{ "fsize", RLIMIT_FSIZE },
{ "locks", RLIMIT_LOCKS },
{ "memlock", RLIMIT_MEMLOCK },
{ "msgqueue", RLIMIT_MSGQUEUE },
{ "nice", RLIMIT_NICE },
{ "nofile", RLIMIT_NOFILE },
{ "nproc", RLIMIT_NPROC },
{ "rss", RLIMIT_RSS },
{ "rtprio", RLIMIT_RTPRIO },
#ifdef RLIMIT_RTTIME
{ "rttime", RLIMIT_RTTIME },
#endif
{ "sigpending", RLIMIT_SIGPENDING },
{ "stack", RLIMIT_STACK },
{ NULL, 0 }
};
/* https://freedesktop.org/software/systemd/man/systemd.exec.html#id-1.20.8 */ /* https://freedesktop.org/software/systemd/man/systemd.exec.html#id-1.20.8 */
static char *exitcodes[] = { static char *exitcodes[] = {
"SUCCESS", /* 0: Std C exit OK */ "SUCCESS", /* 0: Std C exit OK */
@@ -280,25 +311,85 @@ int getcgroup(char *buf, size_t len)
return 0; return 0;
} }
int mksubsys(const char *dir, mode_t mode, char *user, char *group) static int rmrf_cb(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftw)
{
(void)sb;
(void)tflag;
if (ftw->level == 0)
return 0;
if (remove(fpath) && errno != EBUSY)
warn("Failed removing %s", fpath);
return 0;
}
/* nftw() cannot pass user data, see also tmpfiles.c do_clean() */
static uid_t chownr_uid;
static gid_t chownr_gid;
static int chownr_cb(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftw)
{
(void)tflag;
(void)ftw;
if (sb->st_uid == chownr_uid && sb->st_gid == chownr_gid)
return 0;
if (lchown(fpath, chownr_uid, chownr_gid))
warn("Failed chown(%s, %d, %d)", fpath, (int)chownr_uid, (int)chownr_gid);
return 0;
}
/* chown -R */
int chownr(const char *path, uid_t uid, gid_t gid)
{
chownr_uid = uid;
chownr_gid = gid;
return nftw(path, chownr_cb, 20, FTW_PHYS);
}
/* empty a directory but keep it, silently ignores a missing path */
int rmcontents(const char *path)
{
if (!fisdir(path))
return 0;
return nftw(path, rmrf_cb, 20, FTW_DEPTH | FTW_PHYS);
}
/* rm -rf, silently ignores a missing path */
int rmrf(const char *path)
{
if (!fisdir(path))
return 0;
nftw(path, rmrf_cb, 20, FTW_DEPTH | FTW_PHYS);
if (remove(path) && errno != ENOENT)
warn("Failed removing path %s", path);
return 0;
}
/*
* Like mksubsys() but with the ids already resolved, uid -1 skips the
* chown. Parents are created 0755, only the leaf gets @mode.
*/
int mksubsysd(const char *dir, mode_t mode, uid_t uid, gid_t gid)
{ {
mode_t omask; mode_t omask;
int uid, gid; int rc;
int rc = 0;
omask = umask(0); omask = umask(0);
uid = getuser(user, NULL); rc = mkpath(dir, 0755);
if (uid >= 0) { if (!rc) {
gid = getgroup(group); rc = chmod(dir, mode);
if (gid < 0) if (!rc && uid != (uid_t)-1 && chown(dir, uid, gid))
gid = 0; err(1, "Failed chown(%s, %d, %d)", dir, (int)uid, (int)gid);
rc = makedir(dir, mode);
if (rc && errno == EEXIST)
rc = chmod(dir, mode);
if (chown(dir, uid, gid))
err(1, "Failed chown(%s, %d, %d)", dir, uid, gid);
} }
umask(omask); umask(omask);
@@ -306,34 +397,123 @@ int mksubsys(const char *dir, mode_t mode, char *user, char *group)
return rc; return rc;
} }
int fnread(char *buf, size_t len, char *fmt, ...) int mksubsys(const char *dir, mode_t mode, char *user, char *group)
{ {
char path[256]; int uid, gid;
va_list ap;
FILE *fp;
va_start(ap, fmt); uid = getuser(user, NULL);
vsnprintf(path, sizeof(path), fmt, ap); gid = getgroup(group);
va_end(ap); if (gid < 0)
gid = 0;
if (uid < 0)
warnx("Cannot find user %s, %s is owned by root", user, dir);
if (!buf || !len) { return mksubsysd(dir, mode, uid < 0 ? (uid_t)-1 : (uid_t)uid, (gid_t)gid);
struct stat st; }
if (stat(path, &st)) /*
return -1; * Read an open stream to EOF into a malloc()'ed, NUL terminated buffer.
*
* st_size is only a hint here, and zero on procfs, so the read loop
* runs until EOF rather than trusting it. Sizing from the already
* open fd also leaves no window for the file to change between the
* look and the read.
*/
static char *slurp(FILE *fp, size_t *lenp)
{
size_t len = 0, size = BUFSIZ;
struct stat st;
char *buf;
return (ssize_t)st.st_size; if (!fstat(fileno(fp), &st) && st.st_size > 0 && (size_t)st.st_size > size)
size = (size_t)st.st_size;
buf = malloc(size + 1);
if (!buf)
return NULL;
while (1) {
char *ptr;
len += fread(&buf[len], 1, size - len, fp);
if (len < size)
break; /* EOF, or error caught below */
ptr = realloc(buf, size * 2 + 1);
if (!ptr) {
free(buf);
return NULL;
}
buf = ptr;
size *= 2;
} }
fp = fopen(path, "r"); if (ferror(fp)) {
if (!fp) free(buf);
return -1; return NULL;
}
len = fread(buf, sizeof(char), len - 1, fp);
buf[len] = 0; buf[len] = 0;
if (lenp)
*lenp = len;
return buf;
}
/*
* Read a whole file into a malloc()'ed, NUL terminated buffer, which
* the caller frees. @lenp, when given, returns the number of bytes
* read; the content may itself contain NUL, e.g. /proc/PID/cmdline.
*/
char *vfslurp(size_t *lenp, const char *fmt, va_list ap)
{
char *buf;
FILE *fp;
fp = vfopenf("r", fmt, ap);
if (!fp)
return NULL;
buf = slurp(fp, lenp);
fclose(fp); fclose(fp);
return (int)len; return buf;
}
char *fslurp(size_t *lenp, const char *fmt, ...)
{
va_list ap;
char *buf;
va_start(ap, fmt);
buf = vfslurp(lenp, fmt, ap);
va_end(ap);
return buf;
}
int fnread(char *buf, size_t len, char *fmt, ...)
{
size_t dlen;
va_list ap;
char *data;
va_start(ap, fmt);
data = vfslurp(&dlen, fmt, ap);
va_end(ap);
if (!data)
return -1;
if (buf && len) {
if (dlen > len - 1)
dlen = len - 1;
memcpy(buf, data, dlen);
buf[dlen] = 0;
}
free(data);
return (int)dlen;
} }
int fnwrite(char *value, char *fmt, ...) int fnwrite(char *value, char *fmt, ...)
@@ -483,6 +663,50 @@ char *code2str(int code)
return exitcodes[code]; return exitcodes[code];
} }
int str2rlim(char *str)
{
const struct rlimit_name *rn;
for (rn = rlimit_names; rn->name; rn++) {
if (!strcmp(str, rn->name))
return rn->val;
}
return -1;
}
char *rlim2str(int rlim)
{
const struct rlimit_name *rn;
for (rn = rlimit_names; rn->name; rn++) {
if (rn->val == rlim)
return rn->name;
}
return "unknown";
}
char *lim2str(struct rlimit *rlim)
{
char tmp[25];
static char buf[42];
buf[0] = 0;
if (RLIM_INFINITY == rlim->rlim_cur)
snprintf(tmp, sizeof(tmp), "unlimited, ");
else
snprintf(tmp, sizeof(tmp), "%llu, ", (unsigned long long)rlim->rlim_cur);
strlcat(buf, tmp, sizeof(buf));
if (RLIM_INFINITY == rlim->rlim_max)
snprintf(tmp, sizeof(tmp), "unlimited, ");
else
snprintf(tmp, sizeof(tmp), "%llu, ", (unsigned long long)rlim->rlim_max);
strlcat(buf, tmp, sizeof(buf));
return buf;
}
void do_sleep(unsigned int sec) void do_sleep(unsigned int sec)
{ {
+13
View File
@@ -29,8 +29,10 @@
#include <ctype.h> #include <ctype.h>
#ifdef HAVE_TERMIOS_H #ifdef HAVE_TERMIOS_H
#include <poll.h> #include <poll.h>
#include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <sys/reboot.h> #include <sys/reboot.h>
#include <sys/resource.h>
#include <termios.h> #include <termios.h>
#endif #endif
@@ -55,6 +57,7 @@ extern char *prognm;
# include <lite/conio.h> # include <lite/conio.h>
# include <lite/lite.h> # include <lite/lite.h>
#endif #endif
#include "log.h" #include "log.h"
char *progname (char *arg0); char *progname (char *arg0);
@@ -69,7 +72,13 @@ int getcuser (char *buf, size_t len);
int getcgroup (char *buf, size_t len); int getcgroup (char *buf, size_t len);
int mksubsys (const char *dir, mode_t mode, char *user, char *group); int mksubsys (const char *dir, mode_t mode, char *user, char *group);
int mksubsysd (const char *dir, mode_t mode, uid_t uid, gid_t gid);
int chownr (const char *path, uid_t uid, gid_t gid);
int rmcontents (const char *path);
int rmrf (const char *path);
char *fslurp (size_t *lenp, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
char *vfslurp (size_t *lenp, const char *fmt, va_list ap);
int fnread (char *buf, size_t len, char *fmt, ...) __attribute__ ((format (printf, 3, 4))); int fnread (char *buf, size_t len, char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
int fnwrite (char *value, char *fmt, ...) __attribute__ ((format (printf, 2, 3))); int fnwrite (char *value, char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
int fngetint (char *path, int *val); int fngetint (char *path, int *val);
@@ -81,6 +90,10 @@ char *sig2str (int sig);
int str2sig (char *sig); int str2sig (char *sig);
char *code2str (int code); char *code2str (int code);
int str2rlim (char *str);
char *rlim2str (int rlim);
char *lim2str (struct rlimit *rlim);
void do_sleep (unsigned int sec); void do_sleep (unsigned int sec);
void do_usleep (unsigned int usec); void do_usleep (unsigned int usec);
long jiffies (void); long jiffies (void);
+124 -24
View File
@@ -5,51 +5,151 @@
# often needs to be synchronized carefully, we suggest using your system log # often needs to be synchronized carefully, we suggest using your system log
# daemon as the barrier for all other services. E.g.: # daemon as the barrier for all other services. E.g.:
# #
# service if:udevd <run/udevadm:5/success> [S0123456789] syslogd -F -- System log daemon # service syslogd {
# service if:mdev <run/coldplug/success> [S0123456789] syslogd -F -- System log daemon # description = "System log daemon"
# service if:mdevd <run/coldplug/success> [S0123456789] syslogd -F -- System log daemon # runlevel = "S0123456789"
# if = "udevd"
# conditions = { "run/udevadm:5/success" }
# command = "syslogd -F"
# }
#
# with `if = "mdev"` or `if = "mdevd"` and the matching coldplug condition
# for those variants.
# #
# This provdes a <pid/syslogd> condition that can act as a barrier for all # This provdes a <pid/syslogd> condition that can act as a barrier for all
# other services. Notice the `if:` statement and the condition for starting # other services. Notice the `if` setting: the condition for starting
# syslogd is only considered if either udevd or mdev (service) is loaded and # syslogd is only considered if either udevd or mdev (service) is loaded and
# is guaranteed to run after each respective run stanza have completed. # is guaranteed to run after each respective run stanza have completed.
# #
# Override this file by copying it to /etc/finit.d/, using the same name, then # Override this file by copying it to /etc/finit.d/, using the same name, then
# change the contents any way you like, it can even be empty. # change the contents any way you like, it can even be empty.
#
# The leading '-' on every command says a missing binary is expected here, so
# the block is skipped quietly and the next candidate gets its turn.
# Check for systemd-udevd and eudev, if we find both, we opt for the latter. # Check for systemd-udevd and eudev, if we find both, we opt for the latter.
service nowarn cgroup.system,name:udevd name:udevd notify:none env:-/etc/default/udevd pid:udevd log \ service udevd {
[S12345789] /lib/systemd/systemd-udevd $UDEVD_ARGS -- Device event daemon (udev) description = "Device event daemon (udev)"
service nowarn cgroup.system,name:udevd name:udevd notify:none env:-/etc/default/udevd pid:udevd log \ runlevel = "S12345789"
[S12345789] udevd $UDEVD_ARGS -- Device event daemon (udev) notify = "none"
envfile = "-/etc/default/udevd"
pidfile = "udevd"
pidfile-create = true
log { }
cgroup system { name = "udevd" }
command = "-/lib/systemd/systemd-udevd $UDEVD_ARGS"
}
service udevd {
description = "Device event daemon (udev)"
runlevel = "S12345789"
notify = "none"
envfile = "-/etc/default/udevd"
pidfile = "udevd"
pidfile-create = true
log { }
cgroup system { name = "udevd" }
command = "-udevd $UDEVD_ARGS"
}
# Wait for udevd to start, then trigger coldplug events and module loading. # Wait for udevd to start, then trigger coldplug events and module loading.
# The last 'settle' call waits for it to finalize processing all uevents. # The last 'settle' call waits for it to finalize processing all uevents.
run nowarn if:udevd cgroup.init,name:udevadm :1 [S] <service/udevd/ready> log udevadm settle -t 0 -- run udevadm:1 {
run nowarn if:udevd cgroup.init,name:udevadm :2 [S] <service/udevd/ready> log udevadm control --reload -- runlevel = "S"
run nowarn if:udevd cgroup.init,name:udevadm :3 [S] <service/udevd/ready> log udevadm trigger -c add -t devices -- Requesting device events if = "udevd"
run nowarn if:udevd cgroup.init,name:udevadm :4 [S] <service/udevd/ready> log udevadm trigger -c add -t subsystems -- Requesting subsystem events conditions = { "service/udevd/ready" }
run nowarn if:udevd cgroup.init,name:udevadm :5 [S] <service/udevd/ready> log udevadm settle -t 30 -- Waiting for udev to finish log { }
cgroup init { name = "udevadm" }
command = "-udevadm settle -t 0"
}
run udevadm:2 {
runlevel = "S"
if = "udevd"
conditions = { "service/udevd/ready" }
log { }
cgroup init { name = "udevadm" }
command = "-udevadm control --reload"
}
run udevadm:3 {
description = "Requesting device events"
runlevel = "S"
if = "udevd"
conditions = { "service/udevd/ready" }
log { }
cgroup init { name = "udevadm" }
command = "-udevadm trigger -c add -t devices"
}
run udevadm:4 {
description = "Requesting subsystem events"
runlevel = "S"
if = "udevd"
conditions = { "service/udevd/ready" }
log { }
cgroup init { name = "udevadm" }
command = "-udevadm trigger -c add -t subsystems"
}
run udevadm:5 {
description = "Waiting for udev to finish"
runlevel = "S"
if = "udevd"
conditions = { "service/udevd/ready" }
log { }
cgroup init { name = "udevadm" }
command = "-udevadm settle -t 30"
}
# If udevd is not available, check for mdevd. Unlike plain 'mdev -df', the # If udevd is not available, check for mdevd. Unlike plain 'mdev -df', the
# mdevd-coldplug process exits when mdevd has completed. So the condition # mdevd-coldplug process exits when mdevd has completed. So the condition
# <run/coldplug/success> (same as for plain mdev below) can be relied on as a # <run/mdevd-coldplug/success> can be relied on as a barrier for services
# barrier for services that, e.g., need to probe for switchcore ports or PHYs # that, e.g., need to probe for switchcore ports or PHYs that require
# that require firmware loading before they show up. # firmware loading before they show up.
service nowarn conflict:udevd,mdev cgroup.init,name:mdevd notify:none if:!udevd \ service mdevd {
[S12345789] notify:s6 mdevd -O 4 -D %n -- Device event daemon (mdevd) description = "Device event daemon (mdevd)"
run nowarn conflict:udevd,mdev cgroup.init,name:coldplug <service/mdevd/ready> \ runlevel = "S12345789"
[S] mdevd-coldplug -- Cold plugging system if = "!udevd"
conflicts = { "udevd", "mdev" }
notify = "s6"
cgroup init { name = "mdevd" }
command = "-mdevd -O 4 -D %n"
}
# NOTE: this block is named for its command, so its barrier condition is
# <run/mdevd-coldplug/success>, not the <run/coldplug/success> the
# plain mdev stanza below provides. The legacy line spelled the
# name inside the cgroup argument, where it names the cgroup leaf
# rather than the service, and the two paths have differed ever
# since. Left as it was so boot ordering does not change.
run mdevd-coldplug {
description = "Cold plugging system"
runlevel = "S"
conflicts = { "udevd", "mdev" }
conditions = { "service/mdevd/ready" }
cgroup init { name = "coldplug" }
command = "-mdevd-coldplug"
}
# If neither udevd or mdevd are available, fall back to mdev. The new daemon # If neither udevd or mdevd are available, fall back to mdev. The new daemon
# mode calls `mdev -s` internally but does not create a PID file when that is # mode calls `mdev -s` internally but does not create a PID file when that is
# done, so synchronization with coldplug is fragile. # done, so synchronization with coldplug is fragile.
service nowarn conflict:udevd,mdevd cgroup.init,name:mdev notify:none if:!udevd,!mdevd \ service mdev {
[S12345789] mdev -df -S -- Device event daemon (mdev) description = "Device event daemon (mdev)"
runlevel = "S12345789"
if = "!udevd,!mdevd"
conflicts = { "udevd", "mdevd" }
notify = "none"
cgroup init { name = "mdev" }
command = "-mdev -df -S"
}
# Trigger a replay of all uevents from boot, before userspace was up and the # Trigger a replay of all uevents from boot, before userspace was up and the
# filesystem mounted. Compared to udevd, we don't know when 'mdev -df' has # filesystem mounted. Compared to udevd, we don't know when 'mdev -df' has
# finished processing all events, i.e., depending on <run/coldplug/success> is # finished processing all events, i.e., depending on <run/coldplug/success> is
# not an indicator of all devices having been (re)discovered and fully set up. # not an indicator of all devices having been (re)discovered and fully set up.
run nowarn conflict:udevd,mdevd cgroup.init name:coldplug if:mdev <service/mdev/running> \ run coldplug {
[S] @pkglibexecdir@/coldplug -- Cold plugging system description = "Cold plugging system"
runlevel = "S"
if = "mdev"
conflicts = { "udevd", "mdevd" }
conditions = { "service/mdev/running" }
cgroup init {}
command = "-@pkglibexecdir@/coldplug"
}
+9 -3
View File
@@ -1,4 +1,10 @@
# Test service, only used for `make check`, not for public use # Test service, only used for `make check`, not for public use
service [S123456789] name:testserv pid:!/run/testserv.pid \ service testserv {
<boot/testserv> cgroup.system notify:none \ description = "Test serv daemon"
/sbin/serv -n -p -i testserv -- Test serv daemon runlevel = "S123456789"
conditions = { "boot/testserv" }
notify = "none"
pidfile = "/run/testserv.pid"
cgroup system {}
command = "/sbin/serv -n -p -i testserv"
}
+10
View File
@@ -31,6 +31,11 @@ EXTRA_DIST += add-remove-dynamic-service.sh
EXTRA_DIST += add-remove-dynamic-service-sub-config.sh EXTRA_DIST += add-remove-dynamic-service-sub-config.sh
EXTRA_DIST += bootstrap-crash.sh EXTRA_DIST += bootstrap-crash.sh
EXTRA_DIST += cond-start-task.sh EXTRA_DIST += cond-start-task.sh
EXTRA_DIST += conf-format.sh
EXTRA_DIST += conf-dirs.sh
EXTRA_DIST += conf-if.sh
EXTRA_DIST += conf-template.sh
EXTRA_DIST += script-timeout.sh
EXTRA_DIST += crashing.sh EXTRA_DIST += crashing.sh
EXTRA_DIST += dep-chain-reload.sh EXTRA_DIST += dep-chain-reload.sh
EXTRA_DIST += depserv.sh EXTRA_DIST += depserv.sh
@@ -75,6 +80,11 @@ TESTS += add-remove-dynamic-service.sh
TESTS += add-remove-dynamic-service-sub-config.sh TESTS += add-remove-dynamic-service-sub-config.sh
TESTS += bootstrap-crash.sh TESTS += bootstrap-crash.sh
TESTS += cond-start-task.sh TESTS += cond-start-task.sh
TESTS += conf-format.sh
TESTS += conf-dirs.sh
TESTS += conf-if.sh
TESTS += conf-template.sh
TESTS += script-timeout.sh
TESTS += crashing.sh TESTS += crashing.sh
TESTS += dep-chain-reload.sh TESTS += dep-chain-reload.sh
TESTS += depserv.sh TESTS += depserv.sh
+120
View File
@@ -0,0 +1,120 @@
#!/bin/sh
# Verify the per-service directory settings: runtime-dir, state-dir,
# cache-dir, logs-dir, and config-dir. The directory is created before
# the service starts, owned by the service user, and exported to the
# environment. The runtime directory is removed again when the service
# stops, the others persist.
set -eu
TEST_DIR=$(dirname "$0")
# shellcheck disable=SC2034
BOOTSTRAP="service owned {
runlevel = \"S12345\"
user = \"daemon\"
group = \"daemon\"
runtime-dir = \"owned\"
state-dir = \"owned\"
pidfile = \"/run/owned/serv.pid\"
command = \"/sbin/serv -np -P /run/owned/serv.pid -i owned -e STATE_DIRECTORY:/var/lib/owned\"
}
task probe {
runlevel = \"S12345\"
runtime-dir = \"probe\"
cache-dir = \"probe\"
command = \"/sbin/serv -h -e RUNTIME_DIRECTORY:/run/probe -e CACHE_DIRECTORY:/var/cache/probe\"
}
service escape {
runlevel = \"S12345\"
runtime-dir = \"../escape\"
command = \"serv -np -i escape\"
}
service modes {
runlevel = \"S12345\"
user = \"daemon\"
runtime-dir = \"modes\"
runtime-dir-mode = 0700
runtime-dir-preserve = \"restart\"
config-dir = \"modes\"
command = \"/sbin/serv -np -P /run/modes/serv.pid -i modes\"
}"
# ls output is empty for an empty directory, so test -d instead
assert_dir()
{
assert "Directory $1 exists" "$(texec test -d "$1" && echo yes)" = "yes"
}
assert_nodir()
{
assert "Directory $1 removed" "$(texec test -d "$1" || echo gone)" = "gone"
}
assert_owner()
{
assert "$1 owned by $2" "$(texec stat -c %U:%G "$1")" = "$2"
}
# shellcheck source=/dev/null
. "$TEST_DIR/lib/setup.sh"
say 'Directory created before start, owned by the service user'
retry 'assert_status owned running'
assert_dir /run/owned
assert_owner /run/owned daemon:daemon
assert_dir /var/lib/owned
assert_owner /var/lib/owned daemon:daemon
say 'The paths are exported to the process environment; both commands'
say 'verify their own with serv -e, and refuse to run on a mismatch'
say 'A completed task no longer holds its runtime directory'
retry 'assert_status probe done'
assert_nodir /run/probe
assert_dir /var/cache/probe
say 'Stopping the service removes the runtime directory, state persists'
run "initctl stop owned"
retry 'assert_status owned stopped'
assert_nodir /run/owned
assert_dir /var/lib/owned
say 'Starting again recreates it'
run "initctl start owned"
retry 'assert_status owned running'
assert_dir /run/owned
say 'A path escaping the base directory is refused, service still runs'
retry 'assert_status escape running'
assert_nodir /escape
say 'runtime-dir-mode sets the mode, config-dir is never chowned'
retry 'assert_status modes running'
assert "mode is 0700" "$(texec stat -c %a /run/modes)" = "700"
assert_owner /run/modes daemon:root
assert_owner /etc/modes root:root
say 'runtime-dir-preserve restart keeps the directory across a restart'
run "touch /run/modes/keepsake" || texec touch /run/modes/keepsake
run "initctl restart modes"
retry 'assert_status modes running'
assert_file_exists /run/modes/keepsake
say 'but a real stop still removes it'
run "initctl stop modes"
retry 'assert_status modes stopped'
assert_nodir /run/modes
say 'An existing directory with the wrong owner is chowned back, and'
say 'its mode is locked down again'
run "initctl stop owned"
retry 'assert_status owned stopped'
texec mkdir -p /var/lib/owned/sub
texec touch /var/lib/owned/sub/file
texec chown -R 0:0 /var/lib/owned
texec chmod 0700 /var/lib/owned
run "initctl start owned"
retry 'assert_status owned running'
assert "mode locked down to 0755" "$(texec stat -c %a /var/lib/owned)" = "755"
assert_owner /var/lib/owned daemon:daemon
assert_owner /var/lib/owned/sub/file daemon:daemon
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff. Show more