Compare commits

...
314 Commits
Author SHA1 Message Date
Joachim Wiberg 530d6703f1 Merge pull request #509 from anees-r3hman/umount-rc
mount: report unmount failures
2026-10-12 09:38:08 +02:00
Joachim Wiberg cec76f18a3 doc: ChangeLog entry for the lost reload fix
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-09-27 18:56:03 +02:00
Joachim Wiberg ee6d4a1dae service: keep a pending reload across a second conf reload
A service whose condition goes into flux during a reload is paused
with its reload still pending.  If another reload was requested in
the meantime, re-parsing its unchanged .conf file cleared the pending
mark, so the service was resumed without ever being reloaded.  Seen
with sshd <pid/syslogd> on Infix, where a configuration change that
touched both landed as two reloads in a row and sshd kept its old
listen addresses.

The mark is only ever cleared once the change has been applied, so a
mark that is still set when the file is parsed again means exactly
that: not applied yet.  Leave it alone.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-09-27 18:41:05 +02:00
Joachim Wiberg 0548485be1 doc: update ChangeLog for 5.0
Covers the longer user and group names, the tmpfiles mode and owner
work, the shutdown remount order, and the stop/reload script timeout.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-09-27 18:00:13 +02:00
Joachim Wiberg 1b10c3746e Merge pull request #508 from anees-r3hman/avoid-fstab-on-root-remount
sig: avoid reading fstab during shutdown
2026-09-27 17:58:33 +02:00
Joachim Wiberg 24911ebbc2 Merge pull request #507 from anees-r3hman/master
svc: allow longer user and group names
2026-09-27 17:42:11 +02:00
Joachim Wiberg 88a9e6575b tmpfiles: apply mode and owner of e and f/F entries through an fd
Same treatment as d/D got: e went through fisdir() and a path based
chmod/chown, f/F did the chmod/chown by path after closing the file.
Both now work on the open fd, so the mode and owner end up on the
directory or file that was just checked or written.  f/F use open(2)
directly, which lets a plain f rely on O_EXCL for create-if-missing
instead of the earlier stat().

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
(cherry picked from commit 75739fbeef)
2026-09-27 17:30:16 +02:00
Sam Brkopac 9bfbdc6bac tmpfiles: set mode and owner of d/D directories through an fd
makedir() swallows EEXIST, so the chmod branch for directories that
already existed never ran, while chown() ran every time.  On finix
/var/empty is immutable, which gave a warning on every boot even
though the owner was already correct.

mksubsys() is the original of this code and has the same shape, the
dbus plugin uses it for /tmp/dbus.  Both now go through dirperm(),
which opens the directory and uses fstat/fchmod/fchown on the fd,
changing only what differs.  A failed chown in mksubsys() is a warning
now rather than err(1), PID 1 should not exit over a directory it
cannot adjust.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
(cherry picked from commit 3a72d1263c)
2026-09-27 17:26:08 +02:00
Anees Rehman c016965371 mount: report unmount failures
Return a failure status from unmount_regular() when filesystems remain
mounted, and use it to report the final shutdown unmount result.

Signed-off-by: Anees Rehman <anees.rehman@atlascopco.com>
2026-09-25 21:20:02 +02:00
Anees Rehman 263ea929fd sig: avoid reading fstab during shutdown
Only fall back to the regular mount invocation when both
fstab-independent root remount attempts fail.
This avoids reading an encrypted fstab during a normal shutdown.

Signed-off-by: Anees Rehman <anees.rehman@atlascopco.com>
2026-09-25 19:41:05 +02:00
Anees Rehman cc5e0c5175 svc: allow longer user and group names
Service user and group names are stored in 16-byte buffers, limiting
them to 15 characters. This silently truncates longer names before
getpwnam() and getgrnam(), preventing services configured with valid
longer account names from starting.

Increase the buffers to match the existing maximum configuration
argument length.

Signed-off-by: Anees Rehman <anees.rehman@atlascopco.com>
2026-09-25 16:09:35 +02:00
Joachim Wiberg 417abf9000 Merge pull request #500 from finit-project/pam 2026-09-23 17:52:38 +02:00
Joachim Wiberg 61b0e0f3e6 Fix #420: run services inside a PAM session
Apply a PAM session to run/task/sysv/services Finit starts, pam_limits
above all, so a service running as a given user picks up that user's
limits the way a login does.

Add a new `pam` setting for the new block format (only), like the
per-service directories, naming a file in /etc/pam.d:

    service weston {
        user    = "weston"
        pam     = "weston-autologin"
        command = "/usr/bin/weston --continue-without-input"
    }

pam_close_session() has to be called by a process still holding the
handle, and the handle does not survive exec().  Hence the keeper: it
holds the handle, drops to the service's credentials, and waits for a
parent-death signal before closing the session.  Same shape as
systemd's (sd-pam), for the same reason, and one per fork, so the
script hooks open and close their own.

The keeper closes the descriptors it inherited from Finit and only
those.  Closing everything would also take out what pam_open_session()
opened for itself, a keyring fd or a lock file, and leave the modules
to close a session with those pulled out from under them.  Closing
nothing, as (sd-pam) does, would leave it holding the write end of the
notify pipe for the service's whole lifetime and starve notify = "s6"
services of their ready signal.  So the fds open before pam_start()
are snapshotted and exactly those are closed, while the ones PAM opens
after are marked close-on-exec so the daemon does not inherit them
either.

A refused value, a denied account stack, an uninstalled pam.d file,
and a build without PAM support all keep the service from starting
rather than running it with the stacks skipped: one that quietly loses
pam_limits and its private /tmp, with nothing said.  Capabilities a
module like pam_cap.so granted are merged into the IAB Finit applies
instead of being replaced by it, which only helps a service that also
sets capabilities, the other arm being a plain setuid() with nothing
left to restore once permitted is empty.

The test sysroot gains pam_permit.so, pam_deny.so and pam_limits.so,
which ldd cannot see, libpam dlopen()s them, and the test skips when
the host has none to stage.  The negative cases pin the exit status
rather than only asserting crashed, which serv reports for any early
exit, so a bad command or an unwritable pidfile cannot pass for a
rejected session.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-09-23 16:30:14 +02:00
Joachim Wiberg bad7c5c99a .github: unwrap ChangeLog paragraphs in release notes
The ChangeLog is hard-wrapped at 72 columns, which the GitHub releases
page renders verbatim as a ragged right edge.  Join the continuation
lines of each paragraph and list item, as already done in Infix, and
let the browser do the wrapping.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-30 10:12:29 +02:00
Joachim Wiberg 4c16190831 build: drop md5 and sha256 files from releases
GitHub now shows a sha256 digest for every uploaded release asset, so
the sidecar hash files only clutter the asset list.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-30 10:12:29 +02:00
Joachim Wiberg a274677f05 configure: keep the replacement libsystemd opt-in
Building it by default installs our libsystemd.so.0 in $libdir.  Where
the real one is already present, and contrib/debian/build.sh configures
--prefix=/usr --exec-prefix= on a Debian host, ours outranks it in the
loader cache, and every program linking libsystemd loses
sd_journal_stream_fd and the LIBSYSTEMD_209 symbol versions.  The test
sysroot ran into exactly that, dbus-daemon exited 127 on every restart.

Restore the default to no.  sulogin and watchdogd stay on, they only add
binaries.  distcheck asks for the library so it stays covered.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-29 15:21:29 +02:00
Joachim Wiberg f51aee1ec5 test: keep the replacement libsystemd out of the sysroot
dbus-broker fails, the bus never comes up:

    Service dbus[19] died (exit status: 127), restarting

The sysroot stages dbus-daemon from the host, which links the real
libsystemd, and since v5.0 builds ours by default the install puts a
second libsystemd.so.0 in the sysroot.  It wins the ld.so cache, and
dbus-daemon finds neither sd_journal_stream_fd nor the LIBSYSTEMD_209
symbol versions in it.

Uninstall the library from the sysroot after the install.  The test serv
compiles sd-daemon.c in, so nothing there needs it.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-29 14:28:59 +02:00
Joachim Wiberg cdf2337346 configure: build sulogin, watchdogd, and libsystemd by default
v5.0 ships keventd and the D-Bus support enabled out of the box, but the
remaining bundled pieces stayed opt-in, and the help text for two of them
already claimed otherwise.

Default all three to yes; --without-sulogin, --without-watchdog, and
--without-libsystemd opt out.  The distcheck and CI configure lines drop
the flags they no longer need, so CI exercises the defaults.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-29 13:54:19 +02:00
Joachim Wiberg 847f4db974 dbus: install the bus policy in $datadir/dbus-1/system.d
make distcheck fails in install:

    /usr/bin/install: cannot create regular file '/etc/dbus-1/system.d/org.finit.conf': Permission denied

The policy was installed to $sysconfdir/dbus-1/system.d.  distcheck only
overrides the prefix, so the file escaped its sandbox and aimed for the
real /etc.

Install it where dbus looks for package owned policy, leaving
/etc/dbus-1/system.d to the admin.  The test bus config reads it
relative to itself.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-29 13:54:14 +02:00
Joachim Wiberg 969f9a112c test: locate the fuzz sweep in the build tree
fuzz-msg-parse.sh looked for its driver next to the script, which only
holds when srcdir and builddir are the same.  Every VPATH build skipped
the sweep, claiming D-Bus was off:

    SKIP: fuzz-msg-parse.sh

distcheck, and with it the release job, is a VPATH build.  Use
top_builddir, which the test environment already exports.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-29 13:54:13 +02:00
Joachim Wiberg 2b812101be test: run keventd from the configured libexec path
make distcheck fails in dbus-device:

    sh: /libexec/finit/keventd: not found

distcheck configures with --prefix=$(distdir)/_inst, so the bundled
helpers install below that prefix, not in /libexec/finit, which the test
had hard coded.

Export the configured pkglibexecdir as FINIT_EXECPATH in test.env and
run keventd from there, the same path finit itself is built with.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-29 13:54:06 +02:00
Joachim Wiberg 7644874b42 Update ChangeLog and bump version for v5.0-rc1
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-29 09:35:11 +02:00
Joachim Wiberg c512d57df4 doc: describe the udev rules keventd actually implements
The engine covers most of the udev grammar but not all of it, and the
gaps are invisible until a rule silently does nothing.  Write down what
is implemented and where it parts ways with udev(7), rather than
leaving people to infer it from a ruleset that happens to work.  The
man page gets the directory precedence and a pointer to udev(7) and
the User's Guide, which hold the details.

Rename the menu entry to Device Manager while here, matching how the
watchdog daemon is listed.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-29 09:27:26 +02:00
Joachim Wiberg 59945b87f7 keventd: load udev rules in udev's order, with masking
The rules directories were read one after the other, each sorted on
its own.  udev instead sorts all files from all directories together
and lets a file mask one of the same name in a lower priority
directory.  The difference broke both standard override idioms
without a sound: copying a rules file to /etc/udev/rules.d/ to edit
it left the original running too, and symlinking a name to /dev/null
disabled nothing.

Collect everything with glob(), drop entries shadowed by a later
directory, and sort the survivors by filename, same as udev and the
tmpfiles.d handling in Finit itself.  The -r directory sits above
/etc/ so a test ruleset can override anything shipped.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-29 09:26:18 +02:00
Joachim Wiberg d23a9b8a16 Merge pull request #499 from aanderse/docs
docs: cleanup some development work
2026-08-29 08:05:54 +02:00
Joachim Wiberg e5040385f5 test: fix flaky crashing.sh
Two races, both around the post:script Finit runs when it gives up on a
service.

Finit marks the service crashed before it forks post:script, so the file
the test greps for lands a moment later.  Waiting for the state is not
enough.

slay then takes the PID from initctl status, and service_post_script()
sets svc->pid to the script's PID, so once Finit has given up the PID
reported for the service is the post:script.  Killing that takes out the
script instead of the service and the file never arrives at all.  The
guard for this was already there, with a comment describing it, but
inside the loop that waits for a PID to appear, so it only covered the
case where there was none.  A PID that was already there went straight
to kill -9.  Hence the gcc leg killing once more at lap 13, after Finit
had stopped restarting, where clang stopped at 12.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-22 10:25:28 +02:00
Joachim Wiberg e9efec8694 Merge pull request #501 from aanderse/libsystemd
libsystemd: guard sd-daemon.h with extern C for c++ consumers
2026-08-22 07:51:27 +02:00
Aaron Andersen 7110ae4427 libsystemd: guard sd-daemon.h with extern C for c++ consumers 2026-08-21 16:08:15 -04:00
Aaron Andersen a7ee24662f docs: cleanup some development work 2026-08-19 21:00:48 -04:00
Joachim Wiberg 48e85efb88 Merge pull request #491 from finit-project/devman
Finit 5: udev/eudev replacement

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-18 10:37:14 +02:00
Joachim Wiberg e8391864d7 conf: read /run/finit/system before /lib/finit/system
Stanzas in /lib/finit/system cannot use if:keventd, or any other
built-in service: if: is evaluated at parse time, and the generated
.conf files for built-ins were read after the system directory, so the
referenced service did not exist yet and the stanza was dropped.

The late position was deliberate: it kept bundled services from starting
before udev (5d703fd3).  The motivating case was dbus, which has since
moved to a build-time 20-dbus.conf (2e0b1d6f), and the built-ins that
remain want the early slot: watchdogd should start as soon as possible,
keventd is the device manager, and runparts is gated on int/bootstrap so
its position never mattered.

Reading /run/finit/system first also puts the override chain in its
natural order: built-in/generated defaults, then bundled (read-only)
system files in /lib, then the administrator (override) .conf file in
/etc/finit.d, always read last.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-17 19:04:52 +02:00
Joachim Wiberg 2a2f7cda63 Merge pull request #496 from FixeQD/master
switch-root: return real errors instead of false success

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 23:41:08 +02:00
Paweł Sobczak ad02b343f2 initramfs: drop to sulogin when switch_root fails past the ACK
A failed move of /dev, /proc, /sys or /run is logged at dbg() level
and ignored, so the new init boots without its virtual filesystems
and falls over much later in some unrelated way.  The steps after,
chdir/mount/chroot and the final execl(), do return -1 on failure,
but by then all services are dead and the API socket is gone, so
there is nobody left to report to: the system hangs with a live but
useless PID 1.

Make a failed move fatal, and try all four moves even if one fails,
so a bad /dev does not also skip /proc, /sys and /run.  A plain
directory is not an error though: /run stays a plain directory on a
tmpfs rootfs, so only a path on a different device than / is treated
as a mount point and moved.

Any failure past the point of no return now drops to sulogin(1) for
a maintenance shell that reboots on exit, same as a fatal fsck() at
boot.  Signals are unblocked before the moves so the shell does not
inherit finit's blocked signal mask.

Signed-off-by: Paweł Sobczak <github@fixeq.qzz.io>
2026-08-16 23:17:57 +02:00
Paweł Sobczak 7263641953 initramfs: reject a non-regular init and an overlong init path
The precheck accepts a directory as new init: access(path, X_OK)
only checks search permission, which directories almost always have,
so the mistake is not caught until after teardown.  The snprintf()
building init_path also never checks for truncation, so an overlong
newroot + newinit validates the wrong path.

Require a regular file, with EISDIR or ENOEXEC to match what is
printed, and fail with ENAMETOOLONG on a truncated init path.

Signed-off-by: Paweł Sobczak <github@fixeq.qzz.io>
2026-08-16 23:17:57 +02:00
Paweł Sobczak 42840f0f36 switch-root: NACK invalid requests instead of false success
initctl switch-root always exits 0, no matter what happens on the
finit side.  do_switch_root_api() sends the ACK before switch_root()
has validated anything, so any failure after that point never reaches
the client -- the error only shows up in the log.  The runlevel guard
in api_cb() has the same problem: it rejects the request but still
sends an ACK.

Split the validation out of switch_root() into switch_root_precheck()
and run it before the ACK.  A failed check now sends a NACK with the
error message, which initctl prints before exiting 1:

    initctl switch-root /mnt
    switch-root: /mnt is not a mount point

The ACK is only sent once the precheck passes, since after that point
we are committed.  On success finit execs the new init and the
connection dies with no reply at all, so initctl treats only an
explicit NACK as failure.

Signed-off-by: Paweł Sobczak <github@fixeq.qzz.io>
2026-08-16 23:17:57 +02:00
Joachim Wiberg 1a2cc950f0 man: sync keventd(8) with the daemon
Half the daemon was missing: -p, -r, -S, -t from SYNOPSIS and
OPTIONS, class/ and driver/ conditions, SIGHUP rules reload, the
rules engine and its directories, /run/udev/data, and the D-Bus
socket in FILES.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:45 +02:00
Joachim Wiberg 7eb32bc543 keventd: one condition emitter, one file reader, one clock
Three copies of read-first-line-from-a-file (sysfs_read_file, a
static sysfs_read, fgetline), two condition emitters symlinking the
same reconf marker (cond_emit, sys_cond), two seqnum-stable loops,
and five hand-rolled logit() prototypes, none with a printf
attribute, so no format string was ever checked.

Keep one of each: cond_emit() is exported and mkpaths its parents,
which also retires init_dev_condition_dir() and the sys/ directory
priming loop; logit() moves to keventd.h with the format attribute;
the stability test becomes seqnum_stable(), shared by the coldplug
gate and settle; kev_now_ms() serves dbus.c too.  rule_ctx_free()
and kev_seq_baseline() had one caller each in their own files, now
static.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:45 +02:00
Joachim Wiberg 558c0f8c3b dbus: notify externals when reconfiguration completes
initctl reload freezes conditions to the old generation and each
owner re-asserts.  Finit's own providers do this in-process; an
external provider whose conditions are generation files, rather than
the oneshot symlinks keventd uses, has no way to know the moment.
Emit Manager1.ConfigReloaded when reconfiguration completes.

keventd needs no subscriber: its conditions are symlinks to the
reconf marker itself, so they read the current generation by
construction and never flux, which the device bus test now pins
down.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:44 +02:00
Joachim Wiberg 230c65a080 test: cover org.finit.Device1 end to end
Introspection, queue-state properties, immediate and bus-first
settle, Info by devpath, Trigger(add, net) observed via the
DeviceProcessed signal, and RulesReload -- driven by dummy interface
hotplug like keventd.sh.  New call-ss and call-u client modes, and
getprop learns the b and t variants.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:44 +02:00
Joachim Wiberg 292e87c0d1 keventd: serve org.finit.Device1 on /run/keventd/bus
The udev parity gaps that were blocked on IPC -- settle, trigger,
info, queue introspection, runtime rule reload -- become bus methods.
keventd serves its own socket the way Finit serves /run/finit/bus:
brokerless, libink, one socket per daemon, no forwarding between the
two.

  Settle(u) -> b      parked until the queue drains or the timeout
                      passes; true when settled
  Trigger(s, s)       replay events, action + subsystem glob
  Info(s) -> a{ss}    /run/udev/data properties for a devpath
  RulesReload() -> u  re-read rules dirs, returns rule count
  QueueEmpty (b), SeqnumProcessed (t) properties
  DeviceProcessed (ss) signal after each fully handled event

The queue state is the highest kernel seqnum keventd has handled,
baselined at startup, against /sys/kernel/uevent_seqnum.  keventd -S
now asks the running daemon first and falls back to seqnum polling.
In passive mode Trigger and RulesReload refuse.  Adds
link_call_connection() for the park bookkeeping.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:43 +02:00
Joachim Wiberg 6e60b8183a keventd: read udev database records by devpath
udevdb_read_parent() contained the general capability, keyed from
sysfs when no uevent is in hand.  Split it out as
udevdb_read_devpath() for the D-Bus Info method; parent lookup
becomes a wrapper.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:43 +02:00
Joachim Wiberg 289c1e0de6 keventd: generalize coldplug into a filtered trigger
coldplug_trigger(action, subsystem-glob) replays events for a subset
of devices, the D-Bus Trigger method needs both knobs; coldplug() is
now the ("add", NULL) case.  nftw() has no user cookie, so the
parameters ride in file statics.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:42 +02:00
Joachim Wiberg 1bb7d508a4 libink: let a handler park its call and reply later
The park machinery was welded to broker uid resolution; a handler
that cannot answer yet, like a device-settle call waiting for the
event queue to drain, had no way to defer.  link_call_park() holds
the request, link_call_resume() re-runs the handler with
link_call_resumed() reading true, and the expire sweep remains the
backstop for a resume that never comes.

Resume also no longer drops a local caller's kernel group set in the
privileged re-check: group source now keys on broker-ness.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:42 +02:00
Joachim Wiberg 2b861ea0af libink: 64-bit integer marshalling
Device seqnums are 64-bit; the writer and reader stopped at u32.
Adds t/x/d to the skip path so a{sv} consumers tolerate them.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:42 +02:00
Joachim Wiberg a814e85356 doc: complete and cross-link the D-Bus documentation
dbus.md was orphaned: not in dist_docs_DATA, not linked from the
user guide.  Wire it into the doc dist and link it from the index
features list, features.md, initctl.md, and plugins.md, where the
dbus.so plugin entry now disambiguates the external system bus from
the built-in org.finit API.

Document the 64-peer cap, the supported AddMatch keys, the busconfig
policy file, the legacy-parity edge semantics with the deliberate
SetRunlevel InvalidArgs divergence, the reload-signal behavior of
Service1.Reload, and the reboot family timeout.  Refresh the stale
initctl.md usage paste, add monitor and the D-Bus transport to
initctl(8), add /run/finit/bus to the filesystem layout, and flatten
the ChangeLog D-Bus entry to house style.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:41 +02:00
Joachim Wiberg 39d21ca0bd .github: build leg without D-Bus
The D-Bus support is default-enabled, so the HAVE_DBUS paths only
bit-rot silently without this: the leg caught initctl failing to
build with --disable-dbus on its first local run.  Also asserts the
binaries carry no bus references and smoke-runs one non-dbus test.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:41 +02:00
Joachim Wiberg 480f9c6a9d build: initctl loses AM_CPPFLAGS without D-Bus
initctl_CPPFLAGS is only assigned under the DBUS conditional, but an
automake per-target variable exists even when its conditional is
false, so a --disable-dbus build dropped AM_CPPFLAGS entirely:

    util.c:319:16: error: invalid use of undefined type 'struct FTW'

Assign the base flags unconditionally and append under DBUS.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:40 +02:00
Joachim Wiberg 8f1cd35f1c test: exercise _HH object-path encoding with a name:id service
The path encoding in libink/path.c was untested for identities with
separators; dbus-service.sh only used the bare keventd identity.
Declare a dhcp-client:eth1 service and verify the escaped path, that
the object introspects, and that Identity round-trips.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:40 +02:00
Joachim Wiberg b743d15e35 libink: declare signals in introspection XML
All three org.finit signals were invisible to generated proxies, and
org.freedesktop.DBus was missing from the standard interfaces even
though Hello, AddMatch, and RemoveMatch are answered.

Add a link_signal_t table to the vtable, emitted like methods and
properties, declare the Manager1 and Cond1 signals, and complete the
static XML with PropertiesChanged and org.freedesktop.DBus.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:39 +02:00
Joachim Wiberg 9d4cc8e933 service: one owner for user-requested start/stop/restart
The D-Bus methods carried byte-for-byte copies of api.c's static
start/stop/restart helpers.  Promote them to service.c alongside
service_reload(), which already serves both callers, and reduce both
sides to svc_parse_jobstr-style adapters.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:39 +02:00
Joachim Wiberg 3efa9d6f41 test: cover Manager1 edge semantics against the legacy API
Regression tests for the recent handler fixes: bogus SetRunlevel is
InvalidArgs, Signal on a stopped service is Failed, and the reboot
family declares the timeout argument.  Reboot cannot be invoked
without taking down the sandbox, so the latter is asserted via
introspection.  New call-u and call-su modes in dbus-auth-client.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:39 +02:00
Joachim Wiberg 62b55d5b43 dbus: honor the shutdown timeout in the reboot family
initctl -t N reboot arms an emergency bypass timer over the legacy
socket, but the bus methods took no argument, so the timeout was
silently dropped whenever D-Bus was up.

Reboot, Halt, and Poweroff now take a timeout in seconds, 0 for
none, armed via the same shutdown_bypass() the legacy path uses.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:38 +02:00
Joachim Wiberg 8b61aaea1a dbus: report action and condition failures instead of empty success
Service1 Start/Stop/Restart discarded the action return value, their
Manager1 twins map it to org.finit.Error.Failed.  Cond1 Set/Clear
replied success even when the condition symlink operation failed,
where legacy initctl exits 73.  Verify the resulting condition state
with cond_get() rather than the noupdate return values, which report
no-change, not failure, and would reject an idempotent re-set.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:38 +02:00
Joachim Wiberg 2af83ea632 dbus: signalling a stopped service is an error
Manager1.Signal silently skipped stopped services, so the same
command gave different exit codes depending on transport: the legacy
INIT_CMD_SIGNAL path fails when the service is not running.  Mirror
the legacy behavior.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:37 +02:00
Joachim Wiberg b53c7e6879 dbus: mirror legacy runlevel edge cases in Manager1.SetRunlevel
The bus method called sm_runlevel() unconditionally.  In runlevel 0
and 6 that aborts an in-flight shutdown, which INIT_CMD_RUNLVL
refuses with a warning, and during bootstrap it switches immediately
where the legacy path defers via cfglevel to the end of runlevel S.

Port both.  A bad runlevel argument still returns InvalidArgs, where
the legacy protocol acks silently: a typed interface rejects garbage.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:37 +02:00
Joachim Wiberg 8114508bbb dbus: refuse Manager1.Suspend in bootstrap and shutdown
The legacy INIT_CMD_SUSPEND is refused in runlevel S, 0, and 6, the
bus method suspended unconditionally, even mid-shutdown.  Add the
same guard, replying WrongRunlevel like the reboot family.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:36 +02:00
Joachim Wiberg 8138b402a0 test: don't slay a service Finit has given up on
Once it gives up it forks the post:script and reports that PID as the
service's, so a slay still waiting for the service to come back killed
the script instead, and crashing.sh lost the /tmp/post it checks for.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:36 +02:00
Joachim Wiberg 864a13460c test: add keventd device manager test
The unified keventd has no automated coverage, only the devmon
fallback is exercised by the test suite.  Network interfaces are
the one device class an unprivileged test can hotplug: the sandbox
has its own network namespace, so 'ip link add' makes the kernel
emit genuine uevents.

Verify keventd readiness, <class/net/IFNAME> driving a service --
and <dev/IFNAME> NOT asserted, interfaces are not device nodes --
libudev-compatible n<ifindex> keying in /run/udev/data, conditions
surviving initctl reload, and cleanup on interface remove.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:36 +02:00
Joachim Wiberg b3a206a28c devmon: bridge keventd conditions to the service engine
A service with a <class/net/eth0>, or any other keventd-provided,
condition is never started when the device appears.  keventd asserts
the condition file, but devmon only watches /dev, so no cond_update()
ever reaches affected services.  Reload made it worse:
devmon_reconf() clears any registered dev/ condition without a /dev
node behind it.

Watch the dev/, class/, and driver/ condition directories, like the
sys and usr plugins do for their namespaces, and treat an existing
condition file as device presence in devmon_reconf().

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:35 +02:00
Joachim Wiberg 81cb90804e test: include bundled helpers' libraries in the sysroot
keventd links libblkid, which finit itself does not, but sysroot.mk
only copied the libraries finit links.  Inside the sysroot keventd
then fails to start:

    Service keventd[18] died (exit status: 127)

Collect libraries from finit and everything installed under
libexec/finit/ instead.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:35 +02:00
Joachim Wiberg 5a3972714a keventd: defer pidfile until coldplug queue is drained
When started with -c (the default when keventd is the device manager),
gate the pidfile on the kernel's uevent_seqnum having been stable for
200ms.  Up to now ready signaling with the pidfile was done right after
coldplug() triggered the kernel to re-emit events, but before uev_run()
had drained any of them, so <pid/keventd> really only meant "listening
on netlink".

With the gate, services that depend on <pid/keventd> can now assume /dev
is populated and persistent symlinks are live.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:34 +02:00
Joachim Wiberg c1c68054ea keventd: add -S settle mode polling /sys/kernel/uevent_seqnum
Stop-gap "settle" equivalent of udevadm settle for migration scenarios.
Polls /sys/kernel/uevent_seqnum every 50ms and exits zero when the
sequence number has been stable for 200ms (or non-zero after -t SECONDS
timeout, default 30s).

This is racy by design -- a slow probe firing after we return still
races -- so the doc steers users toward dev/, class/, and bind/
conditions for any service they control.  Settle is for legacy boot
scripts and init transitions where condition wiring isn't feasible.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:34 +02:00
Joachim Wiberg f3461ce2ba keventd: add class/<subsystem>/<name> and driver/<name> conditions
dev/<X> only fires when a device gets a /dev node, which leaves a lot of
embedded-relevant hardware uncoverable: DSA switch cores, IIO sensors,
LEDs, backlight, PHYs, regulators -- all live purely under sysfs.

Two new condition namespaces:

  class/<subsystem>/<sysname>  asserted on every sysfs class device add
                                (e.g. <class/leds/blue>)
  driver/<name>                 asserted while the driver is bound to at
                                least one device (e.g. <driver/mt7530>)

A driver can bind to several devices, so driver/ conditions are
refcounted: asserted on first bind, cleared when the last device is
unbound.

dev_cond() is generalized into a static cond_emit(prefix, rel, set) so
class_cond() and driver_cond() share the same mkpath + symlink/erase
code.  The name avoids colliding with src/cond.h's public cond_path()
helper (unrelated function that returns a condition's filesystem path).

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:34 +02:00
Joachim Wiberg e4d9193f06 keventd: skip futile fork for missing /lib/udev/<helper>
When a rule references an absolute helper path (e.g. /lib/udev/fido_id,
/lib/udev/scsi_id) that the system does not ship and we have no matching
builtin either, return 1 from try_builtin_fallback() so the caller does
not fork /bin/sh on a binary that's known to be missing.  Previously
each such uevent left a zombie 127 child for keventd's sigchld_cb to
reap -- harmless but noisy and wasteful at coldplug.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:33 +02:00
Joachim Wiberg 9d5e646e46 keventd: drop SIGCHLD save/restore dance, libuev reaps for us
modprobe_load() and run_program() used to temporarily restore the
default SIGCHLD handler around fork+waitpid because the main loop set
SIGCHLD=SIG_IGN.  With the libuev conversion, sigchld_cb in keventd.c
handles reaping via signalfd -- and signalfd does not interfere with
synchronous waitpid(pid, ...) -- so the dance is dead code.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:33 +02:00
Joachim Wiberg 0111dc2178 keventd: convert main loop to libuev
Replace the raw poll() + global running/reload_rules flag dance with
libuev.  All four signals (SIGUSR1, SIGTERM, SIGHUP, SIGCHLD) are now
handled via uev_signal_init() -- libuev uses signalfd internally so the
handlers run in normal main-loop context, not async signal context.

The SIGCHLD handler waitpid(-1, WNOHANG) reaps any subprocess (modprobe
loads, rule RUN+= helpers) instead of the old SIG_IGN auto-reap trick.
Synchronous waitpid() in modprobe_load() and run_program() still works
because signalfd queues the signal -- it only reaches sigchld_cb on the
next main-loop iteration.

The netlink receive loop becomes uevent_cb() registered via uev_io_init(),
with the receive buffer passed via the watcher's arg slot (avoids
file-static state).  ENOBUFS and EINTR/EAGAIN are still tolerated, any
other recv() error still panic()s.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:32 +02:00
Joachim Wiberg 153a1de043 keventd: record the libblkid build requirement
keventd is the only thing in the tree that links libblkid, so a tree
that used to build now stops in configure with no hint of which package
to install.  Say so where people look for dependencies, and give CI the
package it now needs.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:32 +02:00
Joachim Wiberg 4e914dd8a9 keventd: ship curated udev rules derived from eudev 3.2.14
Add 27 stock rules installed to /lib/udev/rules.d/.  Rules invoking
/lib/udev/<helper> fall back to keventd builtins (path_id, usb_id,
blkid, hwdb, kmod, net_id, input_id) when the helper binary is absent;
user-supplied helpers in /lib/udev/ still take precedence.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:31 +02:00
Joachim Wiberg 67d61a8282 keventd: add udev rules engine
Transform keventd from a power-supply monitor + basic hotplug handler into
a full udev-compatible device manager.

Rules engine (rules.c):
- Full .rules file parser covering all udev key types: ACTION, KERNEL,
  SUBSYSTEM, DEVPATH, ENV, ATTR, SYSCTL, TAG, RESULT, PROGRAM, TEST,
  parent-chain KERNELS/SUBSYSTEMS/ATTRS/DRIVERS, and more
- Pattern matching: plain string, fnmatch glob, and pipe-separated alternatives
- Operators: ==, !=, =, +=, -=, :=
- Assignments: NAME=, MODE=, OWNER=, GROUP=, SYMLINK+=, ENV{k}=, TAG+=, RUN+=
- IMPORT{program|file|builtin|parent|cmdline|db}=
- PROGRAM= with stdout capture for subsequent RESULT== matching
- GOTO=/LABEL= flow control
- Loads *.rules from /lib/udev/rules.d, /run/udev/rules.d, /etc/udev/rules.d
  and an optional extra directory (-r DIR); reloads on SIGHUP

Builtin framework (builtin.c):
- kmod:     load module by MODALIAS or explicit alias
- hwdb:     match device against *.hwdb text files in udev hwdb dirs; builds
	    correct lookup key per subsystem — evdev:input:b*v*p*e* for input,
	    usb:v*p* for USB, raw modalias for PCI/platform
- path_id:  build stable ID_PATH / ID_PATH_TAG from sysfs topology (PCI, USB,
	    ATA, NVMe, platform, ACPI, virtio)
- usb_id:   read idVendor/idProduct/bcdDevice/serial from sysfs; look up
	    ID_VENDOR_FROM_DATABASE and ID_MODEL_FROM_DATABASE from usb.ids
	    (hwdata package) when available; silent fallback when absent
- input_id: classify input devices (keyboard, mouse, joystick, touchscreen,
	    touchpad) from evdev capability bitmasks in sysfs
- net_id:   generate predictable names — MAC-based enx<mac> and PCI-slot-based
	    enp<bus>s<dev>[f<func>]
- blkid:    probe filesystem type, UUID, and label via libblkid; sets ID_FS_*
	    and ID_PART_TABLE_* properties

Network interface renaming (uevent.c):
- netdev_add() renames interfaces via SIOCSIFNAME when a NAME= rule matched,
  then sets the Finit dev/ condition on the final name; and any setup using
  persistent interface naming via udev rules

Device node and symlink improvements (uevent.c):
- NAME=, MODE=, OWNER=, GROUP= overrides from matched rules applied at
  mknod/chown time, falling back to the built-in permission table
- SYMLINK+= links from rules applied alongside built-in by-id/by-path links

Device property database (udevdb.c):
- Persist per-device E:/S:/I: records to /run/udev/data/ on ADD/CHANGE,
  delete on REMOVE; IMPORT{db}= restores saved properties into event env

Build:
- libblkid (util-linux) is now required for keventd

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:31 +02:00
Joachim Wiberg dda0d5c763 Relocate keventd from src/ to keventd/
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:31 +02:00
Joachim Wiberg 48f677c3a0 keventd: signal readiness via pidfile after coldplug
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:30 +02:00
Joachim Wiberg db5567b5d1 keventd: add passive mode (-p) for coexistence with hotplug plugin
This is a backwards compatible mode for users upgrading and not noticing
that keventd is now build by default.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 22:03:30 +02:00
Joachim Wiberg 8e91bb4297 keventd: disable legacy kernel uevent helper at startup
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 21:43:28 +02:00
Joachim Wiberg 71628471bc keventd: minor, constify + coding style
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 21:43:28 +02:00
Joachim Wiberg 425256ff3d doc: update keventd docs with new rebrodcast feature
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 21:43:28 +02:00
Joachim Wiberg 155d21cee7 keventd: add netlink uevent rebroadcast for libudev-zero
After keventd processes a uevent (creating device nodes, loading
modules, etc.), rebroadcast the original event to netlink group
0x4 so that libudev-zero consumers -- graphical applications,
Wayland/X11 compositors, libinput, and anything else using libudev
to monitor device hotplug -- can receive device events.

Rebroadcast is enabled by default.  Use -g to override the target
netlink group mask, or -G to disable rebroadcast entirely.  Bit 0
(kernel group) is always masked out to prevent feedback loops.

Ref: https://github.com/finit-project/finit/issues/451#issuecomment-3817233886
See: https://github.com/illiliti/libudev-zero

Suggested-by: Aaron Andersen <aaron@fosslib.net>
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 21:43:27 +02:00
Joachim Wiberg 120c8e6002 man: add fine manual for keventd(8)
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 21:43:27 +02:00
Joachim Wiberg 8cf98a7bfd doc: rewrite keventd documentation for unified device manager
Rewrite doc/keventd.md from a 14-line stub into comprehensive
documentation covering all features of the new unified keventd:
device node creation, persistent symlinks, firmware loading,
module loading, coldplug, conditions, and command-line usage.

Update doc/conditions.md to list keventd as the primary provider
of dev/* and sys/pwr/* conditions, with devmon as fallback when
an external device manager is used instead.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 21:43:26 +02:00
Joachim Wiberg 8ea299f39d keventd: expand scope to become a unified device manager
Evolve keventd from a power_supply-only monitor into a full device
manager capable of replacing mdev/mdevd on embedded systems.  This
is the first step towards Finit v5.0 where keventd absorbs devmon.

New capabilities:

 - Parse all uevent actions (add, remove, change, bind, unbind)
 - Create and remove /dev nodes with subsystem-aware permissions
 - Create persistent symlinks in /dev/disk/by-{id,path} and
   /dev/input/by-{id,path}, tracked for cleanup on device removal
 - Load firmware from /lib/firmware/ via the sysfs loading protocol
 - Spawn modprobe for MODALIAS events (async, non-blocking)
 - Coldplug support via -c flag (walks /sys/devices to replay events)
 - Set dev/* conditions for Finit's service dependency system

The original power_supply monitoring and sys/pwr/ac condition are
preserved.

New files: keventd.h (structures/API), uevent.c (all device logic).
The receive buffer is increased to 8K with a 1MB socket buffer to
reduce event loss during coldplug bursts.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 21:43:26 +02:00
Joachim Wiberg f3b013bc0a runparts: improve usage text
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 21:43:26 +02:00
Joachim Wiberg 8e2492fc87 Merge pull request #490 from finit-project/next
Finit 5.x D-Bus Support

Fixes #396

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 09:23:53 +02:00
Joachim Wiberg 3231ade38a dbus: fixes from a code review of the branch
A pass over the whole branch before merge, mostly in libink since
that is the new code and the part exposed to the wire.  Grouped here
rather than scattered so the review is easy to read in one place.

libink parser and dispatch:

 - Bound reader lengths so a 32-bit size_t can't wrap a wire length
   past the guard and read out of bounds.  Reachable pre-auth on any
   bus, so it matters on the 32-bit targets Finit runs on.
 - Drop a peer when a reply send fails instead of limping on with a
   half-written frame; a built-in whose send failed used to fall
   through and put a second frame on the wire.

initctl:

 - Copy a D-Bus error name out of the reply before closing the client;
   the reply points into memory the close frees.  Both error paths now
   share one helper so this can't creep back.

Authorization:

 - Take the caller's groups from the kernel (SO_PEERCRED plus
   SO_PEERGROUPS) rather than getpwuid()/getgrouplist(), which go
   through NSS and can block PID 1 on a slow LDAP or SSSD backend.
   The check is now a lookup against the group resolved once at init,
   with no NSS and no 256 KiB array on the stack.  A caller reaching
   us through a broker carries no group set, so system-bus privileged
   methods are root-only; the local bus keeps group support.  See
   libink/README.md for the note on lifting that.

Shutdown:

 - Call dbus_exit() from the shutdown path so the server, its peers,
   and the socket are let go cleanly.  The teardown existed but nobody
   called it.

Tests, CI, docs:

 - A fuzz target for the message parser, run as a quick sweep in the
   suite and properly under libFuzzer in CI, with the corpus carried
   between runs.  The -as-uid tests drop groups the way a login does
   so SO_PEERGROUPS sees the right set, and widen the test socket to
   reach the per-method check behind the 0660 gate.  Bring the GitHub
   actions up to versions that run on Node 24, and tidy a few small
   things a /simplify pass turned up.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-16 08:57:38 +02:00
Joachim Wiberg e62b463852 .github: bump actions to node24
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:19 +02:00
Joachim Wiberg c28acf21a1 test: fuzz target for the message parser
__msg_parse() turns bytes off a socket into pointers, before anything
has vouched for the peer, and it is the only place in libink that
does.  It had no test of its own beyond whatever the other tests
happened to send it, all of it well-formed.

The target checks the parser's contract, not merely that it survived.
A header field must point into the header field array, and terminate
inside it, and the parse must never claim more bytes than it was
handed.  Crash-only would pass a parser that walked into the body and
returned fields from there, since those bytes were handed over too.
The expected bounds are derived from the raw header rather than from
the parser, so the two have to agree independently.

Every input is copied into an allocation sized to it first.  Reading
past the end of a roomy buffer stays inside the allocation and the
sanitizer never sees it; against an exact one the same read is a
fault, which is where the sharpest findings come from.

Under libFuzzer it is an ordinary fuzz target and named files replay,
which is how a find gets reproduced.  With no arguments it runs a
fixed sweep -- every truncation, every single-byte corruption, every
value of the length that decides where the header ends, and seeded
garbage -- so the suite covers the same contract on every build,
without clang or a corpus in the tree.  It takes 40 ms.

CI fuzzes it properly on every pull request, keeps the crashers, and
carries the corpus between runs so it reaches deeper over time than
any single run can.  Note that clang links the fuzzer runtime against
the newest GCC tree it finds, so the libstdc++ headers have to match
that one and not the default compiler, which is worth saying since
installing the obvious package leaves you exactly where you started.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:19 +02:00
Joachim Wiberg 5cc41b3ba4 libink: take the match keys clients send without asking
A rule carrying sender, destination, or eavesdrop was refused whole,
and a peer whose AddMatch fails gets no signals at all.  That is a
poor trade for keys clients attach as a matter of course: better a
filter wider than asked for than a subscription that never happened.

They are accepted and ignored rather than honoured.  Widening costs
nothing here since Finit is the only sender on this bus, and what it
emits through the match table is state any peer that got this far may
already read.

argN and argNpath still take the whole rule down.  Honouring them
means parsing message bodies, and nothing asks for them yet.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:19 +02:00
Joachim Wiberg f3c73f1192 doc: Properties.Set is absent by design, not pending
"Set not yet implemented" reads as a promise.  Finit exposes no
writable property and has no use for one: everything a caller might
want to change is a Manager1 method, where the authorization lives.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:19 +02:00
Joachim Wiberg bb43b5670c libink: mark the big-endian gap where it is decided
Only the file header said we refuse 'B' messages, and nobody reads a
header comment when they are looking at why a parse failed.  Put it at
the check, with an XXX so it turns up in a grep for known gaps.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:19 +02:00
Joachim Wiberg 853e226812 libink/dbus: give parked and outstanding calls a deadline
A call is parked until the resolver says who sent it, and an outbound
call sits in a pending slot until its reply lands.  Neither had a way
to give up.  A broker that answers GetConnectionUnixUser slowly, or
not at all, leaves the caller waiting forever and keeps the slot; four
of those and every later privileged call is refused with
LimitsExceeded until Finit restarts.

libink cannot time itself out, it has no event loop, so the deadline
is the embedder's to keep.  One sweep per connection covers both, and
the ordering between them stays in the library rather than in each
embedder: calls first, because one timing out usually resolves the
park it was made for, and AccessDenied tells that caller more than a
bare timeout.

The sweep is armed when a resolve is deferred and stops rearming as
soon as nothing is outstanding, so a system that never meets a broker
never wakes up for it.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:19 +02:00
Joachim Wiberg a1969efac1 libink: broker state belongs to the connection that has a broker
Parked calls and outbound calls awaiting a reply only ever happen on a
connection talking to a broker, but the parked array sat on the server
and the pending array on every connection.  A server with no broker
carried 4 KiB of slots it could never fill, and both were reachable
from code paths that have no business in them.

Move both behind one struct, allocated on the first park or call and
freed with the connection.  link_server_t goes from 4400 to 168 bytes;
link_connection_t barely moves, its buffers dominate, but an ordinary
peer no longer carries reply-tracking it never uses.

Tokens are now per bus rather than per server, so link_uid_resolved()
takes the connection the answer is about.  Every resolver already has
it: it is the first argument to both the resolver and the reply
callback.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:19 +02:00
Joachim Wiberg 44e7da6d56 dbus: policy comment predates the per-sender uid lookup
It still described treating every system-bus caller as unprivileged as
the state of things, which stopped being true when Finit learned to ask
the broker who sent a call.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:19 +02:00
Joachim Wiberg 127049d925 test: Finit against a real dbus-daemon
The other dbus-*.sh tests drive libink's own client, so the wire format
was only ever checked against the implementation that wrote it, and the
broker path had no coverage at all.  Every bug found in it so far was
found by hand on a target.

Let the dbus plugin bring up a real dbus-daemon, wait for Finit to
claim org.finit, then talk to Finit with dbus-send, which shares no
code with us.  The privileged call is the interesting one: it can only
be answered by parking the call and asking the broker who sent it.
The bus reads the policy Finit installs, so a malformed org.finit.conf
fails here rather than on a target.

Tests no longer build --with-libsystemd.  Our replacement carries the
real soname but only the sd_notify() symbols, so in the test root it
shadowed the libsystemd the host's libdbus-1 wants and dbus-daemon
died on a missing sd_is_socket.  Nothing under test needs the shared
library: serv is the only consumer and it compiles sd-daemon.c
straight in, which it now does regardless of the flag so notify.sh
keeps testing notify:systemd either way.

Staged from the host by lib/sysroot.mk like any other binary, and
skipped when the host has neither program.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:19 +02:00
Joachim Wiberg 2e0b1d6f8b plugin: start a system bus by default
Finit speaks D-Bus itself now and claims org.finit on the system bus
when it finds one, but nothing in a default build ever brings that bus
up.  The plugin that does was opt-in, so the built-in support sat idle
unless the integrator knew to ask for both halves.

Defaulting it on is only reasonable if the result stays the admin's to
change, and a service registered from C through conf_save_service() is
not: it lands in the run path where it cannot be overridden or emptied
out.  So the daemon moves to 20-dbus.conf and its directories to
tmpfiles.d/dbus.conf, the same way hotplug and every other daemon we
ship them for.  The plugin keeps only what has to look at the running
system, the stale pidfile and the machine UUID.

Those directories are no longer chowned to messagebus.  tmpfiles.d
skips a line whose user does not exist rather than falling back, so
the plugin's messagebus/dbus/root ladder has no equivalent there, and
dbus-daemon binds its socket before dropping privileges anyway.

The plugin already bows out where there is no dbus-daemon installed,
so systems that never wanted a bus are unaffected, and
--disable-dbus-plugin is there for those that have one and would still
rather init left it alone.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:19 +02:00
Joachim Wiberg 9875e76419 dbus: let a dropped peer outlive its own read loop
A peer can be dropped from inside its own read loop: a handler emits a
signal, the write to that very peer fails, and the drop lands while
link_connection_process() still holds the connection and will touch
its rx buffer on the way out.  Freeing there pulls the ground out from
under it.  Unlink the peer and let the event loop free it once the
stack has unwound.

The work has to be scheduled with a non-zero delay.  A uev timer armed
with zero is a disarmed timer, so the queue would never run and the
connections would leak instead.

Losing a peer is also not a warning.  It is what shutdown looks like
from here, and every reboot said so on the console.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:06 +02:00
Joachim Wiberg 1e508f9168 libink: trace connections, calls, and authorization decisions
The legacy socket logs a line per command under initctl debug; the bus
logged nothing, so the transport that now carries most of initctl was
the one you could not watch.

libink gets a logger hook rather than a dependency on Finit's: it
passes the emitting function and a formatted message, and dbus.c hands
both to logit() so the two sources read alike.  Trace points cover the
connection lifecycle, every inbound call, and why a call was refused.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:06 +02:00
Joachim Wiberg 63aabaa6df libink/dbus: identify the caller behind a broker
On the local bus SO_PEERCRED says who is calling and the kernel is the
one saying it.  Behind a broker one connection carries every caller,
so that credential describes dbus-daemon and nothing else, and every
privileged method was refused there, root included.

Ask the bus driver instead.  libink parks the call and hands us the
sender; we ask GetConnectionUnixUser and answer when the reply lands,
through the same event loop as everything else.  Nothing blocks:
blocking in PID 1 is why libuEv exists.  That needs calls libink can
make on a connection it already has, so it gained those too.

Answers are cached, since a bus never reuses a unique name while it
runs.  Not across a restart though: a new dbus-daemon numbers from
scratch and :1.7 becomes somebody else, so the cache goes when the
broker does.  A sender name too long to key on is refused rather than
truncated, two callers sharing a truncated key would share an
identity.

Privilege is no longer uid 0 alone.  The socket is already owned by
the --with-group group, so refusing its members every method that
changes anything left a wheel user able to open the bus and unable to
reboot.  Both gates now say the same thing.

Group membership needs NSS, which the C library loads with dlopen(),
so the lookup is compiled out where Finit is built to link statically.
That leaves such a build root-only, which is worth saying out loud
rather than leaving to be discovered.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:15:06 +02:00
Joachim Wiberg d710a23513 dbus: gate the bus socket like INIT_SOCKET
The D-Bus socket was bound world read/write, on the reasoning that
SO_PEERCRED authorizes each method anyway.  That leaves the read-only
surface open to every local user, and it quietly ignores --with-group:
a system that restricts initctl to the wheel group still handed the
same service state to anyone who asked over the bus.

Bind it 0660 and chown it to the configured group, the same gate the
fallback socket has always had.  libink takes the mode as an argument
rather than assuming one, since who may connect is the embedder's
policy, not the library's.

The mode is applied at bind(), so there is no window where the socket
is more permissive than intended.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:14:48 +02:00
Joachim Wiberg c71ccce742 libink: a broker peer is not an ordinary client
libink was written peer to peer, where one connection is one client
and one principal.  Attaching to a message bus breaks both halves of
that, and two things followed from it.

Signals never reached the system bus.  Fan-out is gated on the peer
having sent AddMatch, but a broker subscribes for its own clients and
never sends us one, so every ServiceStateChanged was dropped on the
floor.  A connection attached with LINK_ATTACH_BROKER gets them all.

Hello, AddMatch and RemoveMatch write per-connection state.  Shared by
every caller behind a broker, that lets one sender exhaust the match
cap or drop another's rule, so we leave all three to the bus, whose
job they are.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:14:48 +02:00
Joachim Wiberg abab0e0fd4 test: depserv waits for the transition to settle
The last assertion sampled bar's state one step after asking Finit to
stop foo, but bar passes through stopped on its way to waiting, so the
test failed roughly one run in eight.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:14:48 +02:00
Joachim Wiberg 3499a5eb76 dbus: say why the system-bus attach failed
The attach is best effort and its diagnostics were tuned for the case
where no broker exists, so a broker that answers but refuses us was
reported as a bare rc=1 at debug level.  Chasing that meant reading
the header builder to find out what the number meant.

Failures now quote the error name the broker sent, and the one for a
name we could not claim says which of the three ways it went wrong.

Repeats stay quiet.  The probe runs on every service and condition
change, and before syslog is up each line is an open, write and close
on /dev/kmsg, so a broker that keeps refusing would otherwise flood
the console during boot.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 10:14:48 +02:00
Joachim Wiberg b55dada80b libink: a message bus is not a peer
libink was written against the only bus it had, its own, where the
peer on the other end is the client.  A broker is not: it routes for
senders it names itself, expects a DESTINATION on anything addressed
through it, and answers on its own schedule rather than next.

Runlevels go on the wire as S and N rather than the digits Finit
keeps internally, since that is what a caller outside Finit means by
one.

The library stays a convenience library, linked into finit and
initctl and installed nowhere: the ABI promise waits until libink is
its own project.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 09:28:26 +02:00
Joachim Wiberg 6310d9e760 initctl: the status views over D-Bus
The summary table, the per-service detail, JSON and the quiet and
ident forms all read state Finit already publishes, so they read it
from the bus like everything else rather than through a second path
that has to be kept in step.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 09:28:26 +02:00
Joachim Wiberg ebc0ef62e6 libink/finit: properties, and org.finit on the system bus
Runlevel and version are state, not actions, so they belong behind
org.freedesktop.DBus.Properties rather than another method each.

Finit also claims org.finit on the system bus when it finds one, so
ordinary D-Bus clients can reach it without knowing about
/run/finit/bus.  Opportunistic on purpose: no dbus-daemon is a normal
state for the systems Finit runs on, not an error to report.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 09:28:14 +02:00
Joachim Wiberg dd1390a6c2 initctl: monitor and condition control over the bus
The bus can already answer questions and change services, so give
initctl the two things it still did another way: watching signals as
they happen, and getting or setting user conditions.

The dbus tests move with it, split by area rather than one file that
grew every time the library did.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 09:28:14 +02:00
Joachim Wiberg 0a269f3298 libink: a brokerless D-Bus implementation for Finit
Finit had no way to answer the question every service manager gets
asked: what is running, and change it.  D-Bus is how the rest of
userspace asks, but linking libdbus, sd-bus or GIO into PID 1 buys a
dependency, an allocator and a main loop we do not control.

So libink: the wire format, an object tree, and a bus of Finit's own
at /run/finit/bus, gated like INIT_SOCKET.  It speaks the standard
org.freedesktop.DBus, .Peer, .Introspectable interfaces, and Finit's
own Manager1, Service1 and Cond1 on top.  Methods that change
something are marked privileged and answered only for a caller the
kernel vouched for, via SO_PEERCRED.

Server and client both, since initctl is the first thing that needs
to talk to it, and its Start/Stop/Restart/Reload now go over the bus
rather than the legacy socket.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-13 09:28:14 +02:00
Joachim Wiberg ae9a24f44f Merge pull request #498 from finit-project/misc-cgroup
Misc cgroup fixes

Fixes #497
2026-08-13 08:15:59 +02:00
Joachim Wiberg 1843c24cda cgroup: move PID 1 out of the init/ group
A cgroup holding processes cannot enable controllers for its children,
so init/ had to stay a leaf.  The hotplug helpers 10-hotplug.conf.in
places there ended up in groups where cpu.weight and friends could
never be set.

Keeping PID 1 in the root cgroup makes init/ a domain like the others.
It also unbreaks lxc-based runtimes: liblxc bases the container tree on
PID 1's cgroup and only special-cases systemd's init.scope/, so under
Finit it landed containers in init/, with no controllers available.

Issue #497

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-12 07:18:40 +02:00
Joachim Wiberg a35ed8f72e cgroups: non-root service cannot create child cgroups
The kernel delegation docs require write access on the directory so
the delegatee can mkdir() children.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-12 06:53:14 +02:00
Joachim Wiberg d2781ef655 Merge pull request #495 from aanderse/cond/bufferlen 2026-08-06 07:39:33 +02:00
Aaron Andersen 287a385e63 cond: fixup path buffer size to fit the longest condition name
cond_path() and cond_checkpath() built the /run/finit/cond/ path in a
MAX_ARG_LEN buffer, so condition names longer than ~40 chars were
truncated.
2026-08-05 18:51:27 -04:00
Joachim Wiberg 6b77a16f8b conf: add missing passenv to tty blocks
The line-based format has had the flag since v4.4 (issue #286), where
it prepends -p to the built-in getty, which turns it into login -p and
passes the environment on.  The block format was written from the three
documented tty variants and the flags listed in the tty documentation,
and passenv was in neither, so it was left out.  Converting a tty line
that used it therefore lost it, with nothing said.

It only reaches the built-in getty.  An external getty is handed its
arguments through command, so there is nowhere to put a -p, and the
setting is refused with a warning rather than quietly ignored.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-05 17:59:11 +02:00
Joachim Wiberg 96c5b4b031 doc: document the provides setting
The migration guide told anyone holding the repeated-stanza idiom for a
per-platform service to split the variants across files or stay on the
line-based format, because a block title is an identity and the
variants have to share one barrier.  provides is the answer, so the
guide converts that shape now instead of routing around it, and the
header of 10-hotplug.conf.in no longer points at the workaround.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-05 17:59:09 +02:00
Joachim Wiberg ffb1db7d2d conf: add provides, additional conditions a service supplies
A block title is a service identity, so two variants of one service
gated differently per platform cannot share a title.  They do need to
share the barrier condition downstream services wait for, which until
now was spelled by the identity alone and so could not be shared:

    service syslogd:udev {
        if         = "udevd"
        conditions = { "run/udevadm:5/success" }
        provides   = "pid/syslogd"
        command    = "-syslogd -F"
    }

Any namespace is allowed, since the point is publishing a name that
existing configurations already wait on.  A claim on a condition that
is already owned is dropped with a warning naming the owner, and the
service still registers: the overlap is a configuration bug, and an
init system is more useful degraded than refusing to boot.  A real
identity outranks a claim, pid/<ident> is how Finit tracks its own
services, so it is not up for grabs.

Claims are dropped before each reload re-reads the .conf files.  Doing
it per service as it re-registers is not enough, since services are
read in file order and one re-registering would lose to a claim
another had not dropped yet, flipping the owner on every reload.

initctl cond dump asked who owned a condition only for the pid/
namespace and printed 'static' for usr/, which now hides a provider.
It asks first and falls back to what the namespace implies.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-05 17:58:43 +02:00
Joachim Wiberg 22fbb408eb conf: route service conditions through one owner primitive
A service's condition was computed with mkcond() at each of the six
sites that assert or clear it, and svc_find_by_cond() reimplemented
the reverse lookup a seventh time.  maybe_clear_cond() had its own
scan for another service supplying the same condition.

svc_cond_owner() answers who owns a condition, svc_cond_nth() walks
the conditions a service owns, and svc_cond_set()/svc_cond_clear()
apply to all of them.  svc_find_by_cond() becomes a wrapper, and
maybe_clear_cond() keeps its rule per condition rather than for the
one it used to compute.

The provides[] storage lands here unused, since svc_cond_nth() reads
num_provides.  Nothing sets it yet, so a service still owns exactly
its own pid/<ident> and there is no functional change.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-05 17:57:57 +02:00
Joachim Wiberg 4299ce8839 doc: document command candidates and the duplicate title rule
The migration guide covered a stanza at a time, which is the wrong
shape for the two idioms that repeated a whole stanza.  One of them,
several candidate binaries for one service, is now a command list.
The other, one service gated differently per platform, has no block
equivalent: those blocks share an identity because they share the
barrier condition downstream services wait for, so they cannot be
given separate titles.  For that one the guide says to split the
variants across files, or leave that file in the line-based format,
which Finit still reads.

The udevd example in services.md taught the merge-broken form, and
system/10-hotplug.conf.in pointed readers at it for their syslogd.

Also lists libConfuse among the build dependencies.  It has been
mandatory since the new .conf format landed, and build.md still said
two libraries.  And corrects the note on variable expansion: it is
${VAR} that libconfuse expands when the file is read, with
${VAR:-default} supported.  A plain $VAR reaches the service, which is
what makes `command = "syslogd -F $SYSLOGD_ARGS"` work with envfile.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-05 17:57:56 +02:00
Joachim Wiberg cfae4a0e10 conf: support list of candidate commands for services
A daemon known under more than one name had to be declared once per
name, each stanza carrying nowarn so the ones that were not installed
were skipped quietly.  That leaned on the line-based format having no
titles.  With a title as the service identity the repetition is no
longer available, so the candidates move inside the block:

    command = { "/lib/systemd/systemd-udevd", "-udevd" }

Finit starts the first one whose binary resolves.  A candidate that is
not installed is expected here, so nothing is logged for the ones that
lose, and only the winner is looked up again by service_register().
The leading '-' keeps its meaning and is read from the candidate that
wins, or from the last one when none resolve.

libconfuse accepts a bare string for a list option, so the common
`command = "prog args"` is unchanged, and whichp() already skips any
arguments to the command, so the candidate goes to it as written.

tty blocks take the same list.  Their command is the getty to run, and
it has the same reason to name alternatives.  Both block types now
share svc_command(), which also puts the leading '-' rule back in one
place: tty_translate() had its own copy.  A tty needs no command at
all, it may name a device or notty instead, so svc_command() returns
NULL for an empty section and each caller decides whether that is an
error.

system/10-hotplug.conf.in returns to one udevd block, which is what it
meant all along.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-05 17:57:27 +02:00
Joachim Wiberg 516ee41494 conf: reject .conf files that declare duplicate block titles
The block title is the service identity, and libconfuse merges two
sections that share one, without a word.  Two blocks titled the same
in one file therefore loaded as a single service holding a mix of both
declarations, with scalars taken from the last block and lists reset
by it.

system/10-hotplug.conf.in is written this way: two udevd blocks, one
per candidate binary, the way the line-based format spelled a
fallback.  Only the second survived the merge, so a system that has
/lib/systemd/systemd-udevd but no udevd got no udevd service at all,
and the whole `if = "udevd"` chain behind it went with it.

CFGF_NO_TITLE_DUPES turns the merge into a parse error naming the file
and the title, and the file is then rejected as a whole.  The same
title in another file is untouched, that is how an administrator
overrides a system .conf.

Format detection had to stop agreeing with it.  is_new_format() probes
by parsing, so a duplicate title made it answer "not block format" and
conf_parse_file() handed the file to the legacy parser, whose errors
buried the real message.  The probe now clears the flag on its own
copy of the option array, keeping the verdict syntactic.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-05 17:56:40 +02:00
Joachim Wiberg c3c41f113e conf: include signal.h for str2sig() and SIGHUP
The reload-signal translation calls str2sig() and compares against
SIGHUP, but conf.c never included signal.h.  glibc pulls it in
transitively, so the omission went unnoticed until a cross-compile
against uClibc-ng in Buildroot failed to build.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-04 10:04:20 +02:00
Joachim Wiberg 1099d663ab conf: escape runtime strings in generated .conf files
The runparts directory and the dbus pidfile and daemon paths are
embedded in double-quoted values of the generated block files.  A
literal quote in either ends the value early and libconfuse rejects
the whole file, and a backslash is read as an escape sequence,
silently mangling the path.  The legacy one-liners had no quoting, so
neither failure existed before the block conversion.

conf_escape() doubles backslashes and escapes quotes; verified by
round-tripping hostile paths through cfg_parse_buf().

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-01 11:25:40 +02:00
Joachim Wiberg 2c6547635a conf: drop stale draft of the pidfile translation comment
An earlier revision of the comment survived right above its final
form.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-01 11:25:40 +02:00
Joachim Wiberg 9360c8e9b9 conf: generate runtime .conf files in the block format
Finit's own generated services -- watchdogd, keventd, runparts, and
the dbus plugin -- still went through conf_save_service() as legacy
one-liners, so `initctl show keventd` taught the old format on a
system otherwise converted to the new one.

conf_save_service() now takes the block title and a printf-style body
and writes the file itself:

    # Generated by finit:conf_save_service()
    service keventd {
            description = "Finit kernel event daemon"
            runlevel    = "S12345789"
            notify      = "none"
            cgroup init {}
            command     = "/libexec/finit/keventd"
    }

vfprintf() into the file also removes the fixed-size staging buffers
in the callers, where a long dbus pidfile path could truncate inside
a quoted string and take the whole generated file with it.

Semantics preserved: watch-only pid:! maps to pidfile without
pidfile-create, the watchdog keeps its watchdog:finit identity, and
log:console becomes log { file = "/dev/console" }.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-01 11:25:39 +02:00
Joachim Wiberg 5aa7daf708 doc: fix mkdocs build warnings
The README.md symlinks exist for GitHub browsing and collide with
index.md when mkdocs renders both; exclude them like TODO.md.

Two links pointed at anchors that never existed: features.md has bold
captions rather than headings, so "Automatic Reload" gets an explicit
attr_list anchor for the link from the front page, and the TTY link
now spells the actual heading, controlling-tty-for-services.

mkdocs build is silent after this.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-01 11:25:39 +02:00
Joachim Wiberg 057ded451f doc: add a syntax migration guide
The new-format reference describes the block format on its own terms,
which is the wrong lookup direction for someone holding a legacy
one-liner.  Aaron migrated Finix OS from the PR description, proving
the need for a token-in, key-out mapping in the user guide.

One table per part of a stanza, worked conversions for the shapes
that changed structurally -- cgroup selection and the three tty
variants -- and the dropped tokens listed with their replacements.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-08-01 11:25:38 +02:00
Joachim Wiberg 3062f49370 Merge pull request #493 from finit-project/file-format
New file format
2026-08-01 10:08:19 +02:00
Joachim Wiberg 301992b8d8 doc: bare-ID services have no block equivalent
The line-based format accepts `service :80 ...`, deriving the name
from the command basename.  The block format has no counterpart, the
title carries both name and ID.  Implied by the format description,
but anyone converting such a line deserves to find it written down.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-31 11:55:38 +02:00
Joachim Wiberg cdf8ec932a .github: fix another stale link and refresh
Fix stale link to kernel coding style and refresh the contributing guidelines.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-31 11:39:12 +02:00
Joachim Wiberg c6802b5ab3 Merge pull request #494 from henziger/patch-1
doc: Update link in CONTRIBUTING.md
2026-07-31 11:32:44 +02:00
Eric Henziger 652479d8fa doc: Update link to Pro Git commit guidelines 2026-07-31 11:00:20 +02:00
Joachim Wiberg 153f719e33 .github: install libconfuse-dev, mandatory since the new .conf format
The workflows install libuev and libite from source and everything
else from apt, but never libconfuse, so every build job on this
branch dies in configure:

    checking for libconfuse >= 3.3... no

Ubuntu ships libconfuse 3.3 with the static library included, which
covers both the static and regular builds.  Staying on 3.3 in CI is
deliberate: it exercises the fallback paths marked
"XXX: Workaround for libConfuse <3.4" that a from-source 3.4 would
leave untested.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:31:46 +02:00
Joachim Wiberg 6b03a1ec8f conf: adopt the systemd semantics for per-service directories
Aaron Andersen points out in the #492 discussion that the *Directory
settings carry more contract than create-and-chown: per-directory
modes, specific ownership rules, and cleanup toggles.  Without them
config-dir was chowned to the service user, which systemd never does,
an existing directory with drifted ownership was left wrong, and the
runtime directory could not survive a restart.

Now matching systemd.exec(5), and where the man page is vague, the
code in setup_exec_directory():

  - each directory takes a matching -mode key, octal with the leading
    zero, default 0755.  The mode of the named directory is locked
    down again on every start, also when it already exists
  - config-dir is created but never chowned
  - the contents of an existing directory are left alone as long as
    the owner is right; on drift everything under it is chowned back
  - runtime-dir-preserve = no | restart | yes maps
    RuntimeDirectoryPreserve=.  A service still qualified to run when
    the runtime directory would be removed is restarting, not
    stopping, which is what svc_enabled() answers

The dir mechanics move to mksubsysd(), taking resolved ids, with
mksubsys() reduced to a name-resolving wrapper for the dbus plugin.
The child resolves uid/gid once for both directory setup and
privilege drop.

The symlink form, RuntimeDirectory=foo:bar, is not adopted.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:23:38 +02:00
Joachim Wiberg f0d7257374 Fix #492: add per-service directories, systemd RuntimeDirectory style
A service that drops privileges cannot create its own PID file in
/run, root owns it.  Finit can create the file with pidfile-create,
but the daemon still cannot touch it to confirm a SIGHUP.

Five new settings, block format only: runtime-dir, state-dir,
cache-dir, logs-dir, and config-dir.  The value is a directory name,
resolved under /run, /var/lib, /var/cache, /var/log, and /etc,
respectively.  The directory is created before the service starts,
mode 0755 owned by user/group, and the full path is exported to the
process as RUNTIME_DIRECTORY, STATE_DIRECTORY, CACHE_DIRECTORY,
LOGS_DIRECTORY, and CONFIGURATION_DIRECTORY.  Mode and ownership are
asserted at creation only, a daemon may tighten them afterwards.

The runtime directory is removed when the unit stops, after any
exec-stop-post script, like systemd with RuntimeDirectoryPreserve=no.
A completed run/task counts as stopped unless remain-after-exit keeps
it up.  The other four persist across restarts.

These are the first settings with no legacy token: they are validated
by service_set_dir() and stored on the svc that service_register()
now returns.  systemd accepts a list of directories per setting; this
is a single name for now, widening later is compatible since
libconfuse accepts a bare value for a list option.

The test sysroot gains libnss_files.so.2, which ldd cannot see, glibc
dlopen()s it.  Without it getpwnam() fails inside the chroot, so
user/group settings never resolved and directory ownership could not
be tested.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:23:37 +02:00
Joachim Wiberg 7f8a64dd39 util: promote rmrf() from tmpfiles.c, fix silent mksubsys() skip
rmrf() is needed outside tmpfiles.c.  The move also deduplicates the
nftw callback: the contents-only removal used by tmpfiles 'D' entries
is now rmcontents(), sharing the callback with rmrf().

mksubsys() did nothing at all when the user could not be resolved, no
directory and no message, and callers had no way to tell.  Now the
directory is always created, ownership is best effort, and an unknown
user is warned about.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:23:37 +02:00
Joachim Wiberg 6615a248ee service: return the svc from service_register()
Settings that exist only in the block format have nowhere to go: the
legacy line cannot carry them, and service_register() returned an errno
that no caller ever read, so conf.c had no handle on the service it just
created.  Return the svc instead, NULL with errno set on failure, errno
zero when a block is skipped on purpose.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:23:36 +02:00
Joachim Wiberg be28436c2d conf: drop the angle brackets from if, infer from the value
The block format spells conditions as bare strings everywhere else, so
requiring `if = "<usr/foo>"` left one sigil behind, carried over from
the line-based `if:` token.  A namespace separator already tells the two
apart: a value with a '/' is a condition, anything else is a service
name.

svc_ifthen() picks its mode from the start of the statement and applies
it to the whole, so a statement naming both kinds cannot be evaluated.
That is now an error, as are the old angle brackets, and either one
skips the block:

    /etc/finit.conf: mixed: if: cannot mix a service name with a
    condition in 'anchor,usr/enable-me', a statement must be all of
    one kind, skipping

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:23:36 +02:00
Joachim Wiberg c584795202 doc: fix back-references and stale claims left by the conversion
Reference sections kept pointing at the line-based format they no longer
document.  `sysv` and `task` sent the reader to Services for "<COND>",
the cgroups chapter opened by listing three legacy directives and then
explained further down that only two of them exist here, and the logging
chapter still gave "log:prio:facility.level,tag:ident" as the full
syntax.

Some claims were wrong independent of the format:

  - a sysv is a supervised daemon, grouped with service in
    SVC_TYPE_DAEMON, not a variation on task
  - restart-max has no upper bound of 255, or any other
  - the built-in rescue fallback runs in 12345789, not 12345
  - conditional loading quotes system/10-hotplug.conf, not
    system/hotplug.conf
  - the key spells conflicts, not conflict
  - the built-in getty no longer wants TERM last, it is a key

`if` takes either a service name or, in angle brackets, a condition,
decided in svc_ifthen().  Only the examples showed this, so it is now
said.

Terminology follows the split index.md already draws: a block is the new
format, a stanza the line-based one.

src/rescue.conf was still line-based, missed because it sits in src/
rather than system/ or contrib/.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:23:35 +02:00
Joachim Wiberg ddda905487 service: warn when capabilities cannot take effect
An ambient capability only reaches the effective set when euid is
non-zero, so a service that pairs `capabilities = { "^cap_..." }` with a
root user gets none of the restriction it asks for, and keeps the full
root set instead.  Finit read the list, applied it, and said nothing.  A
build without libcap dropped the list on the floor just as quietly.

Both now warn, naming the service:

    nginx: ambient capabilities ('^') have no effect as root, use a
    non-root user, or '%' and '!' entries

The ambient entries are read back from the parsed IAB value rather than
matched in the text, so inheritable ('%') and bounding ('!') entries stay
silent -- those work fine as root.

The warning repeats when the .conf files are re-read on runlevel change,
as parse warnings here already do.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:23:35 +02:00
Joachim Wiberg 00794d41bc doc: convert the remaining legacy syntax
The block conversion changed the bodies of the reference sections but
left every "**Syntax:**" header spelling the line-based format, so each
page opened by teaching the format it then stopped using.  Six files
were missed entirely: runparts, files, capabilities, requirements,
runlevels, and switchroot.

runparts had no block spelling written down anywhere, though the parser
has read `runparts`, `runparts-progress`, and `runparts-sysv` all along.

tty gains a table per variant.  Its three syntax lines carried nine
positional fields between them, which no longer describes anything the
parser accepts.

Fixes #148

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:23:13 +02:00
Joachim Wiberg b44a5ff2f8 contrib, system: convert the shipped .conf files to the block format
The examples people copy from were still written in the line-based
format, so the block format was documented but nowhere demonstrated.

Two names in contrib were accidents of the old format, where the
service name falls out of the command basename: the Alpine and Void
keymap task was called zcat, and Debian's console/keyboard setup tasks
carried a .sh suffix.  They now carry the name their file implies.
Nothing referenced the old names.

The mdevd coldplug path keeps the name it has always had.  Its legacy
line spelled the name inside the cgroup argument, where it names the
cgroup leaf and not the service, so the barrier condition really is
<run/mdevd-coldplug/success> and not the <run/coldplug/success> the
comment above it promises.  Converted as-is so boot ordering does not
change; the discrepancy is now written down where it happens.

A list may not contain comments, the lexer sees the entries after the
'#' regardless:

    modules = {
    #	"fbcon",
    	"softdog"
    }

so the commented-out module candidates sit above the list instead.

setup-sysroot.sh removes 10-hotplug.conf from the test sysroot, so that
file is covered by parsing only, not by make check.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:31 +02:00
Joachim Wiberg b9ad9bcb21 doc: convert the documentation to the block format
The syntax overview no longer describes a line-based format, since that
is not what the rest of the documentation shows.  It now covers the
grammar, the two naming conventions, the nine aliases, and the leading
'-' on a path, and it says plainly that both formats are still read and
told apart per file by content.  Without that, a reader with an
existing configuration is left wondering what happened to it.

service-opts.md was a list of modifiers to place between a directive
and its command, so it needed rewriting rather than translating: there
are no positions left to describe.  It is now grouped by what the
settings do.

conditions.md needed correcting.  It presented '!' as a condition
prefix alongside '~'.  It is neither a condition nor a negation, it is
a flag on the block that means one thing on a service and another on a
run or task, so it is spelled reload-signal and required here, and the
page maps the old form to both.

Two things the pages claimed are not true.  The kill delay range is
1-300, not 1-60, and stop and reload scripts are no longer run without
a timeout.

ChangeLog.md keeps its line-based examples.  Those sit in historical
release entries, and rewriting them in a syntax that did not exist at
the time would misdate the format.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:31 +02:00
Joachim Wiberg ecac1e58e3 conf: make cgroup delegate and leaf name first-class keys
parse_cgroup() takes two arguments that are not cgroupfs files: the
leaf directory to place the service in, and whether to hand the subtree
over to it.  The block format could express neither.  'name' happened
to work, because a free-form key is emitted as name:VALUE and that is
what the parser looks for, but 'delegate' came out as delegate:true and
was filed as a cgroup setting, so it silently did nothing.

Declare both, and emit delegate as the bare flag the parser expects.
Neither means anything on a top-level group definition, so say so there
rather than emitting something that would be written to cgroupfs.

    service podman {
        cgroup containers { name = "podman"  delegate = true }
        ...
    }

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:30 +02:00
Joachim Wiberg ddaef0600f doc: rewrite sample.conf in the block format
This is what 'initctl create' and 'initctl edit -c' put in front of a
user writing their first .conf file, so it is also the whole of the
"initctl emits the new format" work: neither command generates syntax,
they copy this file and open an editor on it.

The ASCII diagram naming eight positional fields goes with it.  A
block has no positions to explain.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:30 +02:00
Joachim Wiberg 13d58ace9f conf: rename remain and manual for what they actually do
Both keys prompt the question they should be answering.

'remain' decides whether a finished run or task keeps existing: without
it the entry is pruned, so the work re-runs on every runlevel entry,
initctl cannot see it, and its post script never fires.  With it the
entry stays, is not re-run, and gets a teardown when stopped or when it
leaves its runlevels.  That is systemd's RemainAfterExit, and 'remain'
is that name with the informative half cut off.

'manual' says how a service is started but not that it is about
starting at all.

    remain -> remain-after-exit
    manual -> manual-start

Both keep their old spelling as an alias, which they qualify for twice
over, as abbreviations of the canonical name and as the legacy
spellings.

While here, give sec_getbool() the alias argument its string and list
counterparts already take.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:29 +02:00
Joachim Wiberg f48f8aacec conf: replace the '!' condition prefix with reload-signal and required
A condition list could be led by '!', which is not a condition and not
a negation.  It is a flag on the block, and it means two unrelated
things depending on which block it sits in: a service or sysv does not
handle SIGHUP and must be restarted to reload, while a run or task
must not hold up bootstrap.  Writing '<!>' with no condition at all is
legal, which gives away that it was never an operator.

Give each meaning its own key, valid only where it applies:

    service foo { reload-signal = "none" }   # restart to reload
    task    bar { required      = false  }   # do not hold up bootstrap

Using either on a block type it does not apply to warns, as does a '!'
left in a conditions list.  Both still translate to that same '!',
which is all a legacy line can carry, so reload-signal takes SIGHUP or
none for now; str2sig() already accepts any case and an optional SIG
prefix.

This also clears the way for the conditions list to grow real
operators, '+' and '-' for asserted and deasserted, without '!'
sitting among them meaning something else entirely.

The '~' prefix stays.  It belongs to the list: it marks a dependency
whose reload should propagate here.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:29 +02:00
Joachim Wiberg 8a541acd36 conf: settle the block format's key names and value shapes
The initial implementation was done using a naive translator of the
legacy one-liner format key by key, so it inherited encodings that the
block format exists to remove: a timeout packed into a script path, a
small comma-and-colon language inside the log string, sigils standing in
for booleans, and a log key (services) carrying three (!) types.

Settled naming against systemd, OpenRC, FreeBSD rc.subr, s6 and SMF.
Match systemd's semantics, not its naming.

    pid          -> pidfile, plus pidfile-create for the rare case
                    where Finit writes the file rather than the daemon
    environment  -> envfile, since it names a file to source, and the
                    top-level environment {} block sets variables
    pre/post/... -> exec-start-pre, exec-start-ready, exec-stop,
                    exec-stop-post, exec-reload, exec-cleanup, each
                    with its own -timeout instead of "SEC,script"
    halt, kill   -> stop-signal, stop-timeout
    restart      -> restart for the policy, restart-max for the count
    log          -> a block with file, priority and identity, where
                    /dev/null and /dev/console are spelled as paths
    group        -> group and extra-groups, no longer positional
    nowarn       -> a leading - on command, as on envfile

List-valued keys take plural names.  Aliases are desc, cond, mod,
caps, env, halt and kill; an alias may abbreviate the canonical name
or preserve a legacy spelling, nothing else.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:28 +02:00
Joachim Wiberg 8a3d55b416 test: refuse to run against a stale sysroot binary
'make check' refreshes the sysroot through the setup-chroot rule, but
running a test script by hand does not, so the test exercises whichever
finit was installed last and reports on code that is no longer there.
Both a passing and a failing run are then meaningless, and nothing says
so.

Compare the built binary against the installed one at startup and fail
with the command that fixes it.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:28 +02:00
Joachim Wiberg 53cb2b7bdb service: give stop and reload scripts their own timeout
Both were bounded by killdelay, the delay between the stop signal and
SIGKILL, because service_run_script() had nothing else to reach for.
That conflates two things: how long the daemon may take to die, and
how long its stop script may run.

Give each hook a timeout of its own, defaulting to killdelay when
unset, so the existing behaviour is what you get until you ask for
something else.  parse_script() already falls back that way for the
hooks that had one.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:27 +02:00
Joachim Wiberg 2a5190ecff service: do not let a script timeout take PID 1 with it
A stop: or reload: script written with a timeout killed Finit at
config load:

    service stop:5,/bin/true service.sh -- Boom

parse_script() takes the timeout as a pointer and the caller decides
whether it wants one.  However, both stop: and reload: scripts so far
have no timeout, i.e., NULL.  Guard the branch that reads a leading
number.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:27 +02:00
Joachim Wiberg 0721b0fec2 util: one whole-file reader for all tools
Three private ones had grown: fnread() in util.c, flen() behind
pid_cmdline()/pid_cgroup() in cgutil.c, and conf_read_template() in
conf.c.  Two of them were also wrong in ways the others were not.

fnread() formatted the path into a char[256] and stat()ed it before
opening, so a longer path was silently truncated and then read from
whichever file the truncation happened to name, and the size could
change between the look and the read.  flen() existed because neither
of those approaches works on procfs at all, where stat() reports zero
and the only way to learn the size is to read to EOF.

Add fslurp() to util.[ch], which every tool already links.  It opens
first and sizes the fd it holds, treats st_size as a hint, and reads
until EOF, so procfs and regular files take the same path.  Paths are
formatted by libite's vfopenf(), which allocates to fit.  Callers that
need the byte count, /proc/PID/cmdline embeds NUL, ask for it.

fnread() keeps its signature and becomes a bounded copy out of the
result, so its one caller is unaffected.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:27 +02:00
Joachim Wiberg f1393ff8a1 conf: instantiate %i templates for the block format
A template in the block format registered garbage.  conf_parse_file()
routed every file with an '@' in its name straight to the legacy
parser, which read the block line by line: the section header became a
service whose command was the section title, and each key = value line
below it became an environment variable.

    service serv:%i { ... }   ->  service 'serv:eth0' with argument '{'

Substitute %i over the whole file before parsing instead, so format
detection and both parsers see finished text.  A bare name@.conf is
still skipped, it is the template rather than an instance of one.

The legacy parser no longer opens the file or substitutes per line, it
is handed the instantiated buffer, so the template convention now has
one implementation instead of two.  conf_is_template() applies
basenm(), a directory with an '@' in its name is not a template.

libconfuse cannot name a buffer it parses before 3.4, so a typo in a
template would be reported against "[buf]".  Parse through fmemopen()
with the file name preset until the floor moves.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:26 +02:00
Joachim Wiberg 47a18140c1 conf: cleanup and relocation of common parsing functions and helpers
- legacy.[ch]: strictly legacy .conf parser boilerplate only
 - conf.[ch]: .conf parser and generic configuration functionality

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:26 +02:00
Joachim Wiberg c20d64587c conf: relocate global state from legacy parser
Relocate process-wide global variables from legacy parser that ended up
there because it used to be conf.c, but which is now now frozen at the
4.x feature set.  Each variable is moved to their respective "owner".

Give cgroup_current[] and cgroup_settings_current[] named bounds.  Their
extern declarations were unsized, so sizeof() on them stopped compiling
once the definitions moved to another translation unit.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:25 +02:00
Joachim Wiberg 3b866c95e0 conf: add libconfuse block format alongside the one-liner format
The one-liner format has grown crowded and very wide, and every new
service option makes it worse.

Add a second, block-based format, parsed with libconfuse:

    service sshd {
        description = "OpenSSH daemon"
        runlevel    = "2345"
        command     = "/usr/sbin/sshd -D $SSHD_OPTS"
    }

Both formats keep the .conf extension and are detected per file by
content.  Try-parse strictly with libconfuse; on a parse error,
re-parse leniently to tell a block file with a typo from a one-liner
file.  Only a one-liner file reaches the legacy parser, a typo is
reported with its file and line.

Each block is translated to the canonical one-liner and registered
through the existing entry points, so the two formats cannot drift.

The one-liner parser is frozen at the 4.x feature set, new options
land only in the block schema.  libconfuse 3.3 or later is required,
CFGF_KEYSTRVAL does not exist before it.

Covers service, task, run, sysv and tty blocks, the static directives,
and the cgroup, rlimit, set and log blocks.  Templating and the
documentation rewrite are still to come.

The regression test covers translation of a service block to the
one-liner, a block-format /etc/finit.conf booting with set {} applied
at bootstrap, both formats side by side, and rejection of a typo at
block and at root level.

A rejected file must not fall through to the legacy parser, which
registers a bogus unstartable service per line.  assert_num_children
cannot see that, the bogus service has no children either, so the
check is assert_num_services.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:25 +02:00
Joachim Wiberg fc74f87986 conf: rename conf.c to legacy.c
The one-liner parser is about to be joined by a second, block-based
format.  Give it a name that says which of the two it implements,
before any content changes make the diff hard to follow.

No functional change.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:24 +02:00
Joachim Wiberg ee5038e7af test: reap the watchdog's sleep in wdkill()
Every test left a stray `sleep 300` behind, reparented to PID 1, where
it lingered for up to five minutes after the test had finished.

wdstart() runs the watchdog in a subshell, so $! is the pid of the
subshell, not of the sleep it forks.  wdkill() killed the subshell and
orphaned the sleep.

Kill the child first, killing the subshell puts the sleep beyond the
reach of pkill -P.  Neither kill is sure to match, and wdkill() runs
from the EXIT trap under set -e, so both must tolerate failure.  Also
return early when wdpid is unset, for failures before wdstart() runs.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:24 +02:00
Joachim Wiberg ad750a13c3 Bump version for 5.0 release cycle
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 15:21:23 +02:00
Joachim Wiberg b56ffc155e doc: version the published User Guide by major release
Finit 5.0 changes the .conf syntax, which has been essentially
unchanged since 1.x.  The published docs track master, so when 5.x
lands, 4.x users lose their reference.

Publish the site under a per-major directory, /4.x/ for now, with
the Material version selector to switch between them.  The selector
only needs mike's file layout -- a versions.json at the site root --
which the deploy job now generates from the version directories in
the pages repo, so mike itself is not needed.

The major comes from AC_INIT and the future 4.x maintenance branch
is already in the workflow triggers, so once 5.0 is on master, doc
fixes on the 4.x branch keep /4.x/ updated.  A root index.html
redirects to the newest version, and a 404.html rewrites
pre-versioned deep links so old bookmarks and search hits land in
the right place.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 12:27:27 +02:00
Joachim Wiberg 4596e27eb0 doc: simplify and add some polish to the User Guide
Both the Finit project and Infix use the same MkDocs Material setup, and
in the latter the User Guide has picked up a lot of polish that never
made it back here: a single sidebar with section indexes instead of
tabs, footnote tooltips, more pymdownx markup, image zoom tuning, and no
generator advert in the footer.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-07-30 12:22:17 +02:00
Joachim Wiberg a3eac3a85e Merge pull request #489 from finit-project/stale-pidfile
Handle stale pid file for crashing daemons
2026-05-12 10:40:01 +02:00
Joachim Wiberg 7d09e34b80 doc: document stale pidfile cleanup and new restart log
* src/pid.c: note the stale-pidfile-cleanup exception to the
  documented "Finit does not touch pid:! pidfiles" rule.
* doc/config/services.md: add a user-facing paragraph on the same.
* doc/ChangeLog.md: add Unreleased section covering this PR --
  stale pidfile cleanup, restart log with signal name and core
  dump flag, and the SIGUNKOWN typo fix.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-05-12 10:11:50 +02:00
Joachim Wiberg 3febc7d513 test: regression for stale pidfile cleanup after unclean exit
Cover the scenario fixed in "service: clean stale pidfile after
unclean daemon exit": a daemon with a pid:!/path config dies via
SIGKILL, leaving its pidfile behind, and the next instance must
still come up.

Add a 'serv -x' flag (refuse to start when the pidfile already
exists, dbus-style) so the test actually exercises the cleanup --
without it, plain 'serv' would happily overwrite the file and the
test would pass with or without the fix.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-05-12 10:11:49 +02:00
Joachim Wiberg 309ad1bea7 sig: spell SIGUNKNOWN correctly
The fallback for unknown signal numbers in sig_name() returned the
misspelled "SIGUNKOWN".  Now that this string surfaces in user-
facing logs ("killed by …"), fix the typo.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-05-11 21:09:02 +02:00
Joachim Wiberg 30f2ca3b2e service: log signal name and core dumps in death message
Replace the bare signal number ("by signal: 9") with the symbolic
name ("killed by SIGKILL") and annotate when the kernel wrote a
core:("killed by SIGSEGV, core dumped").  Makes the restart line
self-explanatory and gives operators a strong breadcrumb when a
daemon dies unexpectedly.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-05-11 21:09:01 +02:00
Joachim Wiberg 4a53f610cd service: clean stale pidfile after unclean daemon exit
With `pid:!/path` Finit does not manage the file -- the daemon
creates it on start and removes it on graceful exit.  If the daemon
dies before cleanup (SIGKILL, OOM, segfault, exit during startup)
the file lingers and can block the next instance from starting,
e.g. dbus-daemon refuses with EEXIST and the restart loop fails.

Remove the file when it still names the just-reaped PID and that
PID is no longer alive (the liveness check guards against reuse).
Called from service_cleanup(), and from service_monitor()'s
forking+starting branch where cleanup was previously skipped.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-05-11 21:08:51 +02:00
Joachim Wiberg e74dff96ba Update ChangeLog and bump version for v4.17 GA
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-04-28 10:09:16 +02:00
Joachim Wiberg 9ed4407b20 Follow-up to 96f944b: fix regression in status output at boot
status() returns a pointer to a single static buffer, so calling it twice
in the same cprintf() argument list — status(3) and status(rc) — causes
one to overwrite the other before the format string is rendered.  When
status(3) wins, the line shows [ ⋯  ] instead of [ OK ].  Fix by copying
status(rc) into a local buffer before calling status(3).

Also drop the delline() calls added to print() — that macro writes \033[2K
to buffered stdout while cprintf() writes unbuffered to stderr, so the
erase sequences can arrive out of order.  The \r\e[K already present in
the cprintf format strings makes them redundant anyway.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-04-28 10:02:21 +02:00
Joachim Wiberg eb913e8bf2 initramfs: fix Coverity defects
Check return value of remove() in delete_cb() and log failures via
dbg(), CID 909395

Replace stat() calls with open(O_DIRECTORY)+ fstat() for newroot and "/"
checks.  Eliminates the check-then-use race and lets O_DIRECTORY do the
isdir validation atomically, CID 909394

Drop the explicit close(0/1/2) before opening /dev/console.  dup2()
closes the old targets itself, so open() returns a fd > STDERR_FILENO
that can always be closed unconditionally, removing the conditional
guard, CID 909393

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-04-26 22:11:19 +02:00
Joachim Wiberg 17d04779fb Update ChangeLog and bump version for v4.17-rc1
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-04-26 21:54:49 +02:00
Joachim Wiberg ab661423ba Merge pull request #488 from Ollie-Gutierrez/ctty-support
service: add optional controlling TTY via tty: stanza
2026-04-23 19:41:02 +02:00
Ollie Gutierrez 8e77ea68b2 service: add optional controlling TTY via tty: stanza 2026-04-23 15:08:57 +00:00
Joachim Wiberg b0e672bc9a Merge pull request #487 from aanderse/patch-1
tmpfiles.d: update /var/tmp mode
2026-04-18 07:34:37 +02:00
Aaron Andersen ad76e15949 tmpfiles.d: update /var/tmp mode
`/var/tmp` mode should match `/tmp`
2026-04-18 09:17:10 +09:00
Joachim Wiberg b8735cc893 Merge pull request #486 from aanderse/patch-1 2026-04-14 04:57:00 +02:00
Aaron Andersen c058b3f9ce plugins/plymouth.c: fixup email address 2026-04-14 09:03:19 +09:00
Joachim Wiberg 8f6c61606a Merge pull request #485 from finit-project/console-output-fixes
Atomic print and re-print desc on status, flush before reboot
2026-04-08 17:11:57 +02:00
Joachim Wiberg 96f944b816 Atomic print and re-print desc on status, flush before reboot
Refactor print() to emit description + final status in a single call to
cprintf(), preventing kernel messages from splitting the two parts.

For two-phase print(-1,...) + print_result() sequences used by, e.g.,
run_interactive, save the last description and re-print it before the
[ OK ] / [FAIL] output so the status is never left stranded on a blank
line when command output or kernel messages have scrolled away the
original description.

Finally, add print_exit() which drains the console output buffer with
tcdrain(2) and resets ANSI SGR attributes + cursor visibility before the
kernel takes back the console on reboot/halt, preventing escape code
leakage into bootloader or early-kernel output.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-04-07 11:36:19 +02:00
Joachim Wiberg a81530754b Merge pull request #483 from aanderse/plymouth
Add Plymouth boot splash plugin
2026-03-20 14:35:53 +01:00
Aaron Andersen 1518eb9466 Add Plymouth boot splash plugin
Manage the plymouthd lifecycle across boot, switch_root, and shutdown.
Activated by the "splash" kernel command line argument.
2026-03-20 09:18:18 -04:00
Joachim Wiberg 50b9e46e19 Merge pull request #484 from finit-project/rdep-stop-cond
Clear condition before stopping rdeps on reload
2026-03-19 06:53:40 +01:00
Joachim Wiberg c01faef99b service: clear condition before stopping rdeps on reload
When a service without SIGHUP reload support (noreload) is touched and
'initctl reload' is called, service_update_rdeps() correctly identifies
its reverse dependencies but only marks them dirty.  It does not clear
the service's condition, so when service_step_all() runs:

 - rdeps supporting SIGHUP hit the sm_in_reload() guard and break early,
   left running while their dependency is being killed.
 - rdeps without SIGHUP support may receive SIGTERM too late, after the
   dependency has already died and broken their connection, causing them
   to exit from RUNNING state and have their restart counter incremented.

Fix by calling cond_clear() on the service's condition immediately in
service_update_rdeps(), before service_step_all() runs.  cond_clear()
calls cond_update() which calls service_step() inline on all affected
services, which see COND_OFF and transition to STOPPING_STATE — all
before SIGTERM is ever sent to the dependency itself.

This mirrors the pattern already used in api.c:do_reload() for direct
'initctl reload <svc>' calls.

Fixes: avahi/mdns stop causing mdns-alias restart counter increment

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-03-19 06:37:50 +01:00
Joachim Wiberg 4867b7268b Merge pull request #482 from vazub/plugfix
Use explicit plugin names to prevent subtle macro processing bugs
2026-03-16 07:31:34 +01:00
bazub 662293e194 Use explicit plugin names to prevent subtle macro processing bugs 2026-03-06 20:36:23 +00:00
Joachim Wiberg 4ce810cc1f Merge pull request #481 from vazub/pathfix
Remove redundant global path var and fix memory corruption
2026-03-04 15:05:36 +01:00
bazub 184c079c08 Remove redundant global path var and fix memory corruption 2026-03-03 20:31:33 +00:00
Joachim Wiberg 17c6791c70 Update ChangeLog and bump version for v4.16 release
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-27 10:35:32 +01:00
Joachim Wiberg 42f24f5af5 Silence shutdown logging by default
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-27 10:34:38 +01:00
Joachim Wiberg cef5049bf0 Update ChangeLog and bump version for v4.16-rc1
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-24 05:36:45 +01:00
Joachim Wiberg ec251f1ce1 Merge pull request #480 from aanderse/master 2026-02-24 05:05:05 +01:00
Aaron Andersen 98ec30d308 tmpfiles: add --exclude-prefix and -E flags
Add support for the --exclude-prefix=PATH option to skip rules whose
path starts with the specified prefix.  The option can be specified
multiple times to exclude multiple path prefixes.

The -E flag is a shortcut for:

    --exclude-prefix=/dev --exclude-prefix=/proc \
    --exclude-prefix=/run --exclude-prefix=/sys

This is useful to avoid creating files below virtual or memory-backed
file system mount points.
2026-02-23 15:39:44 +00:00
Joachim Wiberg e1fe870aaf Merge pull request #478 from mattiaswal/fix-initial-devmon
devmon: assert condition immediately if device already exists

  1. udev fires early, creates device nodes in /dev/                                                                                                           
  2. Config is parsed later, calling devmon_add_cond() for each dev/ condition
  3. At this point the device already exists but the inotify event was missed
  4. The PR's fexist() check catches this — new node is added to the TAILQ and condition is immediately asserted

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-23 08:09:12 +01:00
Mattias Walström f04fce544d devmon: assert condition immediately if device already exists
The config parsing happens after udev triggers the initial event,
make sure to assert the condition if the device node exists when adding
it from configuration.

Signed-off-by: Mattias Walström <lazzer@gmail.com>
2026-02-23 07:30:08 +01:00
Joachim Wiberg 7aa9ed5982 Merge pull request #474 from finit-project/multi-depend
Multi depend fix
2026-02-22 21:05:31 +01:00
Joachim Wiberg a1a92a04bd Update ChangeLog
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-22 20:51:07 +01:00
Joachim Wiberg 804f655d87 test: fix depserv.sh after regression in 0b182c06
A service that is reloaded should not trigger dependants to be reloaded
unless the new <~cond> is used.  Which is reserve for tightly coupled
services.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-22 20:36:36 +01:00
Joachim Wiberg 2c78e7429a Only remove managed pidfiles in service cleanup
For SysV services with pid:!/path, the pidfile belongs to the service
itself and Finit shouldn't delete it.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-22 20:36:36 +01:00
Joachim Wiberg d77f0127b4 Don't disrupt dependents on reload of SIGHUP-capable service
This fixes a real bug where `initctl reload syslogd` unconditionally
clears syslogd's pid condition, causing all dependent services (dbus,
dnsmasq, etc.) to be stopped even though syslogd handles SIGHUP
gracefully and its PID/pidfile persist.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-22 20:36:35 +01:00
Joachim Wiberg 4253d0de25 Run service stop: and reload: scripts as non-blocking processes
A reload: script, like 'frrinit.sh reload' could potentially take a
while to finish, during which Finit would be blocked.  This change
reuses the service_script_add(), used for ready: scripts, to track
these background helpers.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-22 20:36:35 +01:00
Joachim Wiberg d47181a4a7 Silence cgroup warnings for non-existing PIDs
When the kernel manages to reap a child process before we've moved it to
its proper cgroup it will return ESRCH (No such process), we can safely
ignore such errors for short-lived processes.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-22 20:36:34 +01:00
Joachim Wiberg 8642007d0e Debug shutdown hangs and guard with timer watchdog
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-22 20:36:34 +01:00
Joachim Wiberg 96c74ed48b Reformat signaling log messages for readability
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-22 15:51:25 +01:00
Joachim Wiberg 03a14bcaa5 Add <~cond> condition modifier for tightly coupled services
Conditions in Finit are dependencies: if A is asserted, service B is
allowed to run.  When A goes through FLUX (e.g., upstream reloads),
dependents are PAUSED and then simply resumed when the condition is
reasserted -- this is the correct behavior for barrier-style deps
like <pid/syslogd>.

However, some setups have tightly coupled services where dependents
must be reloaded/restarted when an upstream service reloads, not just
resumed.  E.g., the FRR routing stack on Infix OS:

    netd <pid/mgmtd> ← zebra <!pid/netd> ← {staticd,ripd} <!pid/zebra>

When netd reloads (SIGHUP), zebra and its dependents must be restarted
to pick up the new configuration.

The new '~' condition prefix marks a dependency as flux-sensitive:

    service <!~pid/netd> name:zebra ...

When the upstream condition goes FLUX and returns to ON, the dependent
is reloaded (SIGHUP) or restarted (noreload '!') instead of merely
resumed.  Transitivity follows naturally through the condition chain.

Closes #416
Closes #476

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-22 15:51:25 +01:00
Joachim Wiberg 4bb2c33859 Dependents not restarted after SIGHUP reload of service
When 'initctl reload' is called after marking a service in a dependency
chain dirty, Finit fails to restart (unfreeze) affected services.

This patch updates the pidfile plugin to watch for IN_ATTRIB changes,
e.g. when a process uses utimensat() to update its pidfile, and adds
service_step_all() at end of reload cycle to guarantee convergence
after conditions are reasserted.

Issue #476

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-17 07:08:30 +01:00
Joachim Wiberg ab81272083 test: new regression test to verify multi-chain deps
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-13 07:30:10 +01:00
Joachim Wiberg 1fbf03bc1e Clear pid condition on service collection to fix stale deps
In a setup like this, when 'netd' is marked dirty and subsequently is
reloaded, e.g., using 'initctl reload', zebra is properly restarted,
but staticd isn't:

mgmtd <!> ← netd <pid/mgmtd> ← zebra <!pid/netd> ← staticd <!pid/zebra>

Finit must invalidate the condition of zebra to trigger a restart also
of staticd.  This to guard against daemons like zebra that may fail to
clean up their pidfiles.

Fixes #475

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-13 07:30:00 +01:00
Joachim Wiberg 92a2861b1c Merge pull request #473 from mattiaswal/fix-devmon 2026-02-10 20:36:37 +01:00
Joachim Wiberg 922714780a Merge pull request #472 from finit-project/reload-foo 2026-02-10 20:33:50 +01:00
Mattias Walström b206c3d655 devmon: re-evaluate device conditions on reconf
Device conditions tracked by devmon were lost on `initctl reload`
because the reconf path did not re-assert them.  Add devmon_reconf()
to iterate all tracked device nodes and set or clear their conditions
based on current device presence.

Signed-off-by: Mattias Walström <lazzer@gmail.com>
2026-02-10 20:09:35 +01:00
Mattias Walström 109d8826bd devmon: Fix deletion of conditions
Only compare the beginning of the condition, not the whole
condition name.

Signed-off-by: Mattias Walström <lazzer@gmail.com>
2026-02-10 19:49:13 +01:00
Joachim Wiberg 60478106eb Update ChangeLog with latest changes and features
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-10 16:01:53 +01:00
Joachim Wiberg 0de9cfb020 doc: Note per-service reload behavior for conditions
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-10 16:01:47 +01:00
Joachim Wiberg 0b182c063e test: Extend depserv test with per-service reload, fix slay race
Verify that 'initctl reload foo' properly triggers dependent
services by checking that bar gets a new PID after the reload.
Also change the second test case from service/foo/running to
service/foo/ready which is the actual condition set by pidfile.so.

Fix a race in slay where the target process could exit between
the PID lookup and kill -9, causing spurious test failures in
tight kill loops (e.g., start-kill-service.sh).

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-10 16:00:57 +01:00
Joachim Wiberg f0914f6d32 Fix 'initctl reload NAME' not updating conditions for dependents
When reloading a specific service with 'initctl reload foo', the
pid/foo and service/foo/ready conditions were never cleared, so
dependent services were not notified of the reload.

Clear the service's pid condition and, for pid/none notify types,
the ready condition before reloading.  The conditions are then
reasserted by the pidfile inotify handler when the service touches
its PID file after processing SIGHUP.

For s6/systemd services the ready condition is left intact since
their readiness notification may not re-trigger on SIGHUP.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-10 15:59:49 +01:00
Joachim Wiberg 7090321ff3 Don't hide cursor when shutting down
Users starting Finit based systems using U-Boot or Barebox may otherwise
not get a visible cursor at their prompt.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-10 15:58:46 +01:00
Joachim Wiberg 266f1132a4 Merge pull request #471 from aanderse/remain-after-exit
Add remain:yes option for run/task oneshot commands

Fixes #457

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-02-08 08:37:25 +01:00
Aaron Andersen 5f7e8457af Add remain:yes option for run/task oneshot commands
Similar to systemd's RemainAfterExit=yes.  Prevents the task from
re-running on runlevel re-entry and ensures the post: script runs
when explicitly stopped or when leaving valid runlevels.

Useful for tasks that set up persistent state like firewall rules:

    task [2345] remain:yes \
         post:/usr/sbin/teardown-firewall \
         /usr/sbin/setup-firewall -- Firewall setup

Not supported for bootstrap-only tasks (runlevel S only) since these
are deleted immediately after completion.
2026-02-05 22:08:22 -05:00
Joachim Wiberg a215747355 Fix clone3 build failure with older toolchain kernel headers
Define __NR_clone3 (435) ourselves when not provided by the toolchain
headers.  The syscall number is stable kernel ABI and the same on all
architectures since Linux 5.3.

The existing runtime fallback to fork() handles older kernels that don't
support the syscall.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-29 13:11:23 +01:00
Joachim Wiberg dacda5ab5d Merge pull request #470 from aanderse/master 2026-01-20 07:18:46 +01:00
Aaron Andersen 39044adcf8 Create mount points in fs_init() if they don't exist
In containerized or virtualized environments, standard mount point
directories may not exist at boot.  Ensure they are created before
attempting to mount.
2026-01-19 15:20:43 -05:00
Joachim Wiberg 9396cd1d26 Merge pull request #469 from aanderse/tmpfiles
enhance tmpfiles implementation
2026-01-19 07:57:16 +01:00
Aaron Andersen c9fd4418e7 tmpfiles: fix f/F to apply ownership when writing content
When f or F types write content to a file, the mode and ownership
specified in the config should be applied. Previously, ownership was
only applied when create() was used (i.e., when no argument was
specified).

Now we explicitly apply mode and ownership after writing content to
the file.
2026-01-18 18:59:20 -05:00
Aaron Andersen 7206f745a2 tmpfiles: fix 'e' type to only adjust existing directories
According to tmpfiles.d(5), the 'e' type adjusts the mode and ownership
of existing paths but should not create them. Previously, mksubsys()
was used which could create directories.

Now we explicitly check if the path is an existing directory before
adjusting its permissions.
2026-01-18 18:59:20 -05:00
Aaron Andersen 25bcde12d4 tmpfiles: add support for numeric uid/gid in config files
Add parse_uid() and parse_gid() helper functions that support both
numeric IDs and name lookups. Update the d/D directory creation
handlers to use these new functions.

This allows config files to specify ownership using numeric UIDs and
GIDs instead of only usernames and group names, matching systemd-tmpfiles
behavior.
2026-01-18 18:59:20 -05:00
Aaron Andersen 7459ede806 tmpfiles: fix L+ to replace non-directory entries
The L+ type should replace existing entries with a symlink. Previously,
rmrf() was always called which is only appropriate for directories.
Now we check if the path is a directory first, and use erase() for
files and symlinks.
2026-01-18 18:59:20 -05:00
Aaron Andersen 6bf35513c3 tmpfiles: add support for config files on command line
Allow specifying one or more configuration files as command line
arguments instead of always processing all files in the standard
tmpfiles.d directories.

This enables targeted operations on specific config files:

    tmpfiles --create /etc/tmpfiles.d/myapp.conf
    tmpfiles --clean /tmp/test.conf /tmp/other.conf

When no config files are specified, the existing behavior of
processing all *.conf files in the standard directories is preserved.

Also refactors file processing into a helper function to reduce
code duplication.
2026-01-18 18:59:20 -05:00
Aaron Andersen 0b8d39329a tmpfiles: add --clean flag for age-based cleanup
Add support for the --clean (-C) flag to remove files and directories
older than the age specified in tmpfiles.d configuration entries.

The age field (6th column) in tmpfiles.d entries can now be used with
'd', 'D', and 'e' type entries to clean up old files.  Supported time
suffixes are: s (seconds), m (minutes), h (hours), d (days), w (weeks).

Example configuration:
    d /tmp/cache 0755 root root 10d

When run with --clean, files in /tmp/cache older than 10 days will be
removed.  The directory itself is preserved.

Uses a conservative cleanup approach matching systemd-tmpfiles:
 - Files: kept if ANY of atime, ctime, mtime is recent
 - Directories: kept if ANY of atime, mtime is recent (ctime excluded
   because cleanup itself updates directory ctime)

A value of "-" or "0" for age disables cleanup for that entry.

Note: x/X exclusion patterns are recognized but not yet implemented.
2026-01-18 18:59:20 -05:00
Aaron Andersen 84098dd8b7 tmpfiles: rename flags for clarity
Rename the command-line flag variables to be more descriptive:

  c_flag -> create_flag
  r_flag -> remove_flag

This improves code readability.
2026-01-18 18:59:20 -05:00
Joachim Wiberg c4463ec64f initctl: escape special characters in JSON output
Strings like command, description, and environment may contain characters
that need escaping for valid JSON, e.g., embedded quotes in command line
arguments like -V "NanoPi R2S".

Add json_escape() helper to handle quotes, backslashes, and control chars.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-18 23:26:37 +01:00
Joachim Wiberg 6f1808248e Merge pull request #468 from aanderse/master
netlink: enumerate existing interfaces at startup
2026-01-18 09:00:27 +01:00
Aaron Andersen 29029bb78f netlink: enumerate existing interfaces at startup
The netlink plugin only receives RTM_NEWLINK events for interfaces that
appear after the plugin starts.  Interfaces that already exist at boot
(e.g., virtio-net in QEMU) never generate events, so their conditions
like net/eth0/exist were never set.

Moving enumeration to PLUGIN_INIT doesn't work because it runs before
cond_init(), so the condition filesystem isn't ready yet.

Fix by registering an HOOK_SVC_PLUGIN callback that queries existing
interfaces and routes.  This hook runs during conf_init(), after the
condition system is initialized.
2026-01-14 15:58:22 -05:00
Joachim Wiberg bbe588ac16 Bump version for new release cycle and update ChangeLog
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-12 19:32:09 +01:00
Joachim Wiberg 34bf9a7776 Fix #467: TTY services stuck in restart state after non-zero exit
When a TTY exited with non-zero code (e.g., user with shell=/sbin/false),
it would enter restart state but never recover, requiring manual restart.

The throttling logic from commit f0032ab had two issues:

  1. Duplicate exit code check in service_retry() created infinite timer loop
  2. TTYs lacked default restart_tmo, causing timer to never start

Fix by removing duplicate check and ensuring TTYs get a 2-second default
restart_tmo for proper throttling.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-12 19:31:39 +01:00
Joachim Wiberg bb111f98fc Merge pull request #465 from aanderse/master
Set USER and LOGNAME environment variables when dropping privileges
2026-01-11 08:54:59 +01:00
Joachim Wiberg c7faf4f9e2 Merge pull request #466 from JackieMium/patch-1
Fix elogind path in elogind.conf
2026-01-11 08:53:29 +01:00
JackieMe 18774539c0 Fix elogind path in elogind.conf
On Debian and Debian-derived distro, the elogind is place at /usr/libexec/elogind
2026-01-11 00:00:06 -06:00
Aaron Andersen abaad560f0 Set USER and LOGNAME environment variables when dropping privileges
When a service is configured to run as a non-root user (@user), finit
correctly drops privileges via setuid() and sets HOME and PATH, but
does not set the USER and LOGNAME environment variables. They remain
set to "root" from boot time.

This causes problems for software that determines its identity from
the environment rather than getuid(). For example, rootless Podman
checks os.Getenv("USER") first when looking up subordinate UID/GID
ranges in /etc/subuid and /etc/subgid.

With USER=root but UID=1000, Podman looks up root's subuid entry
instead of the actual user's, causing applications like newuidmap
to fail. Setting USER and LOGNAME to match the actual user identity
follows POSIX conventions and matches the behavior of su, sudo, and
login.
2026-01-10 21:36:58 -05:00
Joachim Wiberg 0ef325d86c doc: improve cgroups documentation
- Grammar
 - Clarity
 - Examples

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-07 12:49:37 +01:00
Joachim Wiberg 2c94f4e45f doc: fix per-service cgroup syntax
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-07 12:49:13 +01:00
Joachim Wiberg da184e7922 doc: fix invalid user:group examples in cgroups.md
Introduced in 820b9a77 for v4.15.

Fixes #464

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-07 11:55:17 +01:00
Joachim Wiberg 1e93fc1671 Merge pull request #463 from aanderse/switch_root
Add switch_root support for initramfs to real root transitions
2026-01-03 00:49:07 +01:00
Aaron Andersen 8e7d1b7bb5 Refactor: drop do_ prefix from iterate_proc() and switch_root()
The do_ prefix is conventionally reserved for local helper functions.
Move switch_root() declaration to private.h alongside iterate_proc()
and remove the now-empty initramfs.h header.
2026-01-02 18:13:00 -05:00
Aaron Andersen 373738f3d1 Implement switch_root functionality allowing Finit to serve as the init
in an initramfs, then transition to the real root filesystem.  Useful
for systems requiring early boot tasks like LUKS unlock, LVM activation,
or network boot before mounting the real root.

Adds INIT_CMD_SWITCH_ROOT API command, `initctl switch-root` subcommand,
and HOOK_SWITCH_ROOT plugin hook point.  The implementation gracefully
stops services, moves virtual filesystems (/dev, /proc, /sys, /run) to
the new root, deletes initramfs contents to free memory, then execs the
new init as PID 1.

See GitHub Discussion #292 for background.
2026-01-01 19:10:24 -05:00
Aaron Andersen e6d3eb2526 Handle already-mounted cgroups in cgroup_init()
Add handling for EBUSY when mounting cgroup2 filesystem, which occurs
when cgroups are already mounted. This can happen after switch_root
when cgroups were moved from the initramfs, or in container environments.

Verify the existing mount is actually cgroup2 before proceeding, and
track whether we mounted to avoid unmounting on error if we didn't.
2026-01-01 16:02:36 -05:00
Joachim Wiberg d7fd5bc902 .github: ensure regression tests do not run in parallel
At least the sysvpart.sh regression test cannot run in parallel yet with
other tests (probably runparts.sh), so we must ensure the tests never
run in parallel, in particular at release.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-01 16:34:16 +01:00
Joachim Wiberg 3f98220d5d test: simplify
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-01 15:54:06 +01:00
Joachim Wiberg 0ca509a42e test: debug sysroot setup
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-01 15:47:11 +01:00
Joachim Wiberg d17144d16f .github: extract test results dir at release
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-01 15:46:43 +01:00
Joachim Wiberg a608c5a5c9 .github: always upload test results, regardless
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-01 15:46:10 +01:00
Joachim Wiberg 9b44b99480 .github: remember to ldconfig
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-01 15:40:06 +01:00
Joachim Wiberg ad225156f1 Update ChangeLog and bump version for release
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-01 11:09:16 +01:00
Joachim Wiberg eb92a915d3 initctl: drop logically dead code, found by Coverity Scan
The defines already check for plain mode.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-01 10:46:48 +01:00
Joachim Wiberg 69a4f2c115 Drop logically dead code, found by Coverity Scan
Checks for uid and gid introduced in d017661

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-01 10:42:41 +01:00
Joachim Wiberg 9ce95fe8c0 .github: fix logic in weekly workflow
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-01 09:51:02 +01:00
Joachim Wiberg 06889cc014 .github: verify system can find .so libs in /usr/local
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-01 09:48:33 +01:00
Joachim Wiberg 329f11b4da test: add barebones /etc/{passwd,group} and an ld.so.conf
Finit now requires being able to query at least for the root user and
group before starting any services.

Also, add support for using libraries installed in /usr/local

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-01-01 09:45:06 +01:00
Joachim Wiberg ce40e2b9d2 Rename tty services early from "init" -> "getty"
Finit has support for "Please press Enter to activate this console."
which means there's no getty yet running.  However, when profiling
systems with Finit, and embedded systems in general, a common metric
is the time from power-on to getty has started.

This commit makes sure to rename the process so that BusyBox pidof is
capable of detecting that "getty" has started.  This is mostly for the
bootchart2 project's bootchartd, the native BusyBox bootchartd does not
have this issue.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-31 23:34:04 +01:00
Joachim Wiberg d0176612c9 Default to user/group root for services and check for errors
This is a refactor of getuser() and getgroup() so that they always
return a valid user, and group, for all normal use-cases.  When an
error occurs we now handle it properly in service_fork() so as to
not attempt to start services with an invalid user/group setting
as root.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-31 23:29:28 +01:00
Joachim Wiberg 21753e26dd Set critical env PATH + SHELL early
When running Finit under boothcartd (bootchart2 project) the PATH is
lost due to a bug.  This was a wakeup, so set critical variables in
main() early, before calling fs_init().

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-31 23:29:28 +01:00
Joachim Wiberg 0dc2513b32 Follow-up to 702a606, too long string to hide cursor
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-31 23:29:27 +01:00
Joachim Wiberg d16bfa789b Follow-up to 7c8ab79, missing semicolon
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-27 15:22:09 +01:00
Joachim Wiberg 0b27778c96 .github: install missing glightbox plugin for mkdocs
This is for automatic image zoom.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-27 15:21:06 +01:00
Joachim Wiberg 3e25297e3c doc: major overhaul of distro recs, simplify & clarify
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-27 14:12:13 +01:00
Joachim Wiberg 6560968c8b doc: update feature list
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-27 13:54:22 +01:00
Joachim Wiberg c9a3c5c885 doc: reorg. of menu a bit
Try out navigation.tabs, if we don't like it we can revert.

Reorg for stricter sections, what information actually belongs where?
E.g., introduction is now in a Getting Started section.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-27 13:52:13 +01:00
Joachim Wiberg a2d45e8f5e doc: add missing link to setrlimit(2)
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-27 13:06:56 +01:00
Joachim Wiberg f6c69eb108 doc: follow-up to be2a0ec, clearify capabilities with @root
Also, add links to relevant man pages.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-27 13:06:07 +01:00
Joachim Wiberg f0032ab6b7 Throttle failing services, e.g., tty, on error exit code
Some 'respawn' type services, like gettys, may hog the CPU in error
states if the service immediately exits.  E.g., due to missing dev.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-27 12:32:01 +01:00
Joachim Wiberg 53c5d0e55a Always reset ownership and permissions on TTY device nodes
During /bin/login phase the TTY device node is chowned and chmodded to
the authenticated user.  It will remain in this state until the next
call to getty.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-27 12:12:18 +01:00
Joachim Wiberg 7c8ab7915f Fix #458: follow-up to be2a0ec, capabilities breaks root access
A user reports inability to re-start getty on the console after logging
out from a serial console.  After some digging it was found that the tty
was owned by the last user logged in and 600.  Even thougn getty runs as
root, it did not have permission to re-open the device node.

Turns out there was a minor bug in the new capability code that cleared
all capabilities from the root user.  A surprising amount of programs
worked just fine, but restarting getty gave it away.

The fix is to only call cap_setuid() when capabilities are set for the
service, otherwise we just fall back to setuid().

Also, refactor service_register() wrt. capabilities a bit so that we can
give users an early warning if the configuration is invalid, by adding a
parse_caps() helper function that calls cap_iab_from_text() to verify.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-27 00:49:48 +01:00
Joachim Wiberg 12f7855a78 Update ChangeLog for v4.15 release
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-26 13:41:18 +01:00
Joachim Wiberg bcc3360b53 Update site_url for generated documentation
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-26 13:30:30 +01:00
Joachim Wiberg d3408d14a8 Simplify and update links
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-26 13:28:43 +01:00
Joachim Wiberg 804060444a .github: fix deploy issue
- We already have the SHA
 - Use 'git add -A' to handle deleted files too

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-26 13:16:45 +01:00
Joachim Wiberg de544a5b36 .github: deploy docs to project's pages
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-26 12:47:25 +01:00
Joachim Wiberg 0d48d1df49 Merge pull request #461 from aanderse/master
Add support for supplementary groups
2025-12-26 10:28:51 +01:00
Aaron Andersen b46592e818 Add support for supplementary groups
Implement supplementary group support for services, allowing them to
access resources owned by multiple groups. Uses the @user:group,sup1,sup2
syntax to explicitly specify supplementary groups, in addition to now
reading group membership from /etc/group.
2025-12-24 09:54:37 -05:00
Joachim Wiberg 22bc218c84 Fix #462: /dev/pts mounted with wrong mode
Before this fix:

    admin@infix:~$ sudo ls -la /dev/pts/
    total 0
    drwxr-xr-x    2 root     root             0 Dec 24 08:16 .
    drwxr-xr-x   13 root     root         13340 Dec 24 08:16 ..
    cr--------    1 root     tty       136,   0 Dec 24 08:18 0
    crw-rw-rw-    1 root     root        5,   2 Dec 24 08:16 ptmx
    admin@infix:~$ mount | grep devpts
    devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=400,ptmxmode=666)

After:

    admin@infix-00-00-00:~$ sudo ls -l /dev/pts/
    total 0
    crw--w----    1 root     tty       136,   0 Dec 24 08:21 0
    crw-rw-rw-    1 root     root        5,   2 Dec 24 08:20 ptmx
    admin@infix-00-00-00:~$ mount |grep pts
    devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-24 09:23:30 +01:00
Joachim Wiberg 702a606d26 Hide cursor at boot and shutdown
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-17 08:19:39 +01:00
Joachim Wiberg a3d9b6e9b1 initctl: fix remaining lingering artifacts in 'top' output
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-17 08:19:38 +01:00
Joachim Wiberg 390a0f48c1 initctl: minor, simplify code
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-17 08:19:38 +01:00
Joachim Wiberg f4a13687e8 initctl: resolve hierarchical cgroup limits
Cgroups v2 limits are hierarchical - a process is constrained by the
most restrictive limit in its ancestor chain, not just its immediate
cgroup.  This patch updates cg_conf() to walk up the hierarchy and
report effective limits by comparing values at each level.

This fixes incorrect "max" (unlimited) reporting in 'initctl --json
status', 'initctl cgroup', and 'initctl top' when child cgroups have
no explicit limits but parents do.

For memory.max and cpu.max: take minimum (most restrictive)
For memory.min: take maximum (most protection)

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-17 08:19:37 +01:00
Joachim Wiberg 43ca51c3b1 initctl: add cpu/mem limits as well to json status output
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-17 08:19:37 +01:00
Joachim Wiberg 864b71af14 Follow-up to d87d298, prevent "cursor jumps"
Comment-out code that makes the cursor "jump" around at boot before
displaying: Please press Enter to activate this console.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-17 08:19:36 +01:00
Joachim Wiberg 44a928e5e6 Follow-up to e635ea8, adjust libite version dependency
libite v2.6.2 is not yet in Buildroot, so let's relax the dependency a
bit.  Load bearing functionality was in v2.6.0, any fixes on top is a
nice-to-have only.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-16 10:19:29 +01:00
Joachim Wiberg f513348963 doc: update ChangeLog for upcoming v4.15
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:38:41 +01:00
Joachim Wiberg 820b9a77c1 doc: update cgroup configuration section
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:09 +01:00
Joachim Wiberg 85baafe99c doc: update links to new project home and add release badge
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:08 +01:00
Joachim Wiberg e635ea882a Bump required libite and libuev versions
We now require readsnf() introduced in libite 2.6.0, with bug fixes
this effectively means v2.6.2.

The libuev bump is for 64-bit time_t, with bug fix => v2.4.1

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:08 +01:00
Joachim Wiberg f908c8d43a Fix possible out-of-bounds write, found by Coverity Scan
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:07 +01:00
Joachim Wiberg 308f75b209 Silence false positive from Coverity Scan
51static int is_dir_empty(const char *path)
    52{
	 1. var_decl: Declaring variable namelist without initializer.
    53        struct dirent **namelist;
    54        int num;
    55

   CID 898746: (#1 of 1): Uninitialized pointer read (UNINIT)
   2. uninit_use_in_call: Using uninitialized value namelist when calling scandir.
    56        num = scandir(path, &namelist, NULL, NULL);
    57        if (num < 0)
    58                return 0;

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:07 +01:00
Joachim Wiberg d87d298c85 getty: fix terminal scrollback issues after login on console
Fixes an issue where the mouse scroll wheel and Shift+PgUp/PgDn
sometimes would not work properly after login.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:06 +01:00
Joachim Wiberg ae7931f68e plugins: follow-up to 13a8f56, fix missing else branch
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:37:06 +01:00
Joachim Wiberg 8422d2715c initctl: fix flickering in 'top' and handle smaller screens
Drop clear screen to fix flickering in 'initctl top' output.  Also, make
sure to not garble the display if the the terminal is too small.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:34 +01:00
Joachim Wiberg 2eefaa5db6 initctl: fix file descriptor leak causing 'initctl top' to crash
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:33 +01:00
Joachim Wiberg fb0ad18d3a initctl: add CPU throttled information alongside memory usage
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:33 +01:00
Joachim Wiberg 2681444b07 system: use reasonable cgroup names from hotplug services
Instead of /system/10-hotplug/ we now place udevd, mdevd, and others, in
more aptly named groups using the new 'name:' syntax.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:32 +01:00
Joachim Wiberg dd36116a97 Add housekeeping functions to clean up unused cgroups
This should not be needed, but for some reason we don't get events when
early processes exit, so we end up with lingering cgroups.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:32 +01:00
Joachim Wiberg e756d6f8ac Allow forking sysv/services like podman to move between cgroups
The container monitor that podman forks off when starting a container
instance creates subgroups in the cgroup v2 hieararchy that we want to
reuse.  This patch adds cgroup_move_svc() which we call from the pidfile
plugins to relocate the conmon process.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:32 +01:00
Joachim Wiberg 567c695954 Start services with clone3() and place in cgroup directly
This commit activates the use of clone3() for service_fork(), to allow
Linux to create the new process directly in the correct cgroup instead
of later moving it there -- much cheaper and less error prone.

To facilitate this a few new helper cgroup functions have been added and
two new configuration directives introduced: delegate and name:leafname.
The delegate option is for running, e.g., container runtimes that want
to create their own cgroup v2 structur, and the name:leafname allows a
user to change the name of the subgroup under user/system/init.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 22:04:31 +01:00
Joachim Wiberg 4877aecc1e Add support for clone3() to replace fork()
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-15 09:32:03 +01:00
Joachim Wiberg 65644f5956 Follow-up to d9a0c2d, dead code
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-14 10:04:01 +01:00
Joachim Wiberg a02760f499 Minor, constify
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-14 10:00:39 +01:00
Joachim Wiberg 7aab3bb421 mkdocs: update project URLs and name
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-08 09:16:26 +01:00
Joachim Wiberg b5beb2b8d4 Minor, update URLs and clean up a bit
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:53:56 +01:00
Joachim Wiberg 09b4aee5bc Update ChangeLog and bump version for v4.15-rc1
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:38:14 +01:00
Joachim Wiberg d9a0c2dce9 Switch from PTY to pipe for service log redirection
The PTY approach caused isatty() to return true for services using
the log directive, triggering programs like fprintd (using glib) to
emit ANSI escape codes and other TTY-specific formatting in syslog.

Using a standard pipe ensures isatty() correctly returns false, so
programs produce plain text output suitable for logging.

For services that require line-buffered output, users can wrap the
command with `stdbuf -oL` as documented in doc/config/logging.md.

Fixes #455

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:38:13 +01:00
Joachim Wiberg 178301baf2 Change default reboot to SOC reset from WDT reset
Fixes #460

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:38:13 +01:00
Joachim Wiberg 4e18affa9b Allow skipping bootstrap wait with Ctrl-C
When setting up a new system with Finit it is very common to make small
logical mistakes that cause "hangs" at boot.  This is when Finit waits
for 180 sec. for run/tasks to complete before moving to the configured
runlevel.  This patch adds console input monitoring during bootstrap
wait that allows users to press Ctrl-C to skip waiting and proceed to
the configured runlevel.

When Ctrl-C is detected, all incomplete run/task/services are logged
to syslog for later troubleshooting after login.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:38:12 +01:00
Joachim Wiberg 21f40fb95e Allow more initctl commands in runlevels S/0/6
Relax the constraints introduced in a39ee0b, for issue #342, a bit on
when start/stop/restart/reload service can be called.  Also, allow
'initctl reload', but ignore it when the system is in runlevels S/0/6.

This makes it possible to start manual:yes type services at botostrap,
for example, which has been a common feature request.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:38:12 +01:00
Joachim Wiberg 24f0cee49a Ensure mount/unmount skips 'noauto' entries
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-12-07 09:38:11 +01:00
Joachim Wiberg fee38d4359 Merge pull request #458 from aanderse/master
Add support for Linux capabilities
2025-12-05 12:53:04 +01:00
Joachim Wiberg 0c85ea844a Merge pull request #459 from rical/add-mem-to-initctl-json
initctl: add memory field to JSON status output
2025-12-01 22:42:50 +01:00
Richard Alpe c3839edc40 initctl: add memory field to JSON status output
Add missing memory information to JSON output in json_status_one().
The memory field shows cgroup memory usage in bytes when cgroup
support is available and the service has a valid PID, matching
the behavior of the text status output.

Signed-off-by: Richard Alpe <richard@bit42.se>
2025-12-01 14:51:27 +01:00
Aaron Andersen be2a0ec3e7 Add support for Linux capabilities
Implement Linux capability support for services, allowing them to run
with minimal required privileges instead of running as root. This uses
the modern IAB (Inheritable, Ambient, Bounding) API from libcap.
2025-11-30 11:36:53 -05:00
Joachim Wiberg c23952cd9e Merge pull request #453 from liuming50/fix-rtc-restore-issues
plugins:rtc.c: drop some invalid condition checks

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-10-14 20:33:25 +02:00
Ming Liu 13a8f56b46 plugins:rtc.c: drop some invalid condition checks
There are some invalid condition checks in rtc.c, when rtc_file is
missing, it should not consider that as a error and return, otherwise,
it can lead to the case that time_set wont be called at all.

With this fix, when both RTC and file restore fail, it will fall back
to call time_set(NULL) and restore time from rtc_timestamp, this
ensures the OS has a valid time at the very first boot.

Signed-off-by: Ming Liu <liu.ming50@gmail.com>
2025-10-14 15:38:56 +02:00
Joachim Wiberg 927acdbd7e Increase MAX_ID_LEN to support longer service identifiers
Allow service IDs up to 64 characters to support SHA-256 hashes,
UUIDs, and other long unique identifiers. This increases memory
usage by ~98 bytes per service instance, which is negligible for
typical deployments.

The IDENT column width in initctl output adapts dynamically based
on actual ID lengths in use, so short IDs remain unaffected.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-10-14 11:49:57 +02:00
283 changed files with 31960 additions and 2961 deletions
+30 -26
View File
@@ -20,7 +20,6 @@ Please take care to ensure you follow the project coding style and the
commit message format. If you follow these recommendations you help
the maintainer(s) and make it easier for them to include your code.
Coding Style
------------
@@ -31,8 +30,7 @@ First of all, lines are allowed to be longer than 72 characters these
days. In fact, there exist no enforced maximum, but keeping it around
100 chars is OK.
The coding style itself is strictly Linux [KNF][].
The coding style itself is strictly [Linux coding style][KNF].
Commit Messages
---------------
@@ -41,31 +39,38 @@ Commit messages exist to track *why* a change was made. Try to be as
clear and concise as possible in your commit messages, and always, be
proud of your work and set up a proper GIT identity for your commits:
git config --global user.name "Jane Doe"
git config --global user.email jane.doe@example.com
```bash
$ git config --global user.name "Jane Doe"
$ git config --global user.email jane.doe@example.com
```
Example commit message from the [Pro Git][gitbook] online book, notice
how `git commit -s` is used to automatically add a `Signed-off-by`:
Brief, but clear and concise summary of changes
More detailed explanatory text, if necessary. Wrap it to about 72
characters or so. In some contexts, the first line is treated as
the subject of an email and the rest of the text as the body. The
blank line separating the ummary from the body is critical (unless
you omit the body entirely); tools like rebase can get confused if
you run the two together.
Further paragraphs come after blank lines.
- Bullet points are okay, too
- Typically a hyphen or asterisk is used for the bullet, preceded
by a single space, with blank lines in between, but conventions
vary here
Signed-off-by: Jane Doe <jane.doe@example.com>
```
Brief, but clear and concise summary of changes
More detailed explanatory text, if necessary. Remember, a future
reader has the diff and can see what it does, the commit message in
itself should usually talk about the *why* of a change and/or any
technical limitations and future work.
Wrap the text to about 72 characters or so. In some contexts, the
first line is treated as the subject of an email and the rest of the
text as the body. The blank line separating the summary from the body
is critical (unless you omit the body entirely); tools like rebase can
get confused if you run the two together.
Further paragraphs come after blank lines.
- Bullet points are okay, too
- Typically a hyphen or asterisk is used for the bullet, preceded
by a single space, with blank lines in between, but conventions
vary here
Signed-off-by: Jane Doe <jane.doe@example.com>
```
Code of Conduct
---------------
@@ -73,7 +78,6 @@ Code of Conduct
It is expected of everyone engaging in the project to, in the words of
Bill & Ted; [be excellent to each other][conduct].
[KNF]: https://en.wikipedia.org/wiki/Kernel_Normal_Form
[gitbook]: https://git-scm.com/book/ch5-2.html
[KNF]: https://www.kernel.org/doc/html/v5.8/process/coding-style.html
[gitbook]: https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines
[conduct]: CODE-OF-CONDUCT.md
+1 -1
View File
@@ -5,7 +5,7 @@
finit is a small project, as such we have no possibility to support older versions.
The only supported version is the latest released on GitHub:
<https://github.com/troglobit/finit/releases>
<https://github.com/finit-project/finit/releases>
## Reporting a Vulnerability
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
#
# Extract the topmost release entry from doc/ChangeLog.md and unwrap
# its paragraphs, so the GitHub releases page can wrap them to the
# browser width instead of showing the hard 72-column breaks.
#
# Usage: .github/scripts/extract-changelog.sh < doc/ChangeLog.md > release.md
#
set -e
# The entry sits between the first two underlines, minus the blank
# line separating it from the next heading.
awk '/^-----*$/{if (x == 1) exit; x=1;next}x' | head -n -1 |
# Blank lines, headings, blockquotes, tables, and list items (at any
# indent) start a new block, everything else continues the current one.
awk '
function flush() { if (buf) print buf; buf = "" }
{
if (/^$/) { flush(); print; blank = 1; next }
if (/^ *([-*] |[0-9]+\. |[>#|])/ || blank || !buf || brk)
{ flush(); buf = $0 }
else
{ sub(/^ +/, ""); buf = buf " " $0 }
blank = 0
brk = / $/
}
END { flush() }
'
+120 -9
View File
@@ -18,6 +18,71 @@ concurrency:
cancel-in-progress: true
jobs:
fuzz:
name: fuzz
runs-on: ubuntu-latest
if: github.event_name != 'push' || github.ref == 'refs/heads/master'
steps:
- name: Install dependencies
run: |
sudo apt-get -y update
sudo apt-get -y install pkg-config libconfuse-dev clang libblkid-dev
# clang picks the newest gcc tree it finds and needs the
# matching libstdc++ headers to link the fuzzer runtime
sudo apt-get -y install libstdc++-14-dev || true
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
tar xf libuev-2.4.1.tar.xz
tar xf libite-2.6.2.tar.gz
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
sudo ldconfig
- uses: actions/checkout@v7
- name: Configure
run: |
./autogen.sh
./configure --prefix=/usr --exec-prefix= --sysconfdir=/etc --localstatedir=/var
- name: Build fuzz target
run: |
clang -fsanitize=fuzzer,address -DLINK_FUZZ_LIBFUZZER -D_GNU_SOURCE \
-I libink -I . -o fuzz-msg-parse \
test/src/fuzz-msg-parse.c libink/*.c
# Restores the newest corpus and saves a fresh one, since a cache
# entry is immutable once written. Caches made on a branch are
# private to it, so the corpus that accumulates on master is what
# pull requests start from, rather than nothing.
- name: Restore corpus
uses: actions/cache@v6
with:
path: .fuzz-corpus
key: fuzz-corpus-${{ github.run_id }}
restore-keys: fuzz-corpus-
- name: Fuzz
run: |
mkdir -p .fuzz-corpus
./fuzz-msg-parse .fuzz-corpus -max_total_time=120 -max_len=4096 \
-print_final_stats=1
# Without this the corpus only ever grows, and most of what it
# accumulates reaches code some earlier input already reached.
- name: Minimise corpus
if: always()
run: |
mkdir -p .fuzz-corpus-min
./fuzz-msg-parse -merge=1 .fuzz-corpus-min .fuzz-corpus
rm -rf .fuzz-corpus
mv .fuzz-corpus-min .fuzz-corpus
echo "corpus: $(ls .fuzz-corpus | wc -l) inputs"
- name: Upload crashers
if: failure()
uses: actions/upload-artifact@v7
with:
name: fuzz-crashers
path: |
crash-*
leak-*
timeout-*
if-no-files-found: ignore
build:
# Verify we can build on latest Ubuntu with both gcc and clang
name: ${{ matrix.compiler }}
@@ -34,21 +99,27 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get -y update
sudo apt-get -y install pkg-config tree jq libuev-dev libite-dev
- uses: actions/checkout@v4
sudo apt-get -y install pkg-config tree jq libcap-dev libconfuse-dev libblkid-dev libpam0g-dev
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
tar xf libuev-2.4.1.tar.xz
tar xf libite-2.6.2.tar.gz
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
sudo ldconfig
- uses: actions/checkout@v7
- name: Static Finit
run: |
./autogen.sh
./configure --prefix= --enable-static
make -j3 V=1
make -j9 V=1
- name: Regular Finit
run: |
./configure --prefix=/usr --exec-prefix= --sysconfdir=/etc --localstatedir=/var \
--enable-x11-common-plugin --enable-testserv-plugin --with-watchdog \
--with-keventd --with-libsystemd \
--enable-x11-common-plugin --enable-testserv-plugin \
CFLAGS="-fsanitize=address -ggdb"
make -j3 clean
make -j3 V=1
make -j9 clean
make -j9 V=1
- name: Install to /tmp
run: |
DESTDIR=/tmp make install-strip
@@ -70,9 +141,49 @@ jobs:
sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
- name: Run Unit Tests
run: |
make check || (cat test/test-suite.log; false)
make -j1 check || (cat test/test-suite.log; false)
- name: Upload Test Results
uses: actions/upload-artifact@v4
if: always()
uses: actions/upload-artifact@v7
with:
name: finit-test-${{ matrix.compiler }}
path: test/*.log
no-dbus:
# The D-Bus support is default-enabled, so the HAVE_DBUS paths in
# initctl and finit only bit-rot silently without this leg.
name: no-dbus
runs-on: ubuntu-latest
if: github.event_name != 'push' || github.ref == 'refs/heads/master'
steps:
- name: Install dependencies
run: |
sudo apt-get -y update
sudo apt-get -y install pkg-config tree jq libcap-dev libconfuse-dev libblkid-dev
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
tar xf libuev-2.4.1.tar.xz
tar xf libite-2.6.2.tar.gz
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
sudo ldconfig
- uses: actions/checkout@v7
- name: Build without D-Bus
run: |
./autogen.sh
./configure --prefix=/usr --exec-prefix= --sysconfdir=/etc --localstatedir=/var \
--disable-dbus --enable-testserv-plugin
make -j9 V=1
- name: Verify no bus artifacts
run: |
DESTDIR=/tmp make install-strip
! strings /tmp/sbin/initctl | grep -q finit/bus
! strings /tmp/sbin/finit | grep -q org.finit
- name: Enable unprivileged userns (unshare)
run: |
sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
- name: Smoke test
run: |
make -C test setup-chroot
make -j1 -C test check TESTS='start-stop-serv.sh' \
|| (cat test/test-suite.log; false)
+11 -12
View File
@@ -19,7 +19,7 @@ jobs:
coverity:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Fetch latest Coverity Scan MD5
id: var
env:
@@ -29,7 +29,7 @@ jobs:
--post-data "token=$TOKEN&project=${COVERITY_PROJ}&md5=1" \
-O coverity-latest.tar.gz.md5
echo "md5=$(cat coverity-latest.tar.gz.md5)" | tee -a $GITHUB_OUTPUT
- uses: actions/cache@v4
- uses: actions/cache@v6
id: cache
with:
path: coverity-latest.tar.gz
@@ -55,19 +55,18 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get -y update
sudo apt-get -y install pkg-config
wget https://github.com/troglobit/libuev/releases/download/v2.4.0/libuev-2.4.0.tar.xz
wget https://github.com/troglobit/libite/releases/download/v2.5.2/libite-2.5.2.tar.gz
tar xf libuev-2.4.0.tar.xz
tar xf libite-2.5.2.tar.gz
(cd libuev-2.4.0 && ./configure --prefix=/usr && make && sudo make install-strip)
(cd libite-2.5.2 && ./configure --prefix=/usr && make && sudo make install-strip)
sudo apt-get -y install pkg-config libcap-dev libconfuse-dev libblkid-dev
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
tar xf libuev-2.4.1.tar.xz
tar xf libite-2.6.2.tar.gz
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
sudo ldconfig
- name: Configure
run: |
./autogen.sh
./configure --prefix= --enable-x11-common-plugin --with-watchdog \
--with-keventd --with-libsystemd
./configure --prefix= --enable-x11-common-plugin
- name: Build
run: |
export PATH=`pwd`/coverity/bin:$PATH
@@ -86,7 +85,7 @@ jobs:
--form description="${PROJECT_NAME} $(git rev-parse HEAD)" \
https://scan.coverity.com/builds?project=${COVERITY_PROJ}
- name: Upload build.log
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: coverity-build.log
path: cov-int/build-log.txt
+113 -18
View File
@@ -4,10 +4,12 @@ on:
push:
branches:
- master
- 4.x
paths:
- 'doc/**'
- 'README.md'
- 'mkdocs.yml'
- 'configure.ac'
- '.github/workflows/docs.yml'
pull_request:
types: [opened, synchronize, reopened, labeled]
@@ -15,28 +17,32 @@ on:
- 'doc/**'
- 'README.md'
- 'mkdocs.yml'
- 'configure.ac'
- '.github/workflows/docs.yml'
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
fetch-depth: 0 # Needed for git-revision-date-localized plugin
- name: Determine docs version
id: version
run: |
version=$(sed -n 's/^AC_INIT(\[Finit\], *\[\([0-9]*\)\..*/\1.x/p' configure.ac)
test -n "$version"
echo "version=$version" >> $GITHUB_OUTPUT
- name: Setup Python
uses: actions/setup-python@v4
uses: actions/setup-python@v7
with:
python-version: '3.x'
@@ -46,23 +52,112 @@ jobs:
pipx inject mkdocs mkdocs-material
pipx inject mkdocs pymdown-extensions
pipx inject mkdocs mkdocs-callouts
pipx inject mkdocs mkdocs-glightbox
- name: Build documentation
run: mkdocs build
run: mkdocs build --clean
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
- name: Upload site artifact
uses: actions/upload-artifact@v7
with:
name: site
path: site/
deploy:
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
if: github.event_name == 'push'
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
env:
VERSION: ${{ needs.build.outputs.version }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
- name: Download site artifact
uses: actions/download-artifact@v8
with:
name: site
path: site/
- name: Checkout pages repo
uses: actions/checkout@v7
with:
repository: finit-project/finit-project.github.io
path: pages
ssh-key: ${{ secrets.DOCS_DEPLOY_KEY }}
persist-credentials: true
- name: Setup SSH for push
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keyscan github.com >> ~/.ssh/known_hosts
- name: Sync site to pages repo
run: |
rsync -a --delete site/ "pages/$VERSION/"
- name: Update version index and redirects
run: |
cd pages/
# The root holds only version directories and the files
# generated below, drop anything else (pre-versioned site)
find . -maxdepth 1 ! -name . ! -name .git ! -name CNAME \
! -name '[0-9]*.x' -exec rm -rf {} +
latest=$(ls -d [0-9]*.x | sort -rV | head -1)
sep=
printf '[' > versions.json
for v in $(ls -d [0-9]*.x | sort -rV); do
printf '%s{"version": "%s", "title": "%s", "aliases": []}' \
"$sep" "$v" "$v" >> versions.json
sep=', '
done
printf ']\n' >> versions.json
cat > index.html <<EOF
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=$latest/">
<link rel="canonical" href="https://finit-project.github.io/$latest/">
</head>
</html>
EOF
cat > 404.html <<EOF
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Finit &mdash; page not found</title>
<script>
var path = window.location.pathname;
if (!/^\/[0-9]+\.x(\/|$)/.test(path))
window.location.replace("/$latest" + path);
</script>
</head>
<body>
<p>Page not found. Try the <a href="/$latest/">latest User Guide</a>.</p>
</body>
</html>
EOF
- name: Commit and push
run: |
cd pages/
if [ -z "$(git status --porcelain)" ]; then
exit 0
fi
SRC_REPO="${GITHUB_REPOSITORY}"
SRC_REF="${GITHUB_SHA::7}"
SRC_URL="https://github.com/${SRC_REPO}/commit/${SRC_REF}"
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git add -A
git commit -m "docs: update from ${SRC_REPO}@${SRC_REF}" \
-m "For details, see ${SRC_URL}"
git push
+24 -10
View File
@@ -13,14 +13,19 @@ jobs:
name: Build and upload release tarball
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
env:
MAKEFLAGS: -j3
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Installing dependencies ...
run: |
sudo apt-get -y update
sudo apt-get -y install pkg-config jq libuev-dev libite-dev
sudo apt-get -y install pkg-config jq libcap-dev libconfuse-dev libblkid-dev
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
tar xf libuev-2.4.1.tar.xz
tar xf libite-2.6.2.tar.gz
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
sudo ldconfig
- name: Setting release variables ...
id: build
run: |
@@ -41,21 +46,30 @@ jobs:
./autogen.sh
./configure --prefix=/usr --exec-prefix= --sysconfdir=/etc \
--localstatedir=/var --enable-x11-common-plugin \
--enable-testserv-plugin --with-watchdog \
--with-keventd --with-libsystemd
--enable-testserv-plugin
- name: Enable unprivileged userns (unshare)
run: |
sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
- name: Build release ...
run: |
make release || (cat test/test-suite.log; false)
# Get version for finding distcheck build directory
./configure --version | head -1
ver=$(./configure --version | head -1 | awk '{print $NF}')
dir=finit-$ver/_build/sub
make -j1 release || (cat $dir/test/test-suite.log; false)
ls -lF ../
mkdir -p artifacts/
mv ../*.tar.* artifacts/
mv ../*.tar.gz artifacts/
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v7
with:
name: finit-release-test-logs
path: finit-*/_build/sub/test/*.log
- name: Extract ChangeLog entry ...
run: |
awk '/-----*/{if (x == 1) exit; x=1;next}x' doc/ChangeLog.md \
|head -n -1 > release.md
.github/scripts/extract-changelog.sh < doc/ChangeLog.md > release.md
cat release.md
- uses: ncipollo/release-action@v1
with:
+18 -5
View File
@@ -14,14 +14,14 @@ jobs:
schedule:
runs-on: ubuntu-latest
outputs:
run: ${{ steps.check.outputs.run }}
run: ${{ steps.schedule.outputs.run }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Check for any commits since last week
id: schedule
run: |
commits="$(git log --format=%H --since='1 week ago')"
if [ -n "$commits" ]; then
if [ -z "$commits" ]; then
echo "run=true" >> $GITHUB_OUTPUT
else
echo "run=false" >> $GITHUB_OUTPUT
@@ -34,8 +34,15 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get -y update
sudo apt-get -y install pkg-config jq libuev-dev libite-dev
- uses: actions/checkout@v4
sudo apt-get -y install pkg-config jq libcap-dev libconfuse-dev libblkid-dev
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
tar xf libuev-2.4.1.tar.xz
tar xf libite-2.6.2.tar.gz
(cd libuev-2.4.1 && ./configure && make -j9 && sudo make install-strip)
(cd libite-2.6.2 && ./configure && make -j9 && sudo make install-strip)
sudo ldconfig
- uses: actions/checkout@v7
- name: Base run
run: |
./autogen.sh
@@ -52,3 +59,9 @@ jobs:
ver=$(/tmp/sbin/initctl -V | awk '/Finit.*/ {print $2;}')
dir=finit-$ver/_build/sub
make -j1 distcheck || (cat $dir/test/test-suite.log; false)
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v7
with:
name: finit-weekly-test-logs
path: finit-*/_build/sub/test/*.log
+5
View File
@@ -52,3 +52,8 @@ GTAGS
# VS Code user settings
.vscode
# Fuzzing: the target is built by hand with clang, see doc/build.md,
# and the corpus it grows is a local artefact, not something to ship
/fuzz-msg-parse
/.fuzz-corpus/
+29 -16
View File
@@ -1,5 +1,21 @@
ACLOCAL_AMFLAGS = -I m4
SUBDIRS = man plugins src system tmpfiles.d
# libink must precede src in SUBDIRS because finit links against
# libink at build time. Automake recurses subdirs strictly in
# declaration order, so the typical "explicit dependency" pattern
# (foo: bar) doesn't help here — ordering is the only thing that
# does. libsystemd is consumed by test/serv only, so its position
# after src is fine.
SUBDIRS = man plugins
if DBUS
SUBDIRS += libink dbus-1
endif
SUBDIRS += src system tmpfiles.d
if KEVENTD
SUBDIRS += keventd
endif
dist_doc_DATA = README.md LICENSE contrib/finit.conf
if CONTRIB
@@ -24,23 +40,20 @@ endif
install-dev:
@make -C src install-pkgincludeHEADERS
# The fuzz target is built by hand with clang (doc/build.md), so it is
# not in any _PROGRAMS and nothing else would clear it or the corpus
# it grows.
distclean-local:
-rm -f $(top_builddir)/fuzz-msg-parse
-rm -rf $(top_builddir)/.fuzz-corpus
# Target to run when building a release
release: distcheck
@for file in $(DIST_ARCHIVES); do \
md5sum $$file > ../$$file.md5; \
sha256sum $$file > ../$$file.sha256; \
done
@mv $(DIST_ARCHIVES) ../
@echo
@echo "Resulting release files:"
@echo "================================================================="
@for file in $(DIST_ARCHIVES); do \
printf "$$file \tDistribution tarball\n"; \
printf "$$file.md5\t"; cat ../$$file.md5 | cut -f1 -d' '; \
printf "$$file.sha256\t"; cat ../$$file.sha256 | cut -f1 -d' '; \
done
@echo "Resulting release tarball(s): $(DIST_ARCHIVES)"
DISTCHECK_CONFIGURE_FLAGS = --prefix=/usr --sysconfdir=/etc --localstatedir=/var \
--enable-testserv-plugin --enable-x11-common-plugin \
--with-watchdog --with-keventd --with-fstab=/etc/fstab \
--with-libsystemd --with-bash-completion-dir=no
DISTCHECK_CONFIGURE_FLAGS = --prefix=/usr --sysconfdir=/etc --localstatedir=/var \
--enable-testserv-plugin --enable-x11-common-plugin \
--with-fstab=/etc/fstab --with-libsystemd \
--with-bash-completion-dir=no
+22 -19
View File
@@ -1,17 +1,16 @@
[![License Badge][]][License] [![GitHub Status][]][GitHub] [![Coverity Status][]][Coverity Scan]
[![License Badge][]][License] [![Release Badge][]][Release] [![GitHub Status][]][GitHub] [![Coverity Status][]][Coverity Scan]
<img align="right" src="doc/img/finit3.png" alt="Finit: Fast Init">
Finit is an alternative to [SysV init](https://en.wikipedia.org/wiki/Init)
and [systemd](https://www.freedesktop.org/wiki/Software/systemd/), focused
on small and embedded Linux systems, although fully usable on server and
[desktop systems][finix].
Finit is a fast, simple alternative to [SysV init](https://en.wikipedia.org/wiki/Init)
and [systemd](https://systemd.io), designed for small and embedded Linux systems.
It can also run on desktop and server systems, like [finix][].
> Reverse engineered from the [EeePC fastinit][]
> "gaps filled with frog DNA …"
> — [Claudio Matsuoka][]
For detailed information, explore our extensive documentation
:books: **<https://troglobit.github.io/finit/>**
:books: **<http://finit-project.github.io>**
<div align="center">
<img src="doc/img/alpine-screenshot2.png" alt="Alpine screenshot"><br/>
@@ -20,28 +19,32 @@ For detailed information, explore our extensive documentation
For working examples, see the :rocket: [contrib/](contrib/) section or these
tutorials:
* :hammer_and_wrench: [Buildroot embedded Linux](https://troglobit.com/post/2022-12-26-buildroot-demo-of-fastinit-finit/),
* :package: [Debian GNU/Linux](contrib/debian/),
* :mountain: [Alpine Linux](contrib/alpine/), and
* :milky_way: [Void Linux](contrib/void/)
* :hammer_and_wrench: [Buildroot embedded Linux][br2demo],
* :package: [Debian GNU/Linux](contrib/debian/),
* :mountain: [Alpine Linux](contrib/alpine/), and
* :milky_way: [Void Linux](contrib/void/)
> [!NOTE]
> Support for various Linux distributions does not mean Finit installs
> easily on all architectures. Bundled install scripts are examples for
> standard installations, tested on amd64 (x86_64) systems. Custom
> setups, e.g., for embedded systems, can be found in the following
> [Buildroot][] based examples: [myLinux][], [Infix][], or the plain
> [br2-finit-demo](https://github.com/troglobit/br2-finit-demo).
> Finit can run on various Linux distributions, but the bundled install
> scripts are examples only. They have been tested on amd64 (x86_64) systems
> with standard configurations.
>
> For embedded systems, see these [Buildroot][]-based examples:
> [myLinux][], [Infix][], or [br2-finit-demo][].
[finix]: https://github.com/aanderse/finix
[finix]: https://github.com/finix-community/finix
[Buildroot]: https://buildroot.org
[Infix]: https://kernelkit.github.io
[myLinux]: https://github.com/troglobit/myLinux/
[br2demo]: https://troglobit.com/post/2022-12-26-buildroot-demo-of-fastinit-finit/
[EeePC fastinit]: https://web.archive.org/web/20071208212450/http://wiki.eeeuser.com/boot_process:the_boot_process
[Claudio Matsuoka]: https://github.com/cmatsuoka
[License]: https://en.wikipedia.org/wiki/MIT_License
[License Badge]: https://img.shields.io/badge/License-MIT-teal.svg
[GitHub]: https://github.com/troglobit/finit/actions/workflows/build.yml/
[GitHub Status]: https://github.com/troglobit/finit/actions/workflows/build.yml/badge.svg
[Release]: https://github.com/finit-project/finit/releases
[Release Badge]: https://img.shields.io/github/v/release/finit-project/finit
[GitHub]: https://github.com/finit-project/finit/actions/workflows/build.yml/
[GitHub Status]: https://github.com/finit-project/finit/actions/workflows/build.yml/badge.svg
[Coverity Scan]: https://scan.coverity.com/projects/3545
[Coverity Status]: https://scan.coverity.com/projects/3545/badge.svg
[br2-finit-demo]: https://github.com/finit-project/br2-finit-demo
+86 -16
View File
@@ -1,5 +1,5 @@
AC_INIT([Finit], [4.14], [https://github.com/troglobit/finit/issues],
[finit], [https://troglobit.com/projects/finit/])
AC_INIT([Finit], [5.0-rc1], [https://github.com/finit-project/finit/issues],
[finit], [https://http://finit-project.github.io/])
AC_CONFIG_AUX_DIR(aux)
AM_INIT_AUTOMAKE([1.11 foreign subdir-objects])
AM_SILENT_RULES([yes])
@@ -12,10 +12,13 @@ AC_CONFIG_FILES([Makefile
contrib/debian/Makefile contrib/debian/finit.d/Makefile contrib/debian/finit.d/available/Makefile
contrib/void/Makefile contrib/void/finit.d/Makefile contrib/void/finit.d/available/Makefile
doc/Makefile doc/config/Makefile
dbus-1/Makefile
libink/Makefile
libsystemd/Makefile libsystemd/libsystemd.pc
man/Makefile
plugins/Makefile
src/Makefile
keventd/Makefile
system/Makefile system/10-hotplug.conf
test/test.env test/Makefile
test/lib/Makefile test/src/Makefile
@@ -37,7 +40,7 @@ AC_PROG_MKDIR_P
# Configuration.
AC_CHECK_HEADERS([termios.h sys/ioctl.h mntent.h sys/sysmacros.h])
AC_CHECK_FUNCS([strstr getopt getmntent getmntent_r])
AC_CHECK_FUNCS([strstr getopt getmntent getmntent_r close_range])
# Check for uint[8,16,32]_t
AC_TYPE_UINT8_T
@@ -48,8 +51,14 @@ AC_TYPE_UINT32_T
PKG_PROG_PKG_CONFIG
# Check for required libraries
PKG_CHECK_MODULES([uev], [libuev >= 2.2.0])
PKG_CHECK_MODULES([lite], [libite >= 2.2.0])
PKG_CHECK_MODULES([uev], [libuev >= 2.4.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
AC_ARG_ENABLE(auto_reload,
@@ -72,6 +81,15 @@ AC_ARG_ENABLE(cgroup,
AS_HELP_STRING([--disable-cgroup], [Disable cgroup v2 support, default: autodetect from /sys/fs/cgroup]),,[
enable_cgroup=yes])
AC_ARG_ENABLE(libcap,
AS_HELP_STRING([--disable-libcap], [Disable Linux capabilities support]),,[
enable_libcap=yes])
AC_ARG_ENABLE(pam,
AS_HELP_STRING([--disable-pam], [Disable PAM session support for services]),
[pam_explicit=yes],
[enable_pam=yes])
AC_ARG_ENABLE(redirect,
AS_HELP_STRING([--disable-redirect], [Disable redirection of service output to /dev/null]),,[
enable_redirect=yes])
@@ -84,6 +102,10 @@ AC_ARG_ENABLE(logrotate,
AS_HELP_STRING([--disable-logrotate], [Disable built-in rotation of /var/log/wtmp]),,[
enable_logrotate=yes])
AC_ARG_ENABLE(dbus,
AS_HELP_STRING([--disable-dbus], [Disable D-Bus support (libink + Finit object tree)]),,[
enable_dbus=yes])
AC_ARG_ENABLE(doc,
AS_HELP_STRING([--disable-doc], [Disable build and install of doc/ section]),,[
enable_doc=yes])
@@ -101,17 +123,18 @@ AC_ARG_ENABLE(all_plugins,
AS_HELP_STRING([--enable-all-plugins], [Enable all plugins, default: auto]),
enable_all_plugins=$enableval, enable_all_plugins=auto)
AC_PLUGIN([alsa-utils], [no], [Save and restore ALSA sound settings using alsactl])
AC_PLUGIN([dbus], [no], [Setup and start system message bus, D-Bus])
AC_PLUGIN([dbus], [yes], [Setup and start system message bus, D-Bus])
AC_PLUGIN([modules-load], [no], [Scans /etc/modules-load.d for modules to load])
AC_PLUGIN([modprobe], [no], [Coldplug modules using modalias magic])
AC_PLUGIN([resolvconf], [no], [Setup necessary files for resolvconf])
AC_PLUGIN([x11-common], [no], [Console setup (for X)])
AC_PLUGIN([netlink], [yes], [Basic netlink plugin for IFUP/IFDN and GW events. Can be replaced with externally built plugin that links with libnl or similar.])
AC_PLUGIN([hook-scripts], [no], [Trigger script execution from hook points])
AC_PLUGIN([hotplug], [yes], [Start udevd or mdev kernel event datamon])
AC_PLUGIN([hotplug], [no], [Start udevd or mdev kernel event datamon])
AC_PLUGIN([rtc], [yes], [Save and restore RTC using hwclock])
AC_PLUGIN([tty], [yes], [Automatically activate new TTYs, e.g. USB-to-serial])
AC_PLUGIN([urandom], [yes], [Setup and save random seed at boot/shutdown])
AC_PLUGIN([plymouth], [no], [Plymouth boot splash integration])
AC_PLUGIN([testserv], [no], [Test plugin to start test serv daemon])
# Check for extra arguments or packages
@@ -144,7 +167,7 @@ AC_ARG_WITH(sysconfig,
[sysconfig=$withval], [sysconfig=default])
AC_ARG_WITH(group,
AS_HELP_STRING([--with-group=NAME], [Group for /run/finit/socket (initctl), default: root]),
AS_HELP_STRING([--with-group=NAME], [Group for /run/finit/socket and /run/finit/bus, default: root]),
[group=$withval], [group=root])
AC_ARG_WITH(hostname,
@@ -160,17 +183,20 @@ AC_ARG_WITH(random-seed,
[random_seed=$withval], [random_seed=yes])
AC_ARG_WITH(keventd,
AS_HELP_STRING([--with-keventd], [Enable built-in keventd, default: no]),, [with_keventd=no])
AS_HELP_STRING([--with-keventd], [Enable built-in keventd device manager, default: yes]),, [with_keventd=yes])
AC_ARG_WITH(udev-rules,
AS_HELP_STRING([--without-udev-rules], [Skip install of curated udev rules under /lib/udev/rules.d, default: yes when keventd enabled]),, [with_udev_rules=yes])
AC_ARG_WITH(sulogin,
AS_HELP_STRING([--with-sulogin@<:@=USER@:>@], [Enable built-in sulogin, optional USER to request password for (default root), default: no.]),[sulogin=$withval],[with_sulogin=no])
AS_HELP_STRING([--with-sulogin@<:@=USER@:>@], [Built-in sulogin, optional USER to request password for, default: yes (root)]),[sulogin=$withval],[with_sulogin=yes sulogin=yes])
AC_ARG_WITH(watchdog,
AS_HELP_STRING([--with-watchdog@<:@=DEV@:>@], [Enable built-in watchdog, default: /dev/watchdog]),
[watchdog=$withval], [with_watchdog=no watchdog=])
AS_HELP_STRING([--with-watchdog@<:@=DEV@:>@], [Built-in watchdog, default: yes (/dev/watchdog)]),
[watchdog=$withval], [with_watchdog=yes watchdog=yes])
AC_ARG_WITH(libsystemd,
AS_HELP_STRING([--with-libsystemd], [Build replacement libsystemd library, default: yes]),
AS_HELP_STRING([--with-libsystemd], [Build replacement libsystemd library, default: no]),
[with_libsystemd=$withval], [with_libsystemd=no])
AC_ARG_WITH(hook-scripts-path,
@@ -204,6 +230,36 @@ AS_IF([test "x$enable_kernel_logging" = "xyes"], [
AS_IF([test "x$enable_cgroup" = "xyes"], [
AC_DEFINE(CGROUP2_ENABLED, 1, [Autodetect cgroup v2 support from /sys/fs/cgroup])])
AS_IF([test "x$enable_libcap" = "xyes"], [
AC_CHECK_LIB([cap], [cap_from_text], [
AC_DEFINE(HAVE_LIBCAP, 1, [Have libcap for Linux capabilities support])
LIBS="$LIBS -lcap"
], [
AC_MSG_WARN([libcap not found, capabilities support disabled])
enable_libcap=no
])
])
AS_IF([test "x$enable_pam" = "xyes"], [
AS_IF([test "x$enable_static" = "xyes"], [
AS_IF([test "x$pam_explicit" = "xyes"], [
AC_MSG_ERROR([--enable-pam does not work with --enable-static, PAM dlopen()s its modules])], [
AC_MSG_NOTICE([PAM disabled for static build, PAM dlopen()s its modules])
enable_pam=no])])
])
AS_IF([test "x$enable_pam" = "xyes"], [
AC_CHECK_HEADER([security/pam_appl.h], [], [
AC_MSG_WARN([PAM headers not found, PAM support disabled])
enable_pam=no])
AS_IF([test "x$enable_pam" = "xyes"], [
AC_CHECK_LIB([pam], [pam_start], [
AC_DEFINE(HAVE_LIBPAM, 1, [Have Linux-PAM for per-service session support])
LIBS="$LIBS -lpam"
], [
AC_MSG_WARN([libpam not found, PAM support disabled])
enable_pam=no])])
])
AS_IF([test "x$enable_fastboot" = "xyes"], [
AC_DEFINE(FAST_BOOT, 1, [Skip fsck check on filesystems listed in /etc/fstab])])
@@ -222,6 +278,12 @@ AS_IF([test "x$enable_rescue" != "xno"], [
AM_CONDITIONAL(LOGROTATE, [test "x$enable_logrotate" = "xyes"])
AS_IF([test "x$enable_dbus" = "xyes"], [
AC_DEFINE(HAVE_DBUS, 1, [Build D-Bus support via libink])])
AM_CONDITIONAL(DBUS, [test "x$enable_dbus" = "xyes"])
AM_CONDITIONAL(PAM, [test "x$enable_pam" = "xyes"])
### With features ##############################################################################
AS_IF([test "x$bash_dir" = "xyes"], [
PKG_CHECK_MODULES([BASH_COMPLETION], [bash-completion >= 2.0],
@@ -267,7 +329,7 @@ AS_IF([test "x$with_heading" != "xno"], [
AS_IF([test "x$with_group" != "xno"], [
AS_IF([test "x$group" = "xyes"], [
group=root])])
AC_DEFINE_UNQUOTED(DEFGROUP, "$group", [For /run/finit/socket])
AC_DEFINE_UNQUOTED(DEFGROUP, "$group", [For /run/finit/socket and /run/finit/bus])
AS_IF([test "x$with_hostname" != "xno"], [
AS_IF([test "x$hostname" = "xyes"], [
@@ -297,7 +359,11 @@ AS_IF([test "x$rtc_file" != "xno"], [
AC_DEFINE_UNQUOTED(RTC_FILE, "$rtcfile_path", [Save and restore system time from this file if /dev/rtc is missing.])],[
AC_DEFINE_UNQUOTED(RTC_FILE, NULL)])
AS_IF([test "x$with_keventd" != "xno"], [with_keventd=yes])
AS_IF([test "x$with_keventd" != "xno"], [
with_keventd=yes
PKG_CHECK_MODULES([blkid], [blkid],, [
AC_MSG_ERROR([libblkid (util-linux) is required to build keventd.
Install the dev package (e.g. libblkid-dev) or disable keventd with --without-keventd])])])
AS_IF([test "x$with_sulogin" != "xno"], [
AS_IF([test "x$sulogin" = "xyes"], [
@@ -330,6 +396,7 @@ AS_IF([test "x$with_plugin_path" != "xno"], [
AM_CONDITIONAL(BASH, [test "x$bash_dir" != "xno"])
AM_CONDITIONAL(STATIC, [test "x$enable_static" = "xyes"])
AM_CONDITIONAL(KEVENTD, [test "x$with_keventd" != "xno"])
AM_CONDITIONAL(UDEV_RULES, [test "x$with_keventd" != "xno" -a "x$with_udev_rules" != "xno"])
AM_CONDITIONAL(SULOGIN, [test "x$with_sulogin" != "xno"])
AM_CONDITIONAL(WATCHDOGD, [test "x$with_watchdog" != "xno"])
AM_CONDITIONAL(LIBSYSTEMD, [test "x$with_libsystemd" != "xno"])
@@ -413,12 +480,15 @@ Optional features:
Install doc/..........: $enable_doc
Install contrib/......: $enable_contrib
Bash completion.......: $bash_completion_dir
Built-in keventd......: $with_keventd
Built-in keventd......: $with_keventd (blkid: $blkid_LIBS)
Built-in sulogin......: $with_sulogin $sulogin
Built-in watchdogd....: $with_watchdog $watchdog
Built-in logrotate....: $enable_logrotate
D-Bus support (libink): $enable_dbus
Replacement libsystemd: $with_libsystemd
Use cgroup v2.........: $enable_cgroup
Use libcap............: $enable_libcap
Use PAM...............: $enable_pam
Parse kernel cmdline..: $enable_kernel_cmdline
Keep kernel logging...: $enable_kernel_logging
Skip fsck check.......: $enable_fastboot
+16 -6
View File
@@ -3,24 +3,34 @@
## Runlevel to start after bootstrap, runlevel 'S'
# Default is 2
#runlevel 2
#runlevel = 2
# Top-level cgroups and their default settings. All groups mandatory
# but more can be added, max 8 groups in total currently. The cgroup
# 'root' is also available, reserved for RT processes. Settings are
# as-is, only one shorthand 'mem.' exists, other than that it's the
# cgroup v2 controller default names.
#cgroup init cpu.weight:100
#cgroup user cpu.weight:100
#cgroup system cpu.weight:9700
#cgroup init { cpu.weight = 100 }
#cgroup user { cpu.weight = 100 }
#cgroup system { cpu.weight = 9700 }
# Task to run if ctrl-alt-del is pressed. This condition is asserted by
# 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.
# 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
# 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)
# 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
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
tty [12345] /dev/ttyS0 noclear
tty [12345] /dev/tty1 noclear nowait
tty [2345] /dev/tty2 noclear nowait
tty [2345] /dev/tty3 noclear nowait
tty ttyS0 {
runlevel = "12345"
device = "/dev/ttyS0"
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
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
#module fbcon
#module button
#module evdev
#module mousedev
#module 8139cp
#module softdog
#modules = { "fbcon", "button", "evdev", "mousedev", "8139cp", "softdog" }
+8 -1
View File
@@ -1,2 +1,9 @@
# 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 klogd as soon as possible, but after syslogd
service [S12345] env:-/etc/conf.d/syslog syslogd -n $SYSLOGD_OPTS -- Syslog daemon
service [S12345] <pid/syslogd> env:-/etc/conf.d/klogd klogd -n $KLOGD_OPTS -- Kernel log daemon
service syslogd {
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'
# Default is 2
#runlevel 2
#runlevel = 2
# Top-level cgroups and their default settings. All groups mandatory
# but more can be added, max 8 groups in total currently. The cgroup
# 'root' is also available, reserved for RT processes. Settings are
# as-is, only one shorthand 'mem.' exists, other than that it's the
# cgroup v2 controller default names.
#cgroup init cpu.weight:100
#cgroup user cpu.weight:100
#cgroup system cpu.weight:9700
#cgroup init { cpu.weight = 100 }
#cgroup user { cpu.weight = 100 }
#cgroup system { cpu.weight = 9700 }
# Task to run if ctrl-alt-del is pressed. This condition is asserted by
# 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.
# 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
# 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)
# 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
# "--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
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
# 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
service [2345] <pid/syslogd> /lib/elogind/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
tty [12345] /dev/tty1 linux noclear nowait
tty [2345] /dev/tty2 linux nowait
tty [2345] /dev/tty3 linux nowait
tty [2345] /dev/tty4 linux nowait
tty [2345] /dev/tty5 linux nowait
tty [2345] /dev/tty6 linux nowait
tty tty1 {
runlevel = "12345"
device = "/dev/tty1"
term = "linux"
noclear = true
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
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
#module fbcon
#module button
#module evdev
#module loop
#module psmouse
#module mousedev
#module 8139cp
#modules = { "fbcon", "button", "evdev", "loop", "psmouse", "mousedev", "8139cp" }
@@ -1,2 +1,6 @@
# 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
# 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)
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
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
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).
# It is also an example of a service without a -- description.
# 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
#
host testbed
hostname = "testbed"
# Linux kernel modules to load
module button
module evdev
module loop
module psmouse
modules = { "button", "evdev", "loop", "psmouse" }
# Top-level cgroups and their default settings. All groups mandatory
# but more can be added, max 8 groups in total currently. The cgroup
# 'root' is also available, reserved for RT processes. Settings are
# as-is, only one shorthand 'mem.' exists, other than that it's the
# cgroup v2 controller default names.
cgroup init cpu.weight:100
cgroup user cpu.weight:100
cgroup system cpu.weight:9700 cpu.max:50000
cgroup init { cpu.weight = 100 }
cgroup user { cpu.weight = 100 }
cgroup system { cpu.weight = 9700 cpu.max = 50000 }
# Example extra cgroup
cgroup maint cpu.weight:100
cgroup maint { cpu.weight = 100 }
# Runlevel to start after bootstrap, runlevel 'S'
runlevel 2
runlevel = 2
# Network bringup script
network service networking start
network = "service networking start"
# 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 size:100k count:4
log {
size = 100k
count = 4
}
# Virtual consoles to start getty on
tty /dev/tty1
tty /dev/tty2
tty /dev/tty3
tty tty1 { device = "/dev/tty1" }
tty tty2 { device = "/dev/tty2" }
tty tty3 { device = "/dev/tty3" }
# Task to run if ctrl-alt-del is pressed. This condition is asserted by
# 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.
# 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
#task [S] /etc/init.d/keyboard-setup start -- Setting up preliminary keymap
#task [S] /etc/init.d/acpid start -- Starting ACPI Daemon
#task [S] /etc/init.d/kbd start -- Preparing console
#run [2] /etc/init.d/networking start -- Start networking
#task keyboard-setup {
# description = "Setting up preliminary keymap"
# runlevel = "S"
# 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
# klgod and syslogd are placed in the maint cgroup
cgroup.maint
service [2345] <pid/syslogd> klogd -n -- Kernel logging server
service [2345] syslogd -n -- Syslog server
# klogd and syslogd are placed in the maint cgroup. There is no
# "current cgroup" to switch in this format, every block names its own.
service klogd {
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)
cgroup.system
service [3] cgroup:cpu.weight:250,mem.max:10M gdm -- GNOME Display Manager
# gdm gets its own limits inside the system group, which is where
# services end up when no cgroup block is given.
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
# to user cgroup with cpu.weight:250. Default for services is system
# group. Default weight in user group is 100 (above). Max length of
# cgroup argument is (currently) 63 chars.
#
# Notice alternative syntax where current cgroup is only set for this
# particular service.
service [2345] cgroup.user:cpu.weight:250,mem.max:10M env:-/etc/default/ssh /usr/sbin/sshd -D $SSHD_OPTS -- OpenSSH daemon
# to user cgroup with cpu.weight 250. Default weight in the user group
# is 100 (above).
service sshd {
description = "OpenSSH daemon"
runlevel = "2345"
envfile = "-/etc/default/ssh"
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
# 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
# a dependency on a system default route (gateway) to be set. A single
# <!> at the beginning means ntpd does not respect SIGHUP for restart.
service [2345] <!net/route/default> log:/var/log/ntpd.log ntpd -n -l -I eth0 -- NTP daemon
# a dependency on a system default route (gateway) to be set. ntpd
# does not respect SIGHUP, so Finit restarts it on reload instead.
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
# runparts /etc/start.d
#runparts = "/etc/start.d"
# For multiple instances of the same service, add :ID somewhere between
# the service/run/task keyword and the command.
service :80 [2345] /sbin/httpd -f -h /http -p 80 -- Web server
service :8080 [2345] /sbin/httpd -f -h /http -p 8080 -- Old web server
# For multiple instances of the same service, add :ID to the block title.
service httpd:80 {
description = "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'
# Default is 2
#runlevel 2
#runlevel = 2
# Top-level cgroups and their default settings. All groups mandatory
# but more can be added, max 8 groups in total currently. The cgroup
# 'root' is also available, reserved for RT processes. Settings are
# as-is, only one shorthand 'mem.' exists, other than that it's the
# cgroup v2 controller default names.
#cgroup init cpu.weight:100
#cgroup user cpu.weight:100
#cgroup system cpu.weight:9700
#cgroup init { cpu.weight = 100 }
#cgroup user { cpu.weight = 100 }
#cgroup system { cpu.weight = 9700 }
# Task to run if ctrl-alt-del is pressed. This condition is asserted by
# 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.
# 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
# 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)
# 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
tty [12345] /dev/tty1 noclear nowait
tty [2345] /dev/tty2 noclear nowait
tty [2345] /dev/tty3 noclear nowait
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
}
+5 -1
View File
@@ -1,2 +1,6 @@
# 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
#module fbcon
#module button
#module evdev
#module mousedev
#module 8139cp
module softdog
# Other candidates: fbcon, button, evdev, mousedev, 8139cp
modules = { "softdog" }
+7 -1
View File
@@ -1,2 +1,8 @@
# 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
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 klogd as soon as possible, but after syslogd
service [S12345] syslogd -n -- Syslog daemon
service [S12345] <pid/syslogd> klogd -n -- Kernel log daemon
service syslogd {
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"
}
+2
View File
@@ -0,0 +1,2 @@
/Makefile
/Makefile.in
+6
View File
@@ -0,0 +1,6 @@
EXTRA_DIST = org.finit.conf
if DBUS
dbuspolicydir = $(datadir)/dbus-1/system.d
dist_dbuspolicy_DATA = org.finit.conf
endif
+63
View File
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<!--
System-bus policy for finit.
finit (PID 1, root) owns the `org.finit` well-known name. The
policy below lets unprivileged callers introspect, read
properties, and query service / condition state, but disallows
every state-changing method at the broker. finit *also* enforces
per-method authorisation itself (LINK_METHOD_PRIVILEGED), so
defense-in-depth: even if a permissive policy is installed by
mistake, finit rejects unprivileged state changes. It reads the
caller from SO_PEERCRED on the local bus, and on the system bus it
asks the broker who sent each privileged call, via
GetConnectionUnixUser.
-->
<busconfig>
<!-- Only finit (root) may own the org.finit bus name. -->
<policy user="root">
<allow own="org.finit"/>
<allow send_destination="org.finit"/>
<allow receive_sender="org.finit"/>
</policy>
<!-- Everyone may introspect, read properties, and call the
read-only Manager1 / Cond1 methods. Signals fan out
unconditionally. -->
<policy context="default">
<allow send_destination="org.finit"
send_interface="org.freedesktop.DBus.Introspectable"/>
<allow send_destination="org.finit"
send_interface="org.freedesktop.DBus.Peer"/>
<allow send_destination="org.finit"
send_interface="org.freedesktop.DBus.Properties"
send_member="Get"/>
<allow send_destination="org.finit"
send_interface="org.freedesktop.DBus.Properties"
send_member="GetAll"/>
<allow send_destination="org.finit"
send_interface="org.finit.Manager1"
send_member="ListServices"/>
<allow send_destination="org.finit"
send_interface="org.finit.Manager1"
send_member="GetService"/>
<allow send_destination="org.finit"
send_interface="org.finit.Cond1"
send_member="Get"/>
<allow send_destination="org.finit"
send_interface="org.finit.Cond1"
send_member="List"/>
<allow send_destination="org.finit"
send_interface="org.finit.Cond1"
send_member="Dump"/>
<allow receive_sender="org.finit"/>
</policy>
</busconfig>
+336 -1
View File
@@ -3,6 +3,337 @@ Change Log
All relevant changes are documented in this file.
[5.0][UNRELEASED]
--------------------
### Changes
- keventd is now a full device manager, promoted from the power supply
monitor it was in v4, and the device management defaults have swapped:
keventd is enabled by default (`--without-keventd` opts out, libblkid is
required to build it), while the hotplug plugin, which runs an external
`udevd`, `mdevd`, or `mdev`, is now opt-in. With the plugin enabled,
keventd steps back to passive mode and monitors only power supply
events, as before. As the system device manager it replaces mdev,
mdevd, and udevd, and handles:
* udev rules: an engine covering most of the udev(7) grammar --
parent-chain matching, `PROGRAM`/`RESULT`, `IMPORT`, `RUN`,
`GOTO`/`LABEL`, and the standard substitutions -- reading the usual
`udev/rules.d` directories under `/lib`, `/run`, and `/etc` with
udev's precedence and masking. A curated ruleset derived from eudev
3.2.14 is installed by default, `--without-udev-rules` skips it.
See [Device Manager](keventd.md) for the supported subset and the
few deliberate divergences
* builtins used by the ruleset: `blkid`, `hwdb`, `input_id`, `kmod`,
`net_id`, `path_id`, and `usb_id`
* device nodes with per-subsystem default permissions, persistent
symlinks (`/dev/disk/by-uuid`, `by-label`, `by-id`, `by-path`, plus
`by-id`/`by-path` for input, serial, sound, v4l, and tape), kernel
firmware load requests, and module loading from `MODALIAS`
* predictable network interface names via the eudev ruleset,
`net.ifnames=0` on the kernel command line keeps classic names.
**Note:** switching a system over to keventd can rename its NICs
* coldplug: as device manager keventd starts with `-c`, replaying add
events for hardware present at boot. The `pid/keventd` condition
is deferred until the coldplug queue has drained, so services
gating on it can assume `/dev` is populated and symlinks are live
* `keventd -S`, the `udevadm settle` equivalent, asks the running
daemon over D-Bus and falls back to polling the kernel's uevent
sequence number
* libudev compatibility: per-device properties in `/run/udev/data`
and processed uevents rebroadcast to netlink group 0x4, so
libudev-zero consumers, e.g. compositors and libinput, see hotplug
events
* new conditions for the service engine: `dev/<node>` for device
nodes, `class/<subsystem>/<name>` for sysfs class devices without a
node (LEDs, IIO, DSA ports), and `driver/<name>`, asserted while a
driver is bound to at least one device
- Generated `/run/finit/system/*.conf` files for bundled services (keventd,
watchdogd, runparts) are now read before `/lib/finit/system/*.conf` instead
of after. Built-ins can now be referenced in `if:` statements from system
.conf files and overridden by name from both `/lib/finit/system` and
`/etc/finit.d`, the latter is still always read last
- The `dbus.so` plugin, which starts an external `dbus-daemon`, is now enabled
by default. It does nothing on systems without a `dbus-daemon` installed,
and `--disable-dbus-plugin` opts out. The daemon it used to register from C
now lives in `20-dbus.conf`, and the directories it needs in
`tmpfiles.d/dbus.conf`, so both can be overridden from `/etc` like any other
system file. Those directories are no longer chowned to `messagebus`,
matching how Finit ships directories for other daemons
- The bundled `sulogin` and `watchdogd` are now built and installed by
default, like keventd and the D-Bus support. `--without-sulogin` and
`--without-watchdog` opt out. Note, Finit starts `watchdogd` by itself
when the WDT device node exists
- The `tty` block takes `passenv`, which the line-based format has had since
v4.4 (issue #286) and the block format was missing
- New `provides` setting for run/task/service/sysv blocks, naming conditions
the service asserts in addition to its own `pid/<ident>`. Lets variants of
one service, qualified with `if`, share the barrier condition downstream
services wait for, which a block title alone could not express. Any
namespace is allowed, and a second claim on the same condition is refused
with a warning naming the owner
- The `command` setting in a block takes a list of candidates for the same
service, `command = { "/lib/systemd/systemd-udevd", "-udevd" }`, and Finit
starts the first one it finds. The line-based format could only express
this by repeating the whole stanza per candidate
- New `pam = "NAME"` setting for run/task/service/sysv blocks, runs the
service inside a PAM session set up from `/etc/pam.d/NAME`, so the `session`
stack applies to the process that becomes the daemon, e.g. limits from
`pam_limits`, which override a per-service `rlimit`. Issue #420
- Finit now ships with a built-in brokerless D-Bus implementation, **libink**,
exposing the running init system as a peer on its own private bus at
`/run/finit/bus`, and -- opportunistically -- registering `org.finit` on the
standard system bus when a `dbus-daemon` is reachable. No external
`libdbus`/`sd-bus`/`GIO` dependency. The bus implements the stock
`org.freedesktop.DBus`, `Peer`, `Introspectable`, and `Properties`
interfaces. See [D-Bus Integration](dbus.md) for the full surface, build
flag, and `dbus-send`/`dbus-monitor` examples
- New `org.finit.Manager1` interface at `/org/finit/manager`: `ListServices`,
`GetService`, `Start`/`Stop`/`Restart`/`Reload`, `SetRunlevel`, `SetDebug`,
`Signal`, `Suspend`, and the `Reboot`/`Halt`/`Poweroff` triplet, which take
a timeout argument arming the emergency shutdown bypass like `initctl -t`.
Read-only properties `Runlevel`, `PrevRunlevel`, `Version`; signals
`ServiceStateChanged (sss)` and `RunlevelChanged (ss)`
- New `org.finit.Service1` interface at `/org/finit/service/<encoded>`, one
object per loaded service, with `Start`/`Stop`/`Restart`/`Reload` for
working off an object handle rather than passing the identity string around
- New `org.finit.Cond1` interface at `/org/finit/cond`: `Get`, `Set`, `Clear`,
`List`, `Dump` for [user-defined conditions](conditions.md), with a
`ConditionChanged (ss)` signal
- keventd serves `org.finit.Device1` on its own bus at `/run/keventd/bus`:
`Settle`, `Trigger`, `Info`, `RulesReload`, queue-state properties, and a
`DeviceProcessed (ss)` signal. The `udevadm` settle/trigger/info
equivalents are thereby bus methods and `keventd -S` asks the running daemon
first. Finit also gained a `Manager1.ConfigReloaded` signal, fired when
`initctl reload` completes, for external condition providers and monitoring
- Privileged D-Bus methods accept root and members of the `--with-group`
group; everyone else is refused. On the local bus the caller's uid and
groups come straight from the kernel (`SO_PEERCRED` + `SO_PEERGROUPS`), so
the check never blocks PID 1 on an NSS lookup. Over the system bus the uid
is resolved from the broker (`GetConnectionUnixUser`) and privileged methods
are root-only. Read-only methods are open
- `initctl` now transparently routes through D-Bus when the bus is reachable,
with the legacy `INIT_SOCKET` transport as a fallback: `start`, `stop`,
`restart`, `reload`, `reload <svc>`, `reboot`, `halt`, `poweroff`,
`suspend`, `debug`, `signal`, `runlevel`, and `cond {get,set,clr}` all use
the new path. Two new subcommands show up that have no legacy equivalent:
* `initctl monitor` -- streams every signal on the bus to the terminal,
one line per delivery (`HH:MM:SS iface.member(args)`), until
interrupted. Same idea as `dbus-monitor`, but scoped to Finit and with
no address plumbing required.
* Issuing `initctl cond set/clr` over D-Bus also fires the
`Cond1.ConditionChanged` signal, so observers see user-driven state
changes the same way they see service-driven ones.
- Restart log now spells out the signal name and flags core dumps,
e.g. `killed by SIGKILL` or `killed by SIGSEGV, core dumped`, in place of
the bare numeric `by signal: N`. Gives operators a much stronger breadcrumb
when a daemon dies unexpectedly
- Service `user:` and `group:` names, including supplementary groups, can
now be up to 63 characters, up from 15. Longer names were silently
truncated before the lookup, so the service never started. By Anees Rehman
- `stop:` and `reload:` scripts take a timeout of their own, `stop:5,/path`,
defaulting to the kill delay as before. Such a line used to crash Finit at
config load
- Shutdown now stops after the first successful remount of `/` read only.
The plain `mount -n -o remount,ro /`, which reads `/etc/fstab`, is only
used when both device-agnostic forms fail, the same order as Debian's
`umountroot`. By Anees Rehman
### Fixes
- Reject a .conf file that declares the same block title twice. The title is
the service identity and libConfuse merges two sections sharing one, so the
file loaded as a single service holding a mix of both declarations,
silently. This hit `system/10-hotplug.conf`, where the two `udevd` blocks
left only the second command, dropping the service on systems that only have
`systemd-udevd`. The same title in another file is still how a system .conf
is overridden
- Remove stale daemon-owned (`pid:!`) pidfiles after unclean exits. When a
daemon dies via SIGKILL/OOM/segfault, or exits early during startup, its
pidfile lingers and can prevent the next instance from starting --
`dbus-daemon` for example refuses to start when its pidfile already exists,
so Finit's restart loop gives up. Finit now drops the file when it still
names the just-reaped PID and that PID is no longer alive (the liveness
check guards against PID reuse). This is a controlled exception to the
long-standing rule that Finit does not touch service-owned pidfiles
- Fix misspelled `SIGUNKOWN` returned by `sig_name()` for unknown signal
numbers, now spelled correctly as `SIGUNKNOWN`. Surfaced by the new restart
log above
- Fix mode of existing `d`/`D` directories in tmpfiles, the chmod branch
never ran because the directory creation swallowed `EEXIST`. Mode and owner
of `d`/`D`, `e`, and `f`/`F` entries, and of the service directories
`mksubsysd()` creates, are now set through an open fd and only when they
differ, so an immutable directory like `/var/empty` no longer warns on every
boot. By Sam Brkopac
- Fix a lost reload for a service paused mid-reload by a condition in
flux. A second reload arriving meanwhile re-parsed the unchanged .conf
and cleared the pending mark, so the service resumed without ever being
reloaded. Seen on Infix, where sshd kept its old listen addresses after
a change that landed as two reloads in a row
[4.17][] - 2026-04-28
---------------------
### Changes
- Add Plymouth boot splash plugin to manage the `plymouthd` lifecycle across boot,
`switch_root`, and shutdown. Activated by the `splash` kernel command line
argument. By Aaron Andersen
- Add `tty:` option for run/task/service stanzas to acquire a controlling
terminal. The device is opened as stdin, set as the session's controlling TTY
via `TIOCSCTTY`, and `TERM` is set automatically — `linux` for virtual terminals
and `vt102` for serial lines. By Ollie Gutierrez
- Improve console output atomicity at boot and shutdown: description and status
(`[ OK ]` / `[FAIL]`) are emitted in a single write so kernel messages cannot
split them. If output has been scrolled away, the description is re-printed
before the final status line. Console buffer is drained with `tcdrain(2)` and
ANSI attributes are reset before handing control back to the kernel at
shutdown/reboot
### Fixes
- Fix reverse-dependency condition handling on reload: when a service without
SIGHUP support is reloaded, its condition is now cleared immediately so
dependents transition to `STOPPING` before `SIGTERM` is sent to the parent,
preventing them from crashing with an incremented restart counter
- Fix `/var/tmp` mode in `tmpfiles.d/var.conf` from `0777` to `1777` (sticky bit)
to match `/tmp`, by Aaron Andersen
- Fix potential memory corruption in config parser: stale pointer captures of
`PATH` and `SHELL` at startup could dangle after environment modification;
`conf_reset_env()` now reads from the live environment instead, by Vasily Zubko
- Fix plugin name registration: replace `__FILE__` macro with explicit string
literals in all plugin structs and debug messages, preventing subtle build-
system–dependent macro expansion issues, by Vasily Zubko
[4.16][] - 2026-02-27
---------------------
### Changes
- Add `initctl switch-root` command and plugin hook point. Useful for
systems requiring early boot tasks like LUKS unlock, LVM activation,
or network boot before mounting the real root, by Aaron Andersen
- Set USER and LOGNAME environment variables when dropping privileges.
Fixes issues with software like rootless Podman that determines user
identity from environment variables, by Aaron Andersen
- Add `remain:yes` option for run/task oneshot commands, similar to the
systemd `RemainAfterExit=yes`, by Aaron Andersen
- Clear service conditions on `initctl reload NAME` to ensure dependent
services are properly updated
- Run service `stop:` and `reload:` scripts as non-blocking processes,
preventing Finit from stalling on long-running helper scripts
- Guard shutdown with timer watchdog to detect and debug shutdown hangs
- Add `~` condition prefix to propagate reload from a dependency to the
dependent service. E.g., `<!~pid/netd>` means not only a regular
condition, but when `netd` reloads, restart this service too. Similar
to systemd's directive `PropagatesReloadTo=`, but declared on the
consumer side. Issue #416
- `tmpfiles`: add support for `-E` and `--exclude-prefix` flags
contributed by Aaron Andersen
### Fixes
- Fix #464: invalid user:group examples in cgroups.md
- Fix #466: elogind path for Debian-based distros, by Jackie Liu
- Fix #467: TTY services stuck in restart state after non-zero exit.
Throttling logic introduced in v4.15 had duplicate checks causing
infinite timer loop, and TTYs lacked default restart timeout
- Fix #475: clear pid condition on service collection to fix stale
deps. When a service crashes (SIGKILL), the RUNNING → HALTED path
bypasses STOPPING where `cond_clear()` is normally called, leaving
dependents stuck
- Fix #476: dependents not restarted after SIGHUP reload of service in
dependency chain. Add `service_step_all()` at end of reload cycle to
guarantee convergence after conditions are reasserted. See also the
new `~` condition prefix (above) to propagate reload to dependents
- Fix #478: fix `<dev/foo>` conditions at boot, conditions that exist
before inotify was set up. Found and fixed by Mattias Walström
- Fix reload of SIGHUP-capable services incorrectly disrupting their
dependents. E.g., `initctl reload syslogd` would stop dbus, dnsmasq,
etc. even though syslogd handles SIGHUP gracefully and its PID persists
- Only remove managed pidfiles in service cleanup. For SysV services
with `pid:!/path`, the pidfile belongs to the service itself and Finit
should not delete it
- Silence spurious cgroup warnings for short-lived processes where the
kernel reaps the child before cgroup assignment completes
- Fix handling of already-mounted cgroups in `cgroup_init()`, can occur
after switch_root or in container environments
- Improve cgroups documentation clarity, grammar, and examples
[4.15][] - 2026-01-01
---------------------
The Finit project now has a new home page: <https://finit-project.github.io>
### Changes
- Change default reboot to SOC reset from WDT reset, issue #460
- Bump required versions of libite (v2.6.2) and libuev (v2.4.1)
- Allow longer service identifiers (`:id`), up to 64 characters
- Allow skipping bootstrap wait (180 sec) with Ctrl-C
- Unlock `initctl` commands start/stop/restart/reload service during bootstrap,
shutdown, and reboot. Also, allow `initctl reload` in bootstrap. This makes
it possible to start manual:yes type services at botostrap, for example, which
has been a common feature request
- Add support for `clone3()` syscall with atomic cgroup assignment, from Linux
kernel 5.3+. Eliminates race conditions where forked processes (e.g., logger
processes) ended up in wrong cgroups. Falls back to `fork()` on older kernels
- Add support for cgroup delegation, useful for, e.g., container runtimes
creating sub-groups for the container instance's processes
- Add cgroup leaf name override: `cgroup.system,name:udevd` allows using
descriptive names like `/system/udevd/` instead of filename derived names
like `/system/10-hotplug/`. Syntax supports combining with other options:
`cgroup.system,name:foo,delegate,cpu.max:10000`
- Add support for Linux capabilities, allowing services to run with minimal
required privileges instead of running as root. Uses the modern IAB API
(Inheritable, Ambient, Bounding) from libcap. By Aaron Andersen
- Add support for supplementary groups for services. Uses the syntax
`@user:group,sup1,sup2` to explicitly specify supplementary groups, in
addition to reading group membership from `/etc/group`. By Aaron Andersen
- Add CPU throttled information to `initctl` output, showing when a service
has been throttled due to reaching its CPU quota
- Add memory field to `initctl --json` status output for cgroup-enabled
services
- Default to user and group `root` (UID 0) for services
- Always check that service user and group resolve to valid UID/GID pairs,
otherwise log error and refuse to start service
- Hide cursor at boot and shutdown for cleaner visual experience
- Rename `tty` services early from `init` -> `getty` even though no getty
process has been started yet. This to aid in boot timing detection using
tools like `bootchartd` (BusyBox version or bootchart2 project) when Finit
only shows *"Please press Enter to activate this console."*
- Services with `respawn` option (including TTYs) now delay retry on non-zero
exit codes instead of immediate respawn. Prevents busy-loop when service
fails repeatedly, which on some systems may have a crippling effect
### Fixes
- Fix #453: RTC plugin fixes by Ming Liu, Atlas Copco
- Fix #455: ANSI escape sequences logged by tools using glib logging,
caused by use of PTY instead of pipe for stdout/stderr redirection
- Fix #462: `/dev/pts` mounted with wrong mode (400 instead of 620), causing
permission issues with pseudo-terminals
- getty: fix terminal scrollback issues after login on console TTY. Mouse
scroll wheel and Shift+PgUp/PgDn sometimes would not work properly
- Fix flickering and artifacts in `initctl top`
- Fix file descriptor leak in `initctl top`
- Fix incorrect "max" limit reporting in `initctl [top|cgroup|--json]` output
for child cgroups. Resolves effective limits by walking the cgroup hierarchy
- Fix issue with running Finit under `bootchartd` (bootchart2 project), always
set `PATH` and `SHELL` envs early, before attempting to mount `/proc` et al
- Always reset ownership and permissions on TTY device nodes before launching getty
- Ensure mount/unmount skips `noauto` entries
[4.14][] - 2025-08-29
@@ -1852,7 +2183,11 @@ Major bug fix release.
* Initial release
[UNRELEASED]: https://github.com/troglobit/finit/compare/4.12...HEAD
[UNRELEASED]: https://github.com/finit-project/finit/compare/4.17...HEAD
[4.17]: https://github.com/finit-project/finit/compare/4.16...4.17
[4.16]: https://github.com/finit-project/finit/compare/4.15...4.16
[4.15]: https://github.com/finit-project/finit/compare/4.14...4.15
[4.14]: https://github.com/finit-project/finit/compare/4.13...4.14
[4.13]: https://github.com/troglobit/finit/compare/4.12...4.13
[4.12]: https://github.com/troglobit/finit/compare/4.11...4.12
[4.11]: https://github.com/troglobit/finit/compare/4.10...4.11
+1
View File
@@ -3,6 +3,7 @@ EXTRA_DIST = AUTHORS ChangeLog.md
docsdir := @docdir@/doc
dist_docs_DATA = build.md cmdline.md commands.md conditions.md \
dbus.md \
distro.md example.md features.md index.md initctl.md \
keventd.md plugins.md requirements.md runlevels.md \
runparts.md service.md signals.md state-machine.md \
+119 -10
View File
@@ -2,10 +2,23 @@ Building Finit
==============
Finit comes with a traditional configure script to control features and
optional plugins to enable. It depends on two external libraries:
optional plugins to enable. It depends on three external libraries:
- [libuEv][], the event loop
- [libite][] (-lite), much needed frog DNA
- [libConfuse][], the parser behind the .conf format
The bundled device manager, keventd, needs a fourth: libblkid from
[util-linux][]. It reads the filesystem UUID and label off block
devices, which is what the curated rules name the `/dev/disk/by-uuid/`
and `/dev/disk/by-label/` symlinks after. keventd is enabled by
default, so this is a hard requirement unless you build with
`--without-keventd`.
PAM session support for services is built by default. It needs
[Linux-PAM][] (-lpam); the build falls back to no PAM support when that
library is missing. A static build, `--enable-static`, disables PAM
too, since libpam loads its modules with `dlopen()`.
> [!IMPORTANT]
> Most free/open source software packages that use `configure` default
@@ -22,6 +35,8 @@ version of the above mentioned libraries. Currently required versions:
- libite v2.2.0, or later
- libuEv v2.2.0, or later
- libConfuse v3.3, or later
- libblkid, any version, when keventd is enabled
Configure
@@ -44,21 +59,56 @@ Below are a few of the main switches to configure:
built-ins (.o files) and all external libraries, except the C library
will be linked statically.
* `--disable-pam`: Opt out of Finit's built-in PAM session support,
enabled by default, which lets a service declare `pam = "name"` and
run inside a session set up from `/etc/pam.d/name`. Needs libpam,
falls back to disabled when it is missing, and is also disabled for
`--enable-static` builds. Asking for `--enable-pam` and
`--enable-static` together is an error rather than a fallback. See
[PAM Sessions](config/pam.md)
* `--enable-kernel-cmdline`: Enable Finit pre-4.1 parsing of init args from
`/proc/cmdline`, this is *not recommended* since Finit may be running as the
init for container apps that can see the host's `/proc` filesystem
* `--disable-dbus`: Opt out of Finit's built-in D-Bus support, enabled by
default. See [D-Bus Integration](dbus.md) for what it provides. Not to
be confused with `--disable-dbus-plugin` below, which only governs
starting an external `dbus-daemon`
* `--enable-alsa-utils-plugin`: Enable the optional `alsa-utils.so` sound plugin.
* `--enable-dbus-plugin`: Enable the optional D-Bus `dbus.so` plugin.
* `--disable-dbus-plugin`: Drop the `dbus.so` plugin, which launches
`dbus-daemon` at boot. Enabled by default; the plugin does nothing on
a system that has no `dbus-daemon` installed, so opting out is only
needed to keep init from starting a bus on a system that has one.
Unrelated to the built-in bus, see `--disable-dbus` above.
* `--enable-resolvconf-plugin`: Enable the `resolvconf.so` optional plugin.
* `--enable-x11-common-plugin`: Enable the optional X Window `x11-common.so` plugin.
* `--with-sulogin`: Enable bundled `sulogin` program. Default is to use the
system `sulogin(8)`. The sulogin shipped with Finit *allows password-less*
login if the `root` user is disabled or has no password at all.
* `--without-keventd`: Drop the bundled device manager, for systems that
run mdev, mdevd, or udevd instead. Enabled by default, and the only
thing that pulls in libblkid. See [Bundled Device Manager](keventd.md).
* `--without-sulogin`: Drop the bundled `sulogin`, enabled by default,
and use the system `sulogin(8)` instead. The one shipped with Finit
*allows password-less* login if the `root` user is disabled or has no
password at all. `--with-sulogin=USER` prompts for another user's
password than `root`'s.
* `--without-watchdog`: Drop the bundled `watchdogd`, enabled by default
on `/dev/watchdog`, `--with-watchdog=DEV` selects another device.
Finit starts it when the device node exists, see [Watchdog](watchdog.md).
* `--with-libsystemd`: Build the replacement `libsystemd`, which lets
programs that use `sd_notify()` talk to Finit without systemd. Off by
default because it installs `libsystemd.so.0` in `$libdir`: on a host
that has the real one, ours shadows it in the loader cache and every
program linking it, `dbus-daemon` and `udevd` included, loses the
symbols it needs. Safe on a systemd-free system, which is the point
of it.
For more configure flags, see <kbd>./configure --help</kbd>
@@ -85,8 +135,7 @@ Then configure, build and install:
```shell
$ ./configure --prefix=/usr --exec-prefix= \
--sysconfdir=/etc --localstatedir=/var \
--with-keventd --with-watchdog
--sysconfdir=/etc --localstatedir=/var
$ make
.
.
@@ -114,6 +163,60 @@ Linux config to:
> `/etc/fstab`.
Testing
-------
`make check` runs the test suite in a private namespace, so it is safe
on a running system. It needs `unshare` and, on Ubuntu, unprivileged
user namespaces enabled:
```shell
sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0
make check
```
The D-Bus message parser is the only place in Finit where bytes off a
socket become pointers, so it also has a fuzz target. `make check`
runs it as a fixed sweep, which takes milliseconds and needs nothing
beyond the normal build. To fuzz it properly, build it with clang and
libFuzzer:
```shell
clang -fsanitize=fuzzer,address -DLINK_FUZZ_LIBFUZZER -D_GNU_SOURCE \
-I libink -I . -o fuzz-msg-parse \
test/src/fuzz-msg-parse.c libink/*.c
mkdir -p .fuzz-corpus
./fuzz-msg-parse .fuzz-corpus -max_total_time=300
```
Give it a corpus directory as above and it saves what it learns there,
so the next run picks up where this one left off instead of starting
cold. Nothing writes to it unless you name it: without the argument
libFuzzer keeps everything in memory and the run leaves only crashes
behind. `make distclean` clears the corpus and the target.
Once a corpus has grown, most of it reaches code some earlier input
already reached. Minimise it:
```shell
./fuzz-msg-parse -merge=1 .fuzz-corpus-min .fuzz-corpus
rm -rf .fuzz-corpus && mv .fuzz-corpus-min .fuzz-corpus
```
Run `./configure` first, the target needs the generated `config.h`.
If the link fails with `cannot find -lstdc++`, install the `libstdc++`
headers matching the newest GCC on the system, not the default one:
clang picks the newest tree it finds, and that is the one that needs
them. CI runs this target on every pull request, carrying its
corpus between runs so it reaches deeper over time.
Feed a file back to the target to reproduce a find:
```shell
./fuzz-msg-parse crash-3f2a...
```
Running
-------
@@ -171,7 +274,11 @@ Notice the `--` separator.
To debug startup issues, in particular issues with getty/login, add
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
kernel. Remember, this is only for debugging and would leave your
@@ -186,7 +293,9 @@ it only for debugging start up issues when Finit crashes.
> None of these options should be enabled on production systems since
> they can potentially give a user root access.
[1]: ftp://troglobit.com/finit/finit-4.3.tar.gz
[1]: https://github.com/finit-project/finit/releases/download/4.3/finit-4.3.tar.gz
[libuEv]: https://github.com/troglobit/libuev
[libite]: https://github.com/troglobit/libite
[libConfuse]: https://github.com/libconfuse/libconfuse
[util-linux]: https://github.com/util-linux/util-linux
[Linux-PAM]: https://github.com/linux-pam/linux-pam
+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
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.
* `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
to after bootstrap by forcing it to runlevel 1, this is also known
as single user mode. Useful to debug startup problems. All services
and TTYs in `[1]` will be started, so a `tty [1] @console nologin`
configuration presents you with a root console without login.
and TTYs in runlevel 1 will be started, so a `tty` block with
`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`
and `rescue`, giving a single number on the kernel command line tells
+124 -37
View File
@@ -7,18 +7,26 @@ mechanism for common synchronization problems. For example:
- *"wait for service A to start before starting service B"*, or
- *"wait for basic network access to be available"*
Conditions are similar in syntax to declaring runlevels per service.
They are specified within angle brackets `<>` and can be applied to any
of the `service`, `task`, or `run` stanza. Multiple conditions may be
specified separated by comma. Multiple conditions are logically AND'ed
A condition is named in the `conditions` list of a `service`, `task`, or
`run` block. The list may hold several, and they are logically AND'ed
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>`.
A special syntax, using a leading `!` in run/task/service conditions,
denote if a:
One prefix can be used on a condition:
- service does not support `SIGHUP`
- run/task should not block runlevel changes (i.e., bootstrap)
- `~` -- 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
per runlevel. For most tasks this is a good default, for example
@@ -27,7 +35,12 @@ that are unlikely to happen it is not. (See example below.)
### 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
both the `pid/setupd` *and* `pid/zebra` conditions are satisfied. A
@@ -42,15 +55,62 @@ being created, i.e., the service's default readiness notification.
Another example is `dropbear`, it does not support `SIGHUP`, but we can
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
happens when Finit receives `SIGPWR`, sent from a power daemon like
[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
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
-----------------------------------
By default, when a service reloads during `initctl reload`, dependent
services are paused (`SIGSTOP`) and simply resumed (`SIGCONT`) when the
condition is reasserted. This is correct for barrier-style dependencies
like `<pid/syslogd>`, where dependents just need syslogd running and do
not care if it reloads its config.
For services that need to react when their upstream reloads, the `~`
prefix propagates the reload from the dependency:
service svc_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`.
When `svc_b` reloads, `svc_c` will be restarted, because of
`reload-signal = "none"`, instead of merely resumed. If `svc_c`
supported `SIGHUP`, it would be sent `SIGHUP` instead.
This is similar to systemd's `PropagatesReloadTo=` directive, but
declared on the consumer side rather than the provider side.
Triggering
@@ -79,7 +139,12 @@ services, or run/task jobs, on external site-dependent stimuli.
**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
or period characters are disallowed. E.g., to trigger the `Foo alarm`,
@@ -102,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
> calling `utimensat()` on the PID file. Provided, of course, that the
> service supports reloading on `SIGHUP`, otherwise it will be restarted
> by Finit when they instead exit on the signal. For such services, use
> `<!>` to tell Finit the service does not support `SIGHUP`.
> by Finit when they instead exit on the signal. For such services,
> set `reload-signal = "none"` to tell Finit the service does not
> support `SIGHUP`.
Built-in Conditions
@@ -111,25 +177,35 @@ Built-in Conditions
Finit comes with a set of plugins for conditions:
- `devmon` (built-in)
- `netlink`
- `pidfile`
- `keventd`: provides `<dev/...>`, `<class/...>`, `<driver/...>`, and
`<sys/pwr/...>`
- `devmon` (built-in fallback for `<dev/...>` without keventd)
- `netlink`: provides `<net/...>`
- `pidfile`: provides `<pid/...>`
- `sys`
- `usr`
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
service has declared a `<dev/foo>` or `<dev/dir/bar>` condition.
The `dev/` conditions are provided by `keventd`, the built-in device
manager. When keventd creates a device node in `/dev`, it also asserts
the corresponding `dev/` condition. When a device is removed, the
condition is cleared. If keventd is not in use (an external device
manager like udevd is used instead), the `devmon` built-in provides the
same conditions by monitoring `/dev` and `/dev/dir` with inotify.
The `pidfile` plugin (recursively) watches `/run/` (recursively) for PID
files created by the monitored services, and sets a corresponding
condition in the `pid/` namespace.
keventd also asserts `class/<subsystem>/<name>` for devices that have
no `/dev` node, e.g. LEDs and DSA switch ports, and `driver/<name>`
while a driver is bound to at least one device. See
[keventd](keventd.md) for details.
The `pidfile` plugin recursively watches `/run/` for PID files created
by the monitored services, and sets a corresponding condition in the
`pid/` namespace.
Similarly, the `netlink` plugin provides basic conditions for when an
interface is brought up/down and when a default route (gateway) is set,
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
`usr`, the end-user via the `initctl` tool.
@@ -164,6 +240,8 @@ Built-in conditions:
- `usr/foo`
- `boot/arg`
- `dev/node` and `dev/dir/node`
- `class/<subsystem>/<name>`
- `driver/<name>`
> [!NOTE]
> Here, `up` means administratively up, the interface flag `IFF_UP`.
@@ -175,16 +253,16 @@ Composition
-----------
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
the daemon and the empty string.
composed from a service's block title and its `:id`. By default the
basename of the daemon and the empty string.
| **service** | **condition** |
|----------------------------------------------------|------------------|
| /sbin/foo | pid/foo |
| /sbin/bar -p /run/baz.pid | pid/bar |
| name:lxc :foo lxc-start -n foo -p /run/lxc/foo.pid | pid/lxc:foo |
| /usr/bin/dbus-daemon | pid/dbus-daemon |
| :222 dropbear -p 222 | pid/dropbear:222 |
| **service** | **condition** |
|-------------------------------------------------------------------|------------------|
| `service { command = "/sbin/foo" }` | pid/foo |
| `service { command = "/sbin/bar -p /run/baz.pid" }` | pid/bar |
| `service lxc:foo { command = "lxc-start -n foo -p /run/lxc/foo.pid" }` | pid/lxc:foo |
| `service { command = "/usr/bin/dbus-daemon" }` | pid/dbus-daemon |
| `service dropbear:222 { command = "dropbear -p 222" }` | pid/dropbear:222 |
The condition is asserted when `pidfile.so` receives an inotify event
for a file matching `/run/*.pid`, `/run/**/*.pid`, or `/run/**/pid`,
@@ -194,9 +272,13 @@ When Finit configuration files are changed and the `initctl reload`
command is called, it is expected of services to touch their PID files
for Finit to reassert their conditions.
Similarly, when a single service is reloaded with `initctl reload NAME`,
its conditions are cleared and reasserted, ensuring dependent services
are properly updated.
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
the service stanza for the daemon. It is far from optimal since any
can be worked around by setting `pidfile` and `pidfile-create` in the
service block for the daemon. It is far from optimal since any
synchronization of depending services may fail due to the daemon not
having reinitialized/created their IPC sockets, or similar.
@@ -277,6 +359,11 @@ This STOP/CONT handling minimizes the number of unnecessary service
restarts that would otherwise occur because a depending service was sent
`SIGHUP` for example.
Services with the `~` prefix are an exception to this rule: when their
conditions return to `on` after being in `flux`, the reload is propagated
-- the service is reloaded (SIGHUP), or restarted if it has
`reload-signal = "none"`, instead of simply being resumed.
Therefore, any plugin that supplies Finit with conditions must ensure
that their state is updated after each reconfiguration. This can be
done by binding to the `HOOK_SVC_RECONF` hook. For an example of how
+204
View File
@@ -0,0 +1,204 @@
Linux Capabilities
==================
Finit supports Linux capabilities, allowing services to run with minimal
required privileges instead of running as root. This significantly improves
system security by following the principle of least privilege.
## Overview
Linux capabilities divide the traditional root privileges into distinct units
that can be independently granted to processes. For example, a web server only
needs the capability to bind to privileged ports (< 1024), not full root access.
Finit uses the modern IAB (Inheritable, Ambient, Bounding) API from libcap,
which is the same approach used by other modern service managers like dinit.
## Basic Usage
Capabilities are specified with the `capabilities` key, alias `caps`:
```conf
service nginx {
description = "Web server"
runlevel = "2345"
user = "www-data"
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
running as the unprivileged `www-data` user.
## IAB Format
The capability string uses the IAB (Inheritable, Ambient, Bounding) format
with the following prefixes:
- `^` **Ambient** (and Inheritable) - **Recommended for most use cases**
- Capabilities survive across `exec()` calls
- Automatically raised to effective after exec
- Example: `^cap_net_bind_service`
- `%` **Inheritable** only
- Requires the executed binary to have matching file capabilities
- Less common, more complex setup
- Example: `%cap_net_admin`
- `!` **Bounding** - Block capability from bounding set
- Prevents the service from ever acquiring this capability
- Useful for security hardening
- Example: `!cap_sys_admin`
Multiple capabilities can be specified as a comma-separated list:
```conf
capabilities = { "^cap_net_raw", "^cap_net_admin", "^cap_net_bind_service" }
```
## Common Use Cases
### Web Server (Privileged Ports)
Allow a web server to bind to ports 80 and 443 without running as root:
```conf
service webserver {
runlevel = "2345"
user = "www-data"
group = "www-data"
capabilities = { "^cap_net_bind_service" }
command = "/usr/sbin/nginx -g 'daemon off;'"
}
```
### Network Monitoring (Raw Sockets)
Allow packet capture without root privileges:
```conf
service tcpdump {
runlevel = "2345"
user = "tcpdump"
capabilities = { "^cap_net_raw", "^cap_net_admin" }
command = "/usr/sbin/tcpdump -i eth0 -w /var/log/capture.pcap"
}
```
### NTP Daemon (System Time)
Allow time synchronization without full root:
```conf
service ntpd {
runlevel = "2345"
user = "ntp"
capabilities = { "^cap_sys_time", "^cap_sys_nice" }
command = "/usr/sbin/ntpd -n"
}
```
## Available Capabilities
Common capabilities include (see `man 7 capabilities` for the complete list):
- `cap_chown` - Make arbitrary changes to file UIDs and GIDs
- `cap_dac_override` - Bypass file read, write, and execute permission checks
- `cap_dac_read_search` - Bypass file read permission checks
- `cap_fowner` - Bypass permission checks on operations that normally require filesystem UID
- `cap_kill` - Bypass permission checks for sending signals
- `cap_net_admin` - Perform various network-related operations
- `cap_net_bind_service` - Bind to privileged ports (< 1024)
- `cap_net_raw` - Use RAW and PACKET sockets
- `cap_setgid` - Make arbitrary manipulations of process GIDs
- `cap_setuid` - Make arbitrary manipulations of process UIDs
- `cap_sys_admin` - Perform system administration operations (very powerful!)
- `cap_sys_module` - Load and unload kernel modules
- `cap_sys_nice` - Raise process nice value and change scheduling
- `cap_sys_time` - Set system clock
## Security Best Practices
1. **Use the minimum required capabilities**
- Only grant what the service actually needs
- Don't grant `cap_sys_admin` unless absolutely necessary
2. **Specify a user (preferably non-root)**
- The `user` setting is **required** for `capabilities` to take effect
- For ambient capabilities (`^`), use a non-root user (not `"root"`)
- Example: `user = "www-data"`, `user = "nginx"`, `user = "tcpdump"`
3. **Use ambient capabilities (`^`)**
- The `^` prefix ensures capabilities survive exec()
- Simpler than setting file capabilities on binaries
4. **Block dangerous capabilities**
- Use `!` to explicitly block capabilities you don't want
- Example: `!cap_sys_admin,!cap_sys_module`
5. **Test with `getpcaps`**
- After starting a service, verify its capabilities:
```bash
getpcaps $(pidof nginx)
```
- Should show only the capabilities you granted
## Verification
After configuring a service with capabilities, verify it works correctly:
```bash
# Start the service
initctl start webserver
# Check the process capabilities
getpcaps $(pidof nginx)
# Should show something like:
# 12345: cap_net_bind_service=eip
# Verify the user
ps -o user,pid,cmd -p $(pidof nginx)
# Should show the service running as the specified user
```
## Requirements
- Linux kernel 4.3+ (for ambient capabilities support)
- libcap library installed
- Finit built with `--enable-libcap`, otherwise a `capabilities` list is
ignored, with a warning
## Limitations
- `capabilities` requires `user` to be set for it to take effect
- Without `user`, the service runs as root with full capabilities and
the `capabilities` list is silently ignored
- You can use `user = "root"`, but see below about ambient capabilities
- For ambient capabilities (`^`, recommended), the user **must be non-root**
- Using `user = "root"` with `^` capabilities will not work effectively, as ambient
capabilities are only added to the effective set when euid ≠ 0
- Use inheritable (`%`) or bounding (`!`) capabilities with `user = "root"` if needed
- Finit warns about this when reading the .conf file:
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
- A capability granted by `pam_cap.so` in a [PAM session](pam.md) is
merged into this set, but only when `capabilities` is also set.
Without it the grant is lost when privileges drop
- Some very old binaries may not work correctly with ambient capabilities
- File system capabilities are not managed by Finit (use `setcap` for that)
## See Also
- [capabilities(7)](https://man7.org/linux/man-pages/man7/capabilities.7.html) - Linux capabilities overview
- [cap_iab(3)](https://man7.org/linux/man-pages/man3/cap_iab.3.html) - IAB capability API documentation
- [setcap(8)](https://man7.org/linux/man-pages/man8/setcap.8.html) - Set file capabilities
- [getcap(8)](https://man7.org/linux/man-pages/man8/getcap.8.html) - Query file capabilities
- [capsh(1)](https://man7.org/linux/man-pages/man1/capsh.1.html) - Capability shell wrapper
+212 -33
View File
@@ -1,65 +1,244 @@
There are three major cgroup configuration directives:
Finit has two `cgroup` blocks for controlling resource allocation:
1. Global top-level group: `init`, `system`, `user`, or a custom group
2. Selecting a top-level group for a set of run/task/services
3. Per run/task/service limits
1. **Top-level definition**, at file scope: declares a group such as
`init`, `system`, or `user`, and its default settings.
cgroup system { cpu.weight = 9700 }
2. **Joining a group**, inside a service block: names the group this
service runs in, and may override settings for itself alone.
service foo {
cgroup maint { memory.max = 1G }
command = "/path/to/cmd"
}
> [!NOTE]
> Linux cgroups and details surrounding values are not explained in the
> Finit documentation. The Linux admin-guide cover this well:
> Finit documentation. The Linux admin-guide covers this well:
> <https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html>
Top-level Group Configuration
-----------------------------
Top-level Cgroup Definition
----------------------------
**Syntax:** `cgroup NAME { settings }`
# Top-level cgroups and their default settings. All groups mandatory
# but more can be added, max 8 groups in total currently. The cgroup
# 'root' is also available, reserved for RT processes. Settings are
# as-is, only one shorthand 'mem.' exists, other than that it's the
# cgroup v2 controller default names.
cgroup init cpu.weight:100
cgroup user cpu.weight:100
cgroup system cpu.weight:9800
cgroup init { cpu.weight = 100 }
cgroup user { cpu.weight = 100 }
cgroup system { cpu.weight = 9800 }
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
`system/` to 9700.
cgroup system cpu.weight:9700
cgroup system { cpu.weight = 9700 }
# 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
`init/` cgroup is reserved for PID 1 itself and its closest relatives.
`init/` cgroup is reserved for the early boot helpers Finit drives
itself, like udev and mdev. PID 1 runs in the root cgroup, since a
cgroup holding processes cannot delegate controllers to its children.
The `user/` cgroup is for local TTY logins spawned by getty.
Selecting Cgroups
------------------
Joining a Cgroup
----------------
To select a different top-level cgroup, e.g. `maint/`, one can either
define it for a group of run/task/service directives in a `.conf` or per
each stanza:
**Syntax:** `cgroup NAME { settings }` (inside a service block)
cgroup.maint
service [...] <...> /path/to/foo args -- description
service [...] <...> /path/to/bar args -- description
Every block says which group it joins, by name. An empty block joins
without changing anything:
or
service foo {
cgroup maint {}
command = "/path/to/foo args"
}
service [...] <...> cgroup.maint /path/to/foo args -- description
service bar {
cgroup maint {}
command = "/path/to/bar args"
}
The latter form also allows per-stanza limits on the form:
Both services run in the `maint/` cgroup. Settings inside the block
apply to that service alone:
service [...] <...> cgroup.maint:cpu.max:10000,mem.max:655360 /path/to/foo args -- description
service foo {
cgroup maint {
cpu.max = 10000
memory.max = 655360
}
command = "/path/to/foo args"
}
Notice the comma separation and 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.
> [!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.
A daemon using `SCHED_RR` currently need to run outside the default cgroups.
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
filtering, the one exception being the shorthand `mem.`, which expands
to `memory.`. If the file is not available, either the controller is
missing from your Linux kernel, or the name is misspelled.
service [...] <...> cgroup.root /path/to/daemon arg -- Real-Time process
### Overriding Cgroup Leaf Names
By default, the cgroup leaf directory name is derived from the service
configuration filename (without the `.conf` extension). For example, a
service defined in `system/10-hotplug.conf` would create a cgroup at
`/sys/fs/cgroup/system/10-hotplug/` by default.
To use a more descriptive name (recommended for clarity), set `name`
inside the cgroup block:
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.
`name` combines with any other setting:
service udevd {
description = "Device event daemon"
cgroup system {
name = "udevd"
cpu.max = 10000
}
command = "/lib/systemd/systemd-udevd"
}
Or with delegation:
service podman {
description = "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.
service rt {
description = "Real-Time process"
cgroup root {}
command = "/path/to/daemon arg"
}
Cgroup Delegation
-----------------
For services that need to create their own child cgroups (container runtimes
like Docker, Podman, systemd-nspawn, LXC), set `delegate`:
service dockerd {
description = "Docker daemon"
runlevel = "2345"
user = "dockerd"
group = "dockerd"
cgroup system { delegate = true }
command = "/usr/bin/dockerd"
}
This allows the container runtime to:
- Create child cgroups for containers
- Manage controller settings for containers
- Move processes between cgroups
When delegation is enabled, Finit:
1. Creates the service cgroup as a **domain group** (not a leaf)
2. Enables all available controllers in `cgroup.subtree_control`
3. Changes ownership of delegation files to the service user
4. Moves the service process to the cgroup root
5. Lets the container runtime manage its own subdirectories
**Requirements:**
- The service should specify `user` and `group` for proper ownership
- Controllers are delegated from the parent cgroup
**Example with additional config:**
service podman {
description = "Podman API"
runlevel = "2345"
user = "podman"
group = "podman"
cgroup containers {
delegate = true
memory.max = 4G
}
command = "/usr/bin/podman system service"
}
This delegates the cgroup while also setting a 4GB memory limit.
**Container template example:**
Here's a real-world example from [Infix OS](https://github.com/kernelkit/infix)
for running rootful podman container instances using delegation:
sysv container:%i {
description = "container %i"
runlevel = "2345"
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
delegation works with different service types, not just `service`.
**Cgroup structure with delegation:**
Initially, the service process runs directly in the cgroup root:
/sys/fs/cgroup/system/container@web/
├── cgroup.procs (service PID - owned by service user)
├── cgroup.subtree_control (+cpu +memory +io - owned by service user)
└── (container children will be created here)
Once the container runtime creates child cgroups (e.g., `libpod-*/`), cgroups v2
enforces the "no internal processes" rule. When Finit detects this (`EBUSY` error),
it automatically creates a `supervisor/` subdirectory and moves service-related
processes there:
/sys/fs/cgroup/system/container@web/
├── cgroup.procs (empty)
├── cgroup.subtree_control (+cpu +memory +io)
├── supervisor/ (service processes)
│ └── cgroup.procs (conmon PIDs, etc.)
└── libpod-$HASH/ (container processes)
└── cgroup.procs (container PIDs)
This happens automatically - no configuration needed. Without delegation, the
cgroup would be a leaf and the container runtime could not create child cgroups.
+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
environment is detailed in [Services Environment](service-env.md).
The syntax for global environment variables is straight forward. In
Finit v4.4 the `set` keyword was added for completeness, but the old
syntax (without the `set ` prefix) is still honored:
Global environment variables go in an `environment` block, `env` for
short:
set foo=bar
set baz="qux"
environment {
foo = "bar"
baz = "qux"
}
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
run/tasks or services. The environment of already started processes can
not be changed.
+27 -23
View File
@@ -24,29 +24,32 @@ the following layout is recommended:
| `- ...
`- run/
`- finit/
|- bus # Finit's own D-Bus socket, see doc/dbus.md
`- system/
|- dbus.conf
|- keventd.conf
|- runparts.conf
|- watchdogd.conf
`- ...
Configuration files in `/etc` are provided by the user, or projects like
[finit-skel](https://github.com/troglobit/finit-skel) and extended by
[finit-skel](https://github.com/finit-project/finit-skel) and extended by
the user.
The files in `/run/finit/system/*.conf` are generated by Finit for its
built-in services: runparts, [watchdog](../watchdog.md), and
[`keventd`](../keventd.md), if they are enabled. They are read before
the other .conf directories, so any later file can reference them in
`if:` statements and override them by name.
The files in `/lib/finit/system/*.conf` are system critical services and
setup provided by Finit, e.g. udev/mdev that must run very early at system
bootstrap. This system directory was introduced in Finit v4.4 to replace
the hard-coded services provided by plugins before. All .conf files in this
directory be either replaced by a system administrator or overridden by a
file with the same name in `/etc/finit.d/`.
the hard-coded services provided by plugins before.
The files in `/run/finit/system/*.conf` are created by plugins and Finit
bundled services like runparts, [watchdog](../watchdog.md), and
[`keventd`](../keventd.md) if they are enabled. Like
`/lib/finit/system/*.conf`, these files can be overridden by file with
the same name in `/etc/finit.d/`.
All .conf files in both directories can be either replaced by a system
administrator or overridden by a file with the same name in
`/etc/finit.d/`, which is always read last -- copy a .conf there and
modify it, or symlink it to `/dev/null` to disable it outright.
Services in the `available/` and `enabled/` sub-directories are called
dynamic services, in contrast to static services -- the only difference
@@ -55,27 +58,28 @@ with the `enable` and `disable` commands. An administrator can always
create files and symlinks manually.
At bootstrap, and `initctl reload`, all .conf files are read, starting
with `finit.conf`, then `/lib/finit/system/*.conf`, `finit.d/*.conf`,
and finally all `finit.d/enabled/*.conf` files. Each directory is a
unique group, where files within each group are sorted alphabetically.
with `finit.conf`, then `/run/finit/system/*.conf`,
`/lib/finit/system/*.conf`, `finit.d/*.conf`, and finally all
`finit.d/enabled/*.conf` files. Each directory is a unique group,
where files within each group are sorted alphabetically.
**Example:**
/run/finit/system/keventd.conf
/run/finit/system/runparts.conf
/lib/finit/system/10-hotplug.conf
/lib/finit/system/90-testserv.conf
/run/finit/system/dbus.conf
/run/finit/system/runparts.conf
/etc/finit.d/10-abc.conf
/etc/finit.d/20-abc.conf
/etc/finit.d/enabled/1-aaa.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
started in the same order. This is important because of the blocking
properties of the `run` statement. For an example on the relation of
`service` and `run` statements, and dependency handling between them,
see [Conditional Loading](services.md#conditional-loading), below.
properties of `run`. For an example on the relation of `service` and
`run`, and dependency handling between them, see
[Conditional Loading](services.md#conditional-loading), below.
> [!NOTE]
> The names `finit.conf` and `finit.d/` are only defaults. They can be
@@ -84,7 +88,7 @@ see [Conditional Loading](services.md#conditional-loading), below.
>
> They can also be overridden from the [kernel command line](../cmdline.md)
> 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
-----------------
@@ -180,7 +184,7 @@ document [Finit Services](../service.md).
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:
@@ -194,7 +198,7 @@ configurations, starting with the kernel command line option:
This file in turn can use the `rcsd` directive to tell Finit to use
another set of .conf files, e.g.:
rcsd /etc/factory.d
rcsd = "/etc/factory.d"
> [!NOTE]
> This directive is only available from the top-level bootstrap .conf
@@ -203,6 +207,6 @@ another set of .conf files, e.g.:
Including Finit Configs
------------------------
**Syntax:** `include <CONF>`
**Syntax:** `include("CONF")`
Include another configuration file. Absolute path required.
+99 -13
View File
@@ -6,24 +6,110 @@ the navigation menu.
Configuration File Syntax
--------------------------
The file format is line based, empty lines and comments, lines starting
with `#`, are ignored. A configuration directive starts with a keyword
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:
A `.conf` file is a series of blocks. Every setting is a key inside
one, so nothing has to be remembered by position:
```aconf
# Escape \# chars if you want them literal in, e.g., descriptions
service name:sysklogd [S123456789] \
env:-/etc/default/sysklogd \
syslogd -F $SYSLOGD_ARGS \
-- System log daemon \# 1 # Comments allowed
service sysklogd {
description = "System log daemon"
runlevel = "S123456789"
envfile = "-/etc/default/sysklogd"
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. To convert a
file, see the [Syntax Migration Guide](migration.md).
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
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
to structure your .conf files.
+56 -15
View File
@@ -1,13 +1,13 @@
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
redirection to a log file. Global setting, applies to all services.
Log rotation for run/task/services that redirect output to a log file
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`,
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`.
The count value is recommended to be between 1-5, with a default 5.
@@ -17,25 +17,66 @@ size limit is reached.
Redirecting Output
------------------
The `run`, `task`, and `service` stanzas also allow the keyword `log` to
redirect `stderr` and `stdout` of the application to a file or syslog
The `run`, `task`, and `service` blocks take a `log` block of their own,
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
support syslog on their own, which is sometimes the case when running
in the foreground.
The full syntax is:
An empty block means syslog with the defaults, and three keys adjust it:
log:/path/to/file
log:prio:facility.level,tag:ident
log:console
log:null
log
| Setting | Description |
|---|---|
| `file` | Write to this path instead of syslog |
| `priority` | Syslog `facility.level`, default `daemon.info` |
| `identity` | Syslog tag, default the basename of the command |
Default `prio` is `daemon.info` and default `tag` is the basename of the
service or run/task command.
`/dev/console` and `/dev/null` are spelled as the paths they are:
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.
**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
----------------
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
non-interactive output (i.e., `isatty()` returns false) and typically
switch from line-buffered to fully-buffered mode.
Most well-behaved daemons explicitly flush their output or use syslog
directly, so this is rarely an issue. However, if a service's log
messages appear delayed or batched, you can force line-buffered output
by wrapping the command with `stdbuf`:
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
output. See `stdbuf(1)` for details.
> [!NOTE]
> Using `stdbuf` is rarely necessary. Only use it if you observe actual
> buffering issues with a specific service.
+267
View File
@@ -0,0 +1,267 @@
Syntax Migration Guide
======================
Finit reads both configuration formats. Every file is still `*.conf`,
and the format is detected per file, so a system can be migrated one
file at a time -- a file is one format or the other, never a mix.
This guide maps each part of a line-based stanza to its block key.
The shape of the change:
service [S12345] <pid/syslogd> env:-/etc/default/klogd name:klogd klogd -n $KLOGD_OPTS -- Kernel log daemon
becomes
service klogd {
description = "Kernel log daemon"
runlevel = "S12345"
conditions = { "pid/syslogd" }
envfile = "-/etc/default/klogd"
command = "klogd -n $KLOGD_OPTS"
}
The block title is the service identity, shown by `initctl`. Multiple
instances spell the ID in the title: `service sshd:1 { ... }`. The
legacy bare-ID form, `service :80 ...`, has no equivalent -- the title
carries both name and ID.
Positional parts
----------------
| Line-based | Block key |
|----------------------------------|-------------------------------------------------------|
| `service`, `task`, `run`, `sysv` | same, as block type |
| `[S12345]` | `runlevel = "S12345"` |
| `<pid/a,net/b>` | `conditions = { "pid/a", "net/b" }` |
| `<!pid/a>` on service/sysv | `conditions = { "pid/a" }` + `reload-signal = "none"` |
| `<!pid/a>` on run/task | `conditions = { "pid/a" }` + `required = false` |
| `@user:group,extra` | `user`, `group`, `extra-groups = { "extra" }` |
| `name:foo :1` | block title `foo:1` |
| the command and arguments | `command = "..."` |
| `-- Description text` | `description = "..."` |
The `!` was a flag on the stanza, not a negation, and meant different
things for daemons and one-shots; each meaning is now its own key. A
`~` prefix on a condition is unchanged: `conditions = { "~pid/a" }`.
Service options
---------------
| Line-based | Block key |
|----------------------------------|--------------------------------------------------|
| `env:[-]/path` | `envfile = "[-]/path"` |
| `pid:/path` | `pidfile = "/path"` + `pidfile-create = true` |
| `pid:!/path` | `pidfile = "/path"` |
| `pid` | `pidfile = true` + `pidfile-create = true` |
| `log` | `log { }` |
| `log:/path` | `log { file = "/path" }` |
| `log:null`, `log:console` | `log { file = "/dev/null" }`, `"/dev/console"` |
| `log:prio:p,tag:t` | `log { priority = "p" identity = "t" }` |
| `notify:systemd` | `notify = "systemd"` |
| `type:forking` | `type = "forking"` |
| `manual:yes` | `manual-start = true` |
| `remain:yes` | `remain-after-exit = true` |
| `restart:always` / `restart:NUM` | `restart = "always"` / `restart-max = NUM` |
| `restart_sec:SEC` | `restart-sec = SEC` |
| `norestart` | `restart = "never"` |
| `respawn` | `respawn = true` |
| `oncrash:reboot` | `oncrash = "reboot"` |
| `halt:SIG` | `stop-signal = "SIG"` |
| `kill:SEC` | `stop-timeout = SEC` |
| `pre:[TMO,]/script` | `exec-start-pre`, `exec-start-pre-timeout` |
| `ready:[TMO,]/script` | `exec-start-ready`, and its `-timeout` |
| `stop:[TMO,]/script` | `exec-stop`, and its `-timeout` |
| `post:[TMO,]/script` | `exec-stop-post`, and its `-timeout` |
| `reload:[TMO,]/script` | `exec-reload`, and its `-timeout` |
| `cleanup:[TMO,]/script` | `exec-cleanup`, and its `-timeout` |
| `caps:^cap_a,%cap_b` | `capabilities = { "^cap_a", "%cap_b" }` |
| `conflict:a,b` | `conflicts = { "a", "b" }` |
| `if:svc` / `if:<cond>` | `if = "svc"` / `if = "cond"`, no angle brackets |
| `tty:/dev/x` | `tty = "/dev/x"` |
| `nowarn` | leading `-` on `command` or `envfile` |
| `restarttmo:` | dropped, was already an alias for `restart_sec:` |
Repeated stanzas
----------------
The line-based format has no titles, so the same service could be
declared more than once and Finit would sort out which line applied.
A block title *is* the identity, and two blocks sharing one in the
same file are rejected. The two shapes that relied on the repetition
convert differently.
**Several candidate binaries for one service.** The daemon is known
under more than one name and the stanzas were repeated once per name,
each with `nowarn` so the ones that were not installed were skipped:
service nowarn pid:!/run/udevd.pid [S12345789] /lib/systemd/systemd-udevd -- Device event daemon
service nowarn pid:!/run/udevd.pid [S12345789] udevd -- Device event daemon
The candidates now go in one block, and Finit starts the first one it
finds:
service udevd {
description = "Device event daemon"
runlevel = "S12345789"
pidfile = "/run/udevd.pid"
command = { "/lib/systemd/systemd-udevd", "-udevd" }
}
**One service gated differently per platform.** The command is the
same every time, only `if:` and the conditions differ, e.g. a syslog
daemon that has to wait for whichever hotplug daemon the system has:
service if:udevd nowarn env:-/etc/default/sysklogd <run/udevadm:5/success> \
[S0123456789] syslogd -F $SYSLOGD_ARGS -- System log daemon
service if:mdev nowarn env:-/etc/default/sysklogd <run/coldplug/success> \
[S0123456789] syslogd -F $SYSLOGD_ARGS -- System log daemon
All of these are one service, `syslogd`, and everything downstream
waits for the one `pid/syslogd` barrier it provides. That barrier is
the reason the repetition existed: the title alone spells it, so
separate titles would rename it.
Give each variant its own title and name the shared barrier with
`provides`:
service syslogd:udev {
description = "System log daemon"
runlevel = "S0123456789"
if = "udevd"
conditions = { "run/udevadm:5/success" }
provides = "pid/syslogd"
envfile = "-/etc/default/sysklogd"
command = "-syslogd -F $SYSLOGD_ARGS"
}
service syslogd:mdev {
description = "System log daemon"
runlevel = "S0123456789"
if = "mdev"
conditions = { "run/coldplug/success" }
provides = "pid/syslogd"
envfile = "-/etc/default/sysklogd"
command = "-syslogd -F $SYSLOGD_ARGS"
}
Whichever variant `if` qualifies asserts `pid/syslogd` on top of its
own `pid/syslogd:udev`, so downstream blocks need no change. The `if`
statements are meant to be mutually exclusive; if two of them do
qualify, the second claim is refused with a warning and only the first
variant supplies the barrier. See [Provided
Conditions](service-opts.md#provided-conditions).
`initctl` now knows the variants apart, `initctl status syslogd:udev`,
and a bare `initctl status syslogd` still lists them all.
Splitting the variants across files also works, since the ban on
duplicate titles is per file, as does leaving that one file in the
line-based format. Neither is needed for this shape any more.
Cgroups
-------
The standalone `cgroup.NAME` line selected a group for every stanza
after it in the file, and `cgroup:opts` applied settings to whichever
group was current. Neither survives: every block names its own group,
so nothing depends on what came earlier in the file.
cgroup.maint
service [2345] cgroup:cpu.weight:250 foo -- Foo daemon
becomes
service foo {
description = "Foo daemon"
runlevel = "2345"
cgroup maint { cpu.weight = 250 }
command = "foo"
}
Top-level group declarations keep their name and settings:
cgroup system cpu.weight:9700 mem.max:10M
becomes
cgroup system {
cpu.weight = 9700
memory.max = 10M
}
TTYs
----
The three positional variants become three key choices:
tty [12345] /dev/ttyS0 115200 noclear vt220
tty [12345] /sbin/getty -L ttyS0 115200 vt100
tty [12345] notty
become
tty ttyS0 {
runlevel = "12345"
device = "/dev/ttyS0"
baud = 115200
term = "vt220"
noclear = true
}
tty getty {
runlevel = "12345"
command = "/sbin/getty -L ttyS0 115200 vt100"
}
tty shell {
runlevel = "12345"
notty = true
}
The bare flags keep their names as booleans: `noclear`, `nowait`,
`nologin`, `passenv`, `rescue`, e.g. `passenv` becomes
`passenv = true`.
A `tty` block is named by the device, not the title, so all three above
show up as `tty:ttyS0` and the like in `initctl`. The title still has
to be unique within the file.
Top-level directives
--------------------
| Line-based | Block key |
|----------------------------------|----------------------------------------------------------|
| `host NAME`, `hostname NAME` | `hostname = "NAME"` |
| `module foo args` (repeated) | `modules = { "foo args", ... }` |
| `network script args` | `network = "script args"` |
| `runlevel N` | `runlevel = N` |
| `rcsd /path` | `rcsd = "/path"` |
| `runparts [progress] [sysv] DIR` | `runparts = "DIR"`, `runparts-progress`, `runparts-sysv` |
| `set KEY=VAL` (repeated) | `environment { KEY = "VAL" }` |
| `log size:100k count:4` | `log { size = 100k count = 4 }` |
| `rlimit [hard\|soft] RES LIM` | `rlimit { hard.res = LIM }` |
| `readiness none` | `readiness = "none"` |
| `reboot-delay N` | `reboot-delay = N` |
| `reboot-watchdog on` | `reboot-watchdog = true` |
| `service-interval SEC` | `service-interval = SEC` |
| `shutdown script` | `shutdown = "script"` |
| `mknod /dev/x c 1 2` | `mknod = { "/dev/x c 1 2" }` |
| `include /path` | `include("/path")` |
Worth knowing
-------------
* Templates work unchanged: `%i` is replaced before the file is
parsed, so `service serv:%i { ... }` in a `serv@.conf` behaves
exactly like its legacy counterpart.
* A list cannot hold comments; the lexer reads entries after a `#`
regardless. Put commented-out candidates above the list.
* `${VAR}` in a value is expanded when the file is read, against
Finit's environment, and `${VAR:-default}` works too. An unset
variable expands to nothing. A plain `$VAR` is left alone and
reaches the service, which is what keeps `command = "syslogd -F
$SYSLOGD_ARGS"` working with an `envfile`.
* New settings only appear in the block format. The first are the
[per-service directories](service-opts.md#service-directories),
`runtime-dir` and friends, and [`pam`](pam.md).
For the full description of every key, see the rest of the
[Configuration](index.md) section.
+151
View File
@@ -0,0 +1,151 @@
PAM Sessions
============
`pam = "NAME"` runs a service inside a PAM session set up from
`/etc/pam.d/NAME`. The `session` stack in that file runs for the
process that goes on to become the daemon, so modules like `pam_limits`,
`pam_env`, or `pam_keyinit` see the service the way they see a login.
## Basic Usage
A display server that needs the session a login would have arranged for
it:
```conf
service weston {
user = "weston"
pam = "weston-autologin"
command = "/usr/bin/weston --continue-without-input"
}
```
With `/etc/pam.d/weston-autologin`:
```
auth required pam_permit.so
account required pam_unix.so
session required pam_unix.so
session required pam_limits.so
```
The `auth` line is needed even though nothing is ever authenticated.
Opening the session goes through `pam_setcred()`, which consults the
`auth` stack, and an empty stack comes back as a permission denial, so a
pam.d file with only `account` and `session` lines keeps the service
from starting.
The value names a file in `/etc/pam.d`, it is not a path. A value
holding `/` or `..`, or one too long to fit, is refused, with an error
in the log when the .conf file is read. The service does not start
either, `initctl status` reports it as missing. Like the per-service
directory keys, `pam` exists in the block format only.
## No Authentication
Finit runs the account and session stacks, `pam_acct_mgmt()`,
`pam_setcred()`, and `pam_open_session()`, and never
`pam_authenticate()`. A module that asks a question gets a
conversation error back and its entry in the stack fails.
A service whose account is denied, an expired account say, does not
start, and neither does one naming a pam.d file that is not installed.
The child exits 71 (`EX_OSERR`), `initctl status` shows the service as
crashed, and PAM's own reason is in the log.
## Requirements
PAM support is built by default, see [Building Finit](../build.md).
Without it, e.g. after `--disable-pam`, a service declaring `pam` is
refused rather than started with the stacks skipped:
weston: pam weston-autologin requires Finit built with --enable-pam
## Which Blocks Take It
`service`, `task`, `run`, and `sysv`. Not `tty`: `login` opens a
session of its own there.
Every fork gets its own session, so the `pre:`, `post:`, `ready:`, and
`cleanup:` scripts each open and close one too, as do the stop and
reload scripts and the `stop` call on a `sysv` script.
A refused value stops those too. A `pre:` script forks before the
start-time check runs, so it exits 71 without running instead of the
service being reported missing.
## The User
`user` decides who the session is for. Without it the session is for
root.
systemd documents the opposite for its equivalent. `systemd.exec(5)`
says `PAMName=` is "only useful in conjunction with the `User=`
setting, and is otherwise ignored". That was true of systemd up to and
including v256, v257 changed it to open a session for the manager's own
user, and the man page was never updated. Finit does what v257 does.
A service with a [controlling tty](tty.md#controlling-tty-for-services)
has it passed to PAM as `PAM_TTY`, for the modules that care which
terminal a session is on.
## Precedence
Three places where PAM and a Finit setting cover the same ground:
- `pam_limits` overrides a per-service `rlimit`. A block asking for
`nofile = 4096` under a `limits.conf` that says 512 gets 512.
- `pam_env` overrides Finit's own environment defaults, `PATH`, `USER`,
`LOGNAME`, and `HOME`, and `envfile` in turn overrides `pam_env`. A
`HOME` from the session stack also moves the working directory, which
otherwise is the home directory from `/etc/passwd`.
- The groups from `/etc/group` and `extra-groups` override `pam_group`.
## The Session Keeper
A helper is forked next to the service to close the session when the
service exits, `(finit-pam)`:
```
CGroup : /system/weston cpu 0 [100, max] mem [--.--, max]
|- 312 /usr/bin/weston --continue-without-input
`- 313 (finit-pam)
```
There is one per fork. It runs as the service's user, in the service's
cgroup, and stopping the service takes it along.
A daemon that reaps children in its own `wait()` loop will see a child
it never forked. systemd has the same property, with `(sd-pam)`.
## Limitations
- `type = "forking"` closes the session early. The initial process
exits by design, the keeper's parent-death signal fires with it, and
the session is closed while the real daemon runs on. Finit warns
about the combination when it reads the .conf file, and starts the
service anyway:
/etc/finit.d/foo.conf: foo: pam with type = forking closes the
session when the initial process exits
- A daemon whose initial thread exits while the process lives closes the
session the same way. The parent-death signal follows the thread that
forked the keeper, not the process.
- A capability granted by `pam_cap.so` is only kept when the service
also sets [`capabilities`](capabilities.md).
- The keeper shares the service's cgroup, so an empty cgroup directory
can outlive a stop until the next start reuses it. Cosmetic.
- A module that blocks has no time bound. `pam_open_session()` waits
for as long as the module does, `pam_ldap` against an unreachable
server, say, or `pam_mount` on a hung network mount. The fork has already succeeded by then, so the service
reaches the running state and stays there with no daemon behind it.
Nothing crashes and nothing restarts.
## See Also
- [Service Options](service-opts.md) - the other run/task/service keys
- [Linux Capabilities](capabilities.md) - the key `pam_cap.so` needs
- [Building Finit](../build.md) - `--disable-pam` and its dependency
- [pam(8)](https://man7.org/linux/man-pages/man8/pam.8.html) - the PAM library
- [pam.d(5)](https://man7.org/linux/man-pages/man5/pam.d.5.html) - the file format
- [pam_limits(8)](https://man7.org/linux/man-pages/man8/pam_limits.8.html) - limits from `limits.conf`
+13 -6
View File
@@ -47,16 +47,23 @@ system administrator.
The bundled default `rescue.conf` contains nothing more than:
runlevel 1
tty [12345] rescue
runlevel = 1
tty rescue {
runlevel = "12345"
rescue = true
}
The `tty` has the `rescue` option set, which works similar to the board
bring-up tty option `notty`. The major difference being that `sulogin`
The `tty` block has `rescue` set, which works similar to the board
bring-up setting `notty`. The major difference being that `sulogin`
is started to query for root/admin password. If `sulogin` is not found,
`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.
+66 -31
View File
@@ -26,12 +26,20 @@ the lifetime of your system.
Example:
task [S] /lib/console-setup/console-setup.sh
service [S12345] env:-/etc/default/rsyslog rsyslogd -n $RSYSLOGD_ARGS
task console-setup {
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
changed in `/etc/finit.conf` using the `runlevel N` directive, or by a
script running in runlevel S that calls, e.g., `initctl runlevel 9`.
changed in `/etc/finit.conf` with `runlevel = N`, or by a script running
in runlevel S that calls, e.g., `initctl runlevel 9`.
The latter is useful if startup scripts detect problems outside of
Finit's control, e.g., critical services/devices missing or hardware
problems.
@@ -44,7 +52,7 @@ complete before proceeding to 2.
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
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
least the loopback interface is brought up.
@@ -58,7 +66,7 @@ least the loopback interface is brought up.
Runlevel Configuration
----------------------
**Syntax:** `runlevel <N>`
**Syntax:** `runlevel = N`
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.
@@ -76,11 +84,11 @@ Finit disables networking in this mode.
Networking
----------
**Syntax:** `network <PATH>`
**Syntax:** `network = "PATH"`
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
Debian, Ubuntu, Linux Mint, or an embedded BusyBox system.
@@ -90,7 +98,7 @@ Debian, Ubuntu, Linux Mint, or an embedded BusyBox system.
System Hostname
---------------
**Syntax:** `host <NAME>`, or `hostname <NAME>`
**Syntax:** `hostname = "NAME"`
Set system hostname to NAME, unless `/etc/hostname` exists in which case
the contents of that file is used.
@@ -103,16 +111,24 @@ Deprecated. We recommend using `/etc/hostname` instead.
Kernel Modules
--------------
**Syntax:** `module <MODULE> [ARGS]`
**Syntax:** `modules = { "MODULE [ARGS]", ... }`, alias `mod`
Load a kernel module, with optional arguments. Similar to `insmod`
command line tool.
Load kernel modules, each with optional arguments. Similar to the
`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
that can handle module loading better. The former supports loading from
`/etc/modules-load.d/`, the latter uses kernel modinfo to automatically
load (or coldplug) every required module. For hotplug we recommend the
BusyBox mdev tool, add to `/etc/mdev.conf`:
load (or coldplug) every required module. Hotplug module loading is
handled by [keventd](../keventd.md), the built-in device manager. On
systems using the hotplug plugin with BusyBox mdev instead, add to
`/etc/mdev.conf`:
$MODALIAS=.* root:root 0660 @modprobe -b "$MODALIAS"
@@ -122,34 +138,35 @@ BusyBox mdev tool, add to `/etc/mdev.conf`:
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
`setrlimit(2)`, without prefix. E.g. to set `RLIMIT_CPU`, use `cpu`.
LIMIT is an integer that depends on the resource being modified, see
the man page, or the kernel `/proc/PID/limits` file, for details.
Finit versions before v3.1 used `infinity` for `unlimited`, which is
still supported, albeit deprecated.
[setrlimit(2)](https://man7.org/linux/man-pages/man2/setrlimit.2.html),
or the kernel `/proc/PID/limits` file, for details. Finit versions
before v3.1 used `infinity` for `unlimited`, which is still supported,
albeit deprecated.
# No process is allowed more than 8MB of address space
rlimit hard as 8388608
# Core dumps may be arbitrarily large
rlimit soft core infinity
# CPU limit for all services, soft & hard = 10 sec
rlimit cpu 10
rlimit {
hard.as = 8388608 # no more than 8MB of address space
soft.core = unlimited # core dumps may be arbitrarily large
cpu = 10 # soft & hard = 10 sec
}
`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
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.
For a service with [`pam`](pam.md), `pam_limits` runs after these
limits are applied, so `limits.conf` has the last word.
Miscellaneous Settings
----------------------
**Syntax:** `reboot-delay <0-60>`
**Syntax:** `reboot-delay = 0-60`
Optional delay at reboot (or shutdown or halt) to allow kernel
filesystem threads to complete after calling `sync(2)` before
@@ -165,4 +182,22 @@ sync(2) has been called, twice.
> "On Linux, sync is only guaranteed to schedule the dirty blocks for
> writing; it can actually take a short time before all the blocks are
> finally written.
> finally written.
**Syntax:** `reboot-watchdog = true|false`
Controls whether the system should reboot via the watchdog timer (WDT)
or directly via the SoC/kernel. When enabled, Finit will:
1. Send `SIGPWR` to the registered watchdog daemon before shutdown
2. Send `SIGTERM` to the watchdog daemon and wait up to 10 seconds
for the watchdog to trigger a hardware reset
When disabled (default), Finit skips the watchdog reboot logic and
calls the kernel's `reboot(2)` syscall directly for a clean SoC reboot.
*Default:* off (reboot via SoC)
> [!NOTE]
> This setting only affects reboots. The watchdog daemon will still
> run and monitor the system during normal operation.
+8 -7
View File
@@ -1,7 +1,7 @@
Run-parts Scripts
-----------------
**Syntax:** `runparts [progress] [sysv] <DIR>`
**Syntax:** `runparts = "DIR"`
Call [run-parts(8)][] on `DIR` to run start scripts. All executable
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:**
- `progress`: display the progress of each script being executed
- `sysv`: run only SysV style scripts, i.e., `SNNfoo`, or `KNNbar`,
where `NN` is a number (0-99).
- `runparts-progress = true`: display the progress of each script being
executed
- `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
with the debug flag.
@@ -24,7 +25,7 @@ with the debug flag.
**Limitations:**
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
before continuing. None of them can issue commands to start, stop, or
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
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
Finit will lock up.
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.
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
+14 -4
View File
@@ -13,7 +13,12 @@ file `/etc/default/foo`. Like this:
* `/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
foreground, and the with the options found in the environment file. With
@@ -22,6 +27,11 @@ the `ps` command we can see that the process is started with:
foo -n --extra-arg=bar -s -x
> [!NOTE]
> The leading `-` in `env:` determines if Finit should treat a missing
> environment file as blocking the start of the service or not. When
> `-` is used, a missing environment file does *not* block the start.
For a service with [`pam`](pam.md), the session environment is applied
before `envfile`, so the file overrides anything `pam_env` set.
> The leading `-` on `envfile` determines if Finit should treat a
> missing environment file as blocking the start of the service or not.
> 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.
+287 -89
View File
@@ -1,21 +1,22 @@
Service Options
===============
The run/task/tty/service/sysv directives take modifiers, or options, to
control their behavior. This section lists them with their limitations.
All modifiers must be placed between the directive and its command.
The `run`, `task`, `tty`, `service` and `sysv` blocks take settings as
keys. This section lists them with their limitations.
The name of a service, shown by the `initctl` tool, defaults to the
basename of the service executable. It can be changed with the `name:`
option:
The name of a service, shown by the `initctl` tool, is the block title.
It defaults to the basename of the executable if the title is omitted:
name:<service-name>
service sshd {
command = "/usr/sbin/sshd -D"
}
For multiple instances of a service, with the same `name`, set the
identifier `:ID` to prevent Finit from replacing previous instances:
For multiple instances of a service, with the same name, add an
identifier after a colon to prevent Finit from replacing previous
instances:
service name:ssdp :eth1 ssdpd eth1 -- Windows discovery on eth1
service name:ssdp :eth2 ssdpd eth2 -- Windows discovery on eth2
service ssdp:eth1 { command = "ssdpd eth1" }
service ssdp:eth2 { command = "ssdpd eth2" }
The [`initctl`](../initctl.md) tool will list these two services as:
@@ -23,100 +24,295 @@ The [`initctl`](../initctl.md) tool will list these two services as:
- ssdp:eth2
Conflicting services that must be prevented from starting, use the
`conflict:` option:
`conflicts` key:
service [S12345789] udevd -- Device event management daemon
run [S] conflict:udevd mdev -s -- Populating device tree
service udevd { command = "udevd" }
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 :2 abc
service conflict:abc:1,abc:2 cde
service abc:1 { command = "abc 1" }
service abc:2 { command = "abc 2" }
service cde { conflicts = { "abc:1", "abc:2" } command = "cde" }
If a service should not be automatically started, it can be configured
as manual with the optional `manual` argument. The service can then be
started at any time by running `initctl start <service>`.
If a service should not be automatically started, set `manual-start`.
It can then be started at any time with `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:
* `cgroups:...` -- see the [Cgroups](cgroups.md) section
* `env:[-]/path/to/env` -- see the [Service Environment](service-env.md) section
* `log:...` -- see [Redirecting Output](logging.md#redirecting-output)
* `nowarn` -- see [Conditional Loading](services.md#conditional-loading)
* `notify:...` -- see [Service Synchronization](service-sync.md)
* `if:...` -- see [Conditional Execution](services.md#conditional-execution)
* `type:forking` -- see description of the [service](services.md) directive
* `capabilities` -- see the [Linux Capabilities](capabilities.md) section
* `cgroup NAME {}` -- see the [Cgroups](cgroups.md) section
* `envfile` -- see the [Service Environment](service-env.md) section
* `log {}` -- see [Redirecting Output](logging.md#redirecting-output)
* `tty` -- see [Controlling TTY](tty.md#controlling-tty-for-services)
* `notify` -- see [Service Synchronization](service-sync.md)
* `pam` -- see the [PAM Sessions](pam.md) section
* `if` -- see [Conditional Execution](services.md#conditional-execution)
* `type = "forking"` -- see description of the [service](services.md) block
* a leading `-` on `command` -- see
[Conditional Loading](services.md#conditional-loading)
As mentioned previously, services are automatically started, restarted,
and stopped, depending on the configuration and conditions. Within the
confines of that the following options are available:
Command Candidates
------------------
* `restart:NUM` -- number of times Finit tries to restart a crashing
service, default: 10, max: 255. When this limit is reached the
The `command` setting may list several candidates for the same
service, in which case Finit starts the first one it finds:
service udevd {
command = { "/lib/systemd/systemd-udevd", "-udevd" }
}
A candidate that is not installed is skipped without a warning, that
is the point of listing more than one. The leading `-` is read from
the candidate that wins, or from the last one when none are found, so
a block with a single command behaves as it always has.
The candidates are alternative spellings of one service, not a
fallback for a service that fails to start. Finit chooses when the
.conf file is read and does not revisit the choice.
A [`tty`](tty.md) block takes the same list, where the candidates are
the getty to run:
tty ttyAMA0 {
command = { "/sbin/agetty -L ttyAMA0 115200 vt100",
"/sbin/getty -L 115200 /dev/ttyAMA0 vt100" }
}
Provided Conditions
-------------------
A service always asserts `pid/<ident>` for itself, named after the
block title. `provides` names conditions it asserts in addition to
that one:
service syslogd:udev {
if = "udevd"
provides = "pid/syslogd"
command = "syslogd -F"
}
This is what lets variants of one service, picked apart by
[`if`](services.md#conditional-execution), share the barrier that
everything downstream waits for. Each variant has its own title, so
each is its own service to `initctl`, while the condition they publish
stays the same.
Any namespace works, `pid/`, `usr/`, `service/`, since the point is
publishing a name existing configuration already waits on. A value
without a namespace separator is not a condition and is rejected. The
list holds at most four.
A provided condition is asserted and cleared with the service, exactly
like its own, so stopping the provider clears the barrier.
Two services cannot supply the same condition. The second claim is
refused, with the owner named:
finit.conf: second: provides condition <usr/barrier> already registered by service first, ignoring
The service itself still registers and runs, only the claim is
dropped. Which one is "first" is .conf load order, so it follows the
file names, see [Files & Layout](files.md).
A real identity outranks a claim on it. `provides = "pid/sshd"` in a
system that also runs a service titled `sshd` loses to that service,
because `pid/<ident>` is how Finit tracks the service itself.
`initctl cond dump` names the owner of every condition, which for a
provided one is the service that claimed it:
$ initctl cond dump
PID IDENT STATUS CONDITION
1234 syslogd:udev on <pid/syslogd>
1234 syslogd:udev on <pid/syslogd:udev>
Duplicate Titles
----------------
The title is the service identity, so two blocks sharing a title in
one file are two declarations of one service. The parser underneath
merges them without a word, leaving a single service holding a mix of
both, so Finit rejects the file and names the title:
/etc/finit.d/hotplug.conf:12: found duplicate title 'udevd'
Nothing from a rejected file is loaded and the rest of the
configuration is unaffected.
The same title in *another* file is fine. That is how a system .conf
is overridden, the later file wins, see [Files & Layout](files.md).
Two shapes need a single identity and used to be written as repeated
declarations. One is a service with several candidate binaries, which
is now a [command list](#command-candidates). The other is a service
gated differently per platform, e.g. a syslog daemon waiting for
whichever hotplug daemon the system happens to have. For that one,
give each variant its own file, or keep them as one-liners in the
line-based format, which has no titles. The [migration
guide](migration.md) works through it.
Restarting
----------
Services are automatically started, restarted, and stopped, depending
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
`initctl restart NAME`
* `restart_sec:SEC` -- number of seconds before Finit tries to restart
a crashing service, default: 2 seconds for the first five retries,
then back-off to 5 seconds. The maximum of this configured value
and the above (2 and 5) will be used
* `restart:always` -- no upper limit on the number of times Finit
tries to restart a crashing service. Same as `restart:-1`
* `norestart` -- dont restart on failures, same as `restart:0`
* `respawn` -- bypasses the `restart` mechanism completely, allows
endless restarts. Useful in many use-cases, but not what `service`
was originally designed for so not the default behavior
* `oncrash:reboot` -- when all retries have failed, and the service
has *crashed*, if this option is set the system is rebooted
* `oncrash:script` -- similarly, but instead of rebooting, call the
`post:script` action with exit code `crashed`, see below
* `reload:'script [args]'` -- some services do not support `SIGHUP` but
may have other ways to update the configuration of a running daemon.
When `reload:script` is defined it is preferred over `SIGHUP`. Like
systemd, Finit sets `$MAINPID` as a convenience to scripts, which in
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`
* `restart-sec = SEC` -- number of seconds before Finit tries to
restart a crashing service. The default is 2 seconds for the first
half of `restart-max` attempts, then a back-off to 5 seconds -- with
the default `restart-max` that is the first five retries. The
greater of this configured value and the back-off is used
* `restart = "always"` -- no upper limit on the number of times Finit
tries to restart a crashing service
* `restart = "never"` -- do not restart on failures. `false` is
accepted as a synonym, and `true` selects the default policy
* `respawn = true` -- bypasses the `restart` mechanism completely,
allowing endless restarts. Exiting is treated as normal work
rather than failure, so the service is restarted at once instead of
being counted and delayed. This is how a `tty` behaves; it is not
what `service` was designed for, so it is not the default
* `remain-after-exit = true` -- for `run` and `task` only. Prevents
the task from re-running on runlevel re-entry and ensures the
`exec-stop-post` script runs when the task is explicitly stopped or
leaves its valid runlevels. This is systemd's `RemainAfterExit=`.
See [Task and Run](task-and-run.md) for more details
* `oncrash = "reboot"` -- when all retries have failed, and the
service has *crashed*, the system is rebooted
* `oncrash = "script"` -- similarly, but instead of rebooting, call
the `exec-stop-post` script with exit code `crashed`, see below
> [!CAUTION]
> 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.
Service directories
-------------------
When stopping a service (run/task/sysv/service), either manually or when
moving to another runlevel, Finit starts by sending `SIGTERM`, to allow
the process to shut down gracefully (unless a `stop:'script'` is used).
However, if the process has not been collected within 3 seconds, Finit
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`.
Five settings ask Finit to create a directory for the service before it
starts, owned by its `user` and `group`, mode 0755. The value is a
directory name, resolved under a fixed base -- absolute paths and `..`
are refused:
Services, including the `sysv` variant, support pre/post/ready and
cleanup scripts:
| Setting | Base | Environment variable |
|---|---|---|
| `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
* `post:[0-3600,]script` -- called after the sysv/service has stopped
* `ready:[0-3600,]script` -- called when the sysv/service is ready
* `cleanup:[0-3600,]script` -- called when run/task/sysv/service is removed
Each resolved path is exported to the process environment under the
listed name, the same names systemd uses for `RuntimeDirectory=` and
friends. As in systemd, `config-dir` is the odd one out: it is created
but never chowned.
The optional number (0-3600) is the timeout before Finit kills the
script, it defaults to the kill delay value and can be disabled by
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
from the `$HOME` of the given user. The scripts are not called with any
argument, but get a set of environment variables:
Each takes a matching `-mode`, e.g. `runtime-dir-mode = 0700`, default
0755. Modes are octal, with the leading zero.
The mode of the named directory is locked down again on every start.
Its contents are left alone as long as the owner is right; if the owner
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_NAME=foo`
* `SERVICE_ID=1`
The `post:script` is called with an additional set of environment
variables. Yes, the text is correct, the naming was an accident:
The `exec-stop-post` script is called with an additional set of
environment variables. Yes, the text is correct, the naming was an
accident:
- `EXIT_CODE=[exited,signal,crashed]`: normal exit, signaled, or
crashed
@@ -124,11 +320,13 @@ variables. Yes, the text is correct, the naming was an accident:
the program, if it exited normally, or the signal name (`HUP`,
`TERM`, etc.) if it exited due to signal
When a run/task/sys/service is removed (disable + reload) it is first
stopped and then removed from the runlevel. The `post:script` always
runs when the process has stopped, and the `cleanup:script` runs when
the the stanza has been removed from the runlevel.
When a run/task/sysv/service is removed (disable + reload) it is first
stopped and then removed from the runlevel. The `exec-stop-post`
script always runs when the process has stopped, and `exec-cleanup`
runs when the block has been removed from the runlevel.
> [!IMPORTANT]
> 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
be changed in `finit.conf`:
readiness none
readiness = "none"
This will be made the default in Finit 5.0. In this mode of operation,
every service needs to explicitly declare their readiness notification,
like this:
service notify:pid watchdogd
service notify:systemd foo
service notify:s6 bar
service notify:none qux
service watchdogd { notify = "pid" command = "watchdogd" }
service foo { notify = "systemd" command = "foo" }
service bar { notify = "s6" command = "bar" }
service qux { notify = "none" command = "qux" }
The `notify:none` syntax is for completeness in systems which run in
`readiness pid` mode (default). Services declared with `notify:none`
will transition to ready as soon as Finit has started them, e.g.,
`service/qux/ready`.
The `notify = "none"` setting is for completeness in systems which run
in `readiness = "pid"` mode (default). Services declared with
`notify = "none"` will transition to ready as soon as Finit has started
them, e.g., `service/qux/ready`.
To synchronize two services the following condition can be used:
service notify:pid watchdogd
service <service/watchdogd/ready> stress-ng --cpu 8
service watchdogd {
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]
> 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
> are expected to close your notify descriptor after having written
> `\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
> 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`,
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:**
* `/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:`
@@ -26,6 +31,6 @@ example employs a wrapper script in `/etc/start.d`.
exec /usr/bin/program $OPTIONS
> [!NOTE]
> The example sets `<!>` to denote that it doesn't support `SIGHUP`.
> That way Finit will stop/start the service instead of sending SIGHUP
> at restart/reload events.
> The example sets `reload-signal = "none"` to say the program does not
> support `SIGHUP`. Finit then stop/starts the service instead of
> signalling it at restart/reload events.
+236 -81
View File
@@ -1,16 +1,17 @@
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
exits prematurely. Finit tries to restart services that die, by default
10 times before giving up and marking them as *crashed*. After which
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]
> 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`,
`--no-background`, `-n`, `-F`, or similar command line argument to
@@ -19,65 +20,105 @@ the most reliable way to monitor a service.
However, not all daemons support running in the foreground, or they may
start logging to the foreground as well, these are forking daemons and
are supported using the same syntax as forking `sysv` services, using
the `pid:!/path/to/pidfile.pid` syntax. There is an alternative syntax
that may be more intuitive, where Finit can also guess the PID file
based on the daemon's command name:
are supported using the same syntax as forking `sysv` services, by
naming the file to watch with `pidfile`. There is an alternative that
may be more intuitive, where Finit can also guess the PID file based on
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
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
full `pid:!/path/to/file.pid`.
`/var/run/ntpd.pid`. If Finit guesses wrong, name the file yourself
with `pidfile = "/path/to/file.pid"`.
The file belongs to the service: Finit reads it but does not create or
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
(SIGKILL, OOM, segfault), and the file still names the just-reaped
PID, Finit removes it before the next retry. This prevents daemons
that refuse to start on an existing pidfile (e.g. `dbus-daemon`)
from getting stuck in a crash-restart loop.
**Example:**
In the case of `ospfd` (below), we omit the `-d` flag (daemonize) to
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
optional and default to level 2-5 if omitted.
`<...>` is the condition for starting `ospfd`. In this example Finit
waits for another service, `zebra`, to have created its PID file in
`/var/run/quagga/zebra.pid` before starting `ospfd`. Finit watches
*all* files in `/var/run`, for each file named `*.pid`, or `*/pid`,
Finit opens it and find the matching `NAME:ID` using the PID.
`runlevel` denotes the runlevels `ospfd` is allowed to run in, it is
optional and defaults to level 2-4 if omitted.
`conditions` lists what must be asserted before starting `ospfd`. In
this example Finit waits for another service, `zebra`, to have created
its PID file in `/var/run/quagga/zebra.pid`. Finit watches *all* files
in `/var/run`, for each file named `*.pid`, or `*/pid`, Finit opens it
and finds the matching `NAME:ID` using the PID.
A condition may be prefixed with `~` to propagate a reload of the
upstream service to this one, rather than merely pausing and resuming
it:
conditions = { "~pid/zebra" }
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
application Finit provides a workaround. A `pid` keyword can be set
to have Finit automatically create (when starting) and later remove
(when stopping) the PID file. The file is created in the `/var/run`
directory using the `basename(1)` of the service. The default can be
modified with an optional `pid:`-argument:
application Finit provides a workaround. With `pidfile-create` Finit
creates the file when starting and removes it when stopping. The path
comes from `pidfile`, which takes three forms:
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`,
that PID file will, not only be created and removed automatically, but
also be used by the Finit condition subsystem. So a service/run/task
can depend on `<pid/bar>`, like this foo will not be started until bar
has started:
Such a file is also used by the Finit condition subsystem, so another
service, run or task can depend on `pid/bar`. Here foo is not started
until bar has:
service pid:/run/bar.pid bar -- Bar Service
service <pid/bar> foo -- Foo Service
service bar {
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
has completed starting up and is ready for service.
As an alternative "readiness" notification, Finit supports both systemd
and s6 style notification. This can be enabled by using the `notify`
option:
and s6 style notification. This is enabled with the `notify` key:
* `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
to service events. The [sd_notify()][] API expects `NOTIFY_SOCKET`
to be set to the socket where the application can send `"READY=1\n"`
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"`
and then close their socket. Finit takes care of "hard-wiring" the
READY state as long as the application is running, events across any
@@ -85,7 +126,11 @@ option:
(must be >3) on then command line, Finit provides the following
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
[s6 expect]: https://skarnet.org/software/s6/notifywhenup.html
@@ -99,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:
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`.
Notice how both start in runlevel S, and the coldplug task only runs in
@@ -115,87 +166,191 @@ Non-privileged Services
-----------------------
Every `run`, `task`, or `service` can also list the privileges the
`/path/to/cmd` should be executed with. Prefix the command with
`@USR[:GRP]`, group is optional, like this:
command should be executed with, using `user`, `group` and
`extra-groups`, all optional:
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`
automatically. Any groups the user belongs to will be inherited by
the service.
To specify additional supplementary groups beyond those in
`/etc/group`, list them in `extra-groups`:
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
`caddy`, inheriting any groups `caddy` is a member of in `/etc/group`,
plus the additional `ssl-cert` group. This is useful when a service
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
multiple web servers, add `:ID` somewhere between the `run`, `task`,
`service` keyword and the command, like this:
multiple web servers, add `:ID` to the block title, like this:
service :80 [2345] httpd -f -h /http -p 80 -- Web server
service :8080[2345] httpd -f -h /http -p 8080 -- Old web server
service httpd:80 {
description = "Web server"
runlevel = "2345"
command = "httpd -f -h /http -p 80"
}
Without the `:ID` to the service the latter will overwrite the former
and only the old web server would be started and supervised.
service httpd:8080 {
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
-------------------
Finit support conditional loading of stanzas. The following example is
take from the `system/hotplug.conf` file in the Finit distribution.
Finit supports conditional loading of blocks. The following example is
taken from the `system/10-hotplug.conf` file in the Finit distribution.
Here we only show a simplified subset.
Starting with the `nowarn` option.
Starting with the udev daemon, which goes by two names depending on
how it was built. Both are candidates for the same service:
service nowarn name:udevd pid:udevd /lib/systemd/systemd-udevd
service nowarn name:udevd pid:udevd udevd
service udevd {
pidfile = "udevd"
command = { "/lib/systemd/systemd-udevd", "-udevd" }
}
When loading the .conf file Finit looks for `/lib/systemd/systemd-udevd`
if that is not found Finit automatically logs a warning. The `nowarn`
option disables this warning so that the second line can be evaluated,
which also provides a service named `udevd`.
When loading the .conf file Finit looks for
`/lib/systemd/systemd-udevd`, and if that is not there it moves on to
`udevd`. A candidate that is not installed is expected, so no warning
is logged for the ones that are skipped. The leading `-` on the last
one says it is also fine if none of them are found, in which case the
block is dropped quietly and no service named `udevd` exists.
run nowarn if:udevd <pid/udevd> :1 udevadm settle -t 0
> [!NOTE]
> This needs to be one block. The title is the service identity, so
> two blocks titled `udevd` in the same file are two declarations of
> one service, which Finit rejects. See [Duplicate
> titles](service-opts.md#duplicate-titles).
This line is only loaded if we know of a service named `udevd`. Again,
run udevadm:1 {
runlevel = "S"
if = "udevd"
conditions = { "pid/udevd" }
command = "-udevadm settle -t 0"
}
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
until the PID condition is asserted, i.e., Finit detecting udevd has
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
found, again we do not warn.
Conditional loading statements can also be negated, so the previous stanza
can also be written as:
Conditional loading can also be negated, so the previous block can be
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
resolved. Stanzas marked with `conflict:foo` are rechecked at runtime.
The reason for using `conflicts` in this example is that a conflict can
be resolved. Blocks naming a conflict are rechecked at runtime.
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
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,
2, and 3. It is not considered for other runlevels.
runlevel. E.g., a task with `runlevel = "123"` is qualified to run
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.
Consider this (simplified) example from the Infix operating system:
run [S] name:startup <pid/sysrepo> confd -b --load startup-config
run [S] if:<usr/fail-startup> name:failure <pid/sysrepo> confd --load failure-config
run startup {
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
in true sequence. If loading the file `startup-config` fails confd sets
the condition `usr/fail-startup`, thus allowing the next run statement
to load `failure-config`.
run failure {
runlevel = "S"
if = "usr/fail-startup"
conditions = { "pid/sysrepo" }
command = "confd --load failure-config"
}
Notice the critical difference between the `<pid/sysrepo>` condition and
`if:<usr/fail-startup>`. The former is a condition for starting and the
latter is a condition to check if a run/task/service is qualified to
even be considered.
The two run blocks reside in the same .conf file so Finit runs them in
true sequence. If loading the file `startup-config` fails confd sets
the condition `usr/fail-startup`, thus allowing the next one to load
`failure-config`.
Conditional execution statements can also be negated, so provided the
file loaded did the opposite, i.e., set a condition on success, the
previous stanza can also be written as:
Notice the critical difference between the `conditions` list and `if`.
The former is a condition for starting; the latter is a condition to
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 ..."
}
Variants of one service are often qualified this way, one per platform,
and they usually have to supply the same barrier to whatever waits for
them. Each variant needs its own title, since a title is an identity,
and the shared barrier is named with `provides`. See [Provided
Conditions](service-opts.md#provided-conditions).
+26 -17
View File
@@ -9,42 +9,45 @@ from a serialized boot process.
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
style scripts. The primary intention for this command is to be able to
reuse much of existing setup and init scripts in Linux distributions.
A `sysv` block is a supervised daemon, like `service`, but started and
stopped through a SysV style init script instead of a command line. The
intention is to reuse existing setup and init scripts from Linux
distributions.
When entering an allowed runlevel, Finit calls `init-script start`, when
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
`service` stanzas work.
`service` blocks work.
Forking services started with `sysv` scripts can be monitored by Finit
by declaring the PID file to look for: `pid:!/path/to/pidfile.pid`.
Notice the leading `!`, it signifies Finit should not try to create the
file, but rather watch that file for the resulting forked-off PID. This
syntax also works for forking daemons that do not have a command line
option to run it in the foreground, more on this below in `service`.
by declaring the PID file to look for: `pidfile = "/path/to/file.pid"`.
Finit does not create that file, it watches it for the resulting
forked-off PID. That is the default; `pidfile-create = true` asks Finit
to write it instead. The same applies to forking daemons with no way to
run in the foreground, see [Services](services.md).
> [!TIP]
> See also [SysV Init Compatibility](#sysv-init-compatibility).
`runparts DIRECTORY`
--------------------
Run-parts
---------
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`)
making it perfect for most scenarios.
For syntax details, see the [Run-parts Scripts](runparts.md) section.
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`,
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.
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
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
services in the target runlevel (default 2).
> [!NOTE]
> In Finit releases before v4.5 this script blocked Finit execution and
> made it as good as impossible to call `initctl` during that time.
+60 -5
View File
@@ -1,9 +1,10 @@
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
optional arguments and description. `run` commands are guaranteed to be
@@ -31,16 +32,70 @@ also the `--quiet` and `--batch` options.
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.
Both `run` and `task` commands are run in a shell, so basic pipes and
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
non-trivial constructs are better placed in a separate shell script.
remain-after-exit
-----------------
By default, a `run` or `task` will re-run each time its runlevel is
entered, and its `exec-stop-post` script does not run on completion.
With `remain-after-exit`, the task runs once and does not re-run on
runlevel re-entry:
task firewall {
description = "Firewall setup"
runlevel = "2345"
remain-after-exit = true
command = "/usr/sbin/setup-firewall"
}
This has the following effects:
* The task does not re-run on runlevel re-entry
* The `exec-stop-post` script runs when:
- The task is explicitly stopped (`initctl stop NAME`)
- The task leaves its valid runlevels (e.g., runlevel change)
This is useful for tasks that set up persistent state where:
* Cleanup should only happen on explicit stop or when leaving valid runlevels
* The setup should not be re-run on every runlevel entry
**Example:** Setting up firewall rules with cleanup on shutdown:
```
task firewall {
description = "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 `exec-stop-post` script runs when
entering runlevel 0 (halt) or 6 (reboot), or on explicit stop.
> [!NOTE]
> The `remain-after-exit` setting is not supported for bootstrap-only
> tasks (tasks with only runlevel S). Bootstrap tasks are deleted
> immediately after completion, and their `exec-stop-post` scripts never
> 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:
$ 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
$ initctl enable avahi-autoipd@eth0.conf
The enabled symlink will be set up to `avahi-autoipd@.conf` and every
instance of `%i` will in the instantiated directive be replaced with
`eth0`. Inspect the resulting instantiated template with `initctl show
avahi-autoipd:eth0` and check the status of a running instance with:
instance of `%i` will be replaced with `eth0` before the file is
parsed, so it works in the block title, in any value, and in the
command line alike. Inspect the result with `initctl show
avahi-autoipd:eth0`, and check a running instance with:
```
$ initctl status avahi-autoipd:eth0
+115 -34
View File
@@ -1,67 +1,99 @@
TTYs and Consoles
=================
**Syntax:** `tty [LVLS] <COND> DEV [BAUD] [noclear] [nowait] [nologin] [TERM]`
`tty [LVLS] <COND> CMD <ARGS> [noclear] [nowait]`
`tty [LVLS] <COND> [notty] [rescue]`
**Syntax:** `tty NAME { device = DEV }` -- built-in getty
`tty NAME { command = "CMD ARGS" }` -- external getty
`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
TTY device DEV, in the given runlevels. DEV may be the special keyword
`@console`, which is expanded from `/sys/class/tty/console/active`,
useful on embedded systems.
The block title NAME is what `initctl` shows. The three variants differ
in what they open: `device` runs the built-in getty on that TTY, and DEV
may be the special keyword `@console`, expanded from
`/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.
`command` may name several candidates, `{ "agetty ...", "getty ..." }`,
and Finit runs the first one it finds. See [Command
Candidates](service-opts.md#command-candidates).
> The `tty` stanza inherits runlevel, condition (and other feature)
> parsing from the `service` stanza. So TTYs can run in one or many
Settings common to all three:
| Setting | Alias | Description |
|---|---|---|
| `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 |
| `passenv` | | Pass the environment on to `login`, built-in getty only |
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
> useful e.g. to depend on `<pid/elogind>` before starting a TTY.
**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
agetty or the BusyBox getty.
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
uses is reused for stdio. If the `rescue` option is omitted, a shell is
started (`nologin`, `noclear`, and `nowait` are implied), if the rescue
option is set the bundled `/libexec/finit/sulogin` is started to present
a bare-bones root login prompt. If the root (uid:0, gid:0) user does
not have a password set, no rescue is possible. For more information,
see the [Rescue Mode](rescue.md) section.
device node is required, the same output the kernel uses is reused for
stdio. With `notty` a shell is started (`nologin`, `noclear`, and
`nowait` are implied); with `rescue` the bundled
`/libexec/finit/sulogin` presents a bare-bones root login prompt. If
the root (uid:0, gid:0) user does not have a password set, no rescue is
possible. For more information, see the [Rescue Mode](rescue.md)
section.
By default, the first two syntax variants *clear* the TTY and *wait* for
the user to press enter before starting getty.
**Example:**
tty [12345] /sbin/getty -L 115200 /dev/ttyAMA0 vt100
tty [12345] /sbin/agetty -L ttyAMA0 115200 vt100 nowait
tty getty {
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.
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
embedded systems running multiple unused getty wastes both memory
and CPU cycles, so `wait` is the preferred default.
The `nologin` option disables getty and `/bin/login`, and gives the
user a root (login) shell on the given TTY `<DEV>` immediately.
The `nologin` setting disables getty and `/bin/login`, and gives the
user a root (login) shell on the given TTY immediately.
Needless to say, this is a rather insecure option, but can be very
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
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
it out by querying sysfs: `/sys/class/tty/console/active`. The speed
can be omitted to keep the kernel default.
it out by querying sysfs: `/sys/class/tty/console/active`. Leave
`baud` out to keep the kernel default.
> 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,
@@ -69,18 +101,67 @@ can be omitted to keep the kernel default.
**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
shell prompt as soon as bootstrap is done, without opening any device
node:
tty [12345789] notty
tty board {
runlevel = "12345789"
notty = true
}
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
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
----------------------------
The `tty` setting gives a `run`, `task`, or `service` a controlling
terminal on the given device. The device is opened, set as the
controlling terminal for the session (after `setsid()`), and connected to
the process's stdin, stdout, and stderr. A default `TERM` environment
variable is set based on the device type: `vt102` for serial lines and
`linux` for virtual terminals.
The value may be a device node like `/dev/ttyS0`, or the special
keyword `@console` (see above). Note that `@console` expands only to
the first console, not all.
When `tty` is combined with a `log` block, stdout and stderr are
redirected to the log sink instead of the TTY, but stdin remains
connected to the TTY device.
> The `tty` setting is for `run`, `task`, and `service` blocks only.
> A `tty` block (for getty/login) is a different thing entirely, see
> above.
**Example:**
service foo {
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"
}
+375
View File
@@ -0,0 +1,375 @@
D-Bus Integration
=================
Finit ships with a built-in, brokerless [D-Bus][] implementation, **libink**
(`-link`), that exposes the running init system as a peer on its own private
bus, and optionally on the system bus when `dbus-daemon` is available.
Everything `initctl` does is also reachable from any generic D-Bus tooling —
`dbus-send`, `dbus-monitor`, `gdbus`, language bindings, dashboards,
monitoring agents, etc.
> [!NOTE]
> D-Bus support is enabled by default, opt out at build time with
> `--disable-dbus`, see [Building](build.md) for details. When disabled,
> `initctl` keeps using the legacy `INIT_SOCKET` transport and Finit
> exposes no bus.
Bus address
-----------
| Bus | Address |
|------------------------|---------------------------------------------|
| Local (always) | `unix:path=/run/finit/bus` |
| System (opportunistic) | `unix:path=/var/run/dbus/system_bus_socket` |
The bundled device manager serves its own `org.finit.Device1` the same
way on `unix:path=/run/keventd/bus`, see [keventd](keventd.md); one
socket per daemon, no forwarding between them.
The **local** bus is brokerless: clients connect straight to Finit over a
Unix-domain socket using the standard D-Bus SASL EXTERNAL handshake. No
`dbus-daemon` is required, which makes it suitable for embedded systems that
don't ship one.
The socket is `0660`, owned by `root` and the group given to
`--with-group` at build time, the same gate as `/run/finit/socket` that
`initctl` falls back on. The bus reaches every operation `initctl`
does, so restricting one and not the other would leave the door open.
Members of that group may use it, see [Authorization](#authorization).
Finit accepts at most 64 concurrent local-bus peers; further
connections are refused until one disconnects. Match rules given to
`AddMatch` support the `type`, `interface`, `member`, and `path` keys;
`arg0` and its variants are not implemented.
For the system bus a standard busconfig policy ships in
`dbus-1/org.finit.conf`, installed to `$datadir/dbus-1/system.d`: only
root may own `org.finit`, unprivileged callers reach introspection,
properties, and the read-only methods.
Finit also enforces per-method authorization itself, so a permissive
policy installed by mistake does not open state-changing methods.
The **system** bus is best-effort: Finit probes for a running `dbus-daemon`
and, when reachable, claims the well-known name `org.finit` so that standard
tooling sees Finit just like any other system service:
```sh
dbus-send --system --print-reply --dest=org.finit /org/finit/manager \
org.finit.Manager1.ListServices
dbus-monitor --system "sender='org.finit'"
```
If no system bus is present (the common case on embedded targets), this step
is silently skipped.
Object tree
-----------
```
/
├── org/
│ └── finit/
│ ├── manager Manager1
│ ├── cond Cond1
│ └── service/
│ ├── keventd Service1 (one per service)
│ ├── sshd
│ └── …
└── org/freedesktop/DBus Standard well-known interfaces
```
Every node implements the usual stock interfaces:
| Interface | Purpose |
|---------------------------------------|-------------------------------------------|
| `org.freedesktop.DBus` | `Hello`, `AddMatch`, `RemoveMatch` — on `/org/freedesktop/DBus` only |
| `org.freedesktop.DBus.Peer` | `Ping`, `GetMachineId` |
| `org.freedesktop.DBus.Introspectable` | `Introspect()` — XML description |
| `org.freedesktop.DBus.Properties` | `Get`, `GetAll`; nothing is writable |
Note: `Hello`, `AddMatch`, and `RemoveMatch` are answered on the canonical
`/org/freedesktop/DBus` object only, as per the D-Bus specification.
`org.finit.Manager1`
--------------------
Lives at **`/org/finit/manager`**. Owns the global init operations
and the service registry.
### Methods
| Method | In sig | Out sig | Priv. | Notes |
|------------------------------|--------|---------|-------|-----------------------------------------------------------|
| `ListServices` | — | `as` | no | Returns identities (`name`, `name:id`) of loaded services |
| `GetService` | `s` | `o` | no | Resolves an identity to its `Service1` object path |
| `Start` | `s` | — | yes | Start the service(s) matching the identity |
| `Stop` | `s` | — | yes | Stop the service(s) matching the identity |
| `Restart` | `s` | — | yes | Restart (stop + start) the service(s) |
| `Reload` | — | — | yes | Re-read all `*.conf` and apply changes |
| `SetRunlevel` | `u` | — | yes | Transition to runlevel `u` (0–9) |
| `SetDebug` | — | — | yes | Toggle Finit's runtime debug flag |
| `Signal` | `su` | — | yes | Send signal `u` (1–31) to services matching identity `s` |
| `Suspend` | — | — | yes | `sync()` + suspend-to-RAM |
| `Reboot`, `Halt`, `Poweroff` | `u` | — | yes | Trigger the corresponding shutdown sequence; the argument arms the emergency fallback timer (seconds, 0 = none) |
Edge cases follow the legacy `initctl` protocol: `Suspend` and the
reboot family reply `WrongRunlevel` during bootstrap and shutdown,
`SetRunlevel` is a warning + no-op in runlevel 0/6 and records the
level for the end of bootstrap in runlevel S, and `Signal` fails for
stopped services. One deliberate divergence: a `SetRunlevel` argument
outside 0–9 returns `InvalidArgs`, where the legacy protocol
acknowledges silently.
### Properties
All read-only strings; observable via `Properties.Get` and
`Properties.GetAll`.
| Property | Type | Returns |
|----------------|------|--------------------------------------------------------|
| `Runlevel` | `s` | Current runlevel as a digit (`"2"`, `"3"`, …) or `"S"` |
| `PrevRunlevel` | `s` | Previous runlevel, same encoding, `"N"` when none |
| `Version` | `s` | Finit's version string (`PACKAGE_VERSION`) |
### Signals
| Signal | Body | Fires when |
|-----------------------|----------------------------------------|------------------------|
| `ServiceStateChanged` | `sss` — identity, old state, new state | Service transitions |
| `RunlevelChanged` | `ss` — old level, new level | System runlevel change |
| `ConfigReloaded` | — | `initctl reload` completed; providers of generation-file conditions re-assert |
State names emitted by `ServiceStateChanged` are stable wire strings:
`halted`, `done`, `dead`, `cleanup`, `teardown`, `stopping`, `setup`,
`paused`, `waiting`, `starting`, `running`. `RunlevelChanged` levels
use the same encoding as the `Runlevel` property: digits, `"S"`, `"N"`.
`org.finit.Service1` (per-service objects)
------------------------------------------
Lives at **`/org/finit/service/<encoded>`**, one object per loaded service.
`<encoded>` is the service identity (name, or `name:id` for templated
services) put through systemd-style `_HH` hex escaping — ASCII alphanumerics
and `_` pass through, anything else becomes `_HH` where `HH` is the hex byte.
Use `Manager1.GetService(identity)` to look up the exact path rather than
constructing it by hand.
| Method | In sig | Out sig | Priv. | Notes |
|-----------|--------|---------|-------|-------------------------------------------------------------|
| `Start` | — | — | yes | Equivalent to `Manager1.Start(<identity>)` for this service |
| `Stop` | — | — | yes | … |
| `Restart` | — | — | yes | … |
| `Reload` | — | — | yes | Send the service's `reload-signal`, default `SIGHUP`; restart when set to `"none"` |
### Properties
All read-only; observable via `Properties.Get` and `Properties.GetAll`.
| Property | Type | Returns |
|-----------------|------|----------------------------------------------------------------|
| `Identity` | `s` | Service identity, `name` or `name:id` |
| `Name` | `s` | Program name (basename of the command) |
| `State` | `s` | Current status, same vocabulary as `initctl status` |
| `Pid` | `u` | Current PID, 0 when not running |
| `RestartCount` | `u` | Restarts since the last stable run |
| `Runlevels` | `u` | Allowed runlevels as a bitmask, bit N = runlevel N, bit 10 = S |
| `Description` | `s` | The service's `description` string |
| `Command` | `s` | Full command line, arguments included |
| `Conditions` | `s` | Declared conditions, raw `.conf` form |
| `Type` | `s` | Unit type: `service`, `task`, `run`, `sysv`, `tty`, `free` |
| `Origin` | `s` | Source `.conf` file, empty for built-ins |
| `Environment` | `s` | The service's `env` setting, raw |
| `PidFile` | `s` | Declared PID file, raw (`!` prefix included) |
| `User` | `s` | User the service runs as |
| `Group` | `s` | Group the service runs as |
| `Uptime` | `u` | Seconds since start, 0 when not running |
| `ExitStatus` | `u` | Raw `waitpid(2)` status from the last exit |
| `RestartsTotal` | `u` | Restarts over the service's lifetime |
| `RestartMax` | `u` | Restart limit before the service is blocked |
| `Starts` | `u` | Times started, for `manual-start` units |
| `ManualStart` | `b` | `manual-start` set in the `.conf` |
| `Forking` | `b` | Daemon forks to background |
| `Started` | `b` | Run/task completed successfully |
On every state transition the object also emits the standard
`org.freedesktop.DBus.Properties.PropertiesChanged` signal: `State`
in the changed dictionary, `Pid` and `RestartCount` invalidated (call
`Get` for fresh values).
The per-service surface lets generic tooling supply an object handle
once and then invoke methods on it, instead of repeatedly passing the
identity string.
`org.finit.Cond1`
-----------------
Lives at **`/org/finit/cond`**. Exposes Finit's
[condition system](conditions.md) to bus clients.
### Methods
| Method | In sig | Out sig | Priv. | Notes |
|---------|--------|---------|-------|--------------------------------------------------------------|
| `Get` | `s` | `s` | no | Returns `"on"`, `"off"`, or `"flux"` for the named condition |
| `Set` | `s` | — | yes | Assert a `usr/<name>` condition |
| `Clear` | `s` | — | yes | Deassert a `usr/<name>` condition |
| `List` | — | `as` | no | Names of all known conditions |
| `Dump` | — | `a(ss)` | no | `(name, state)` pairs for everything `List` returns |
Note: non-`usr/*` paths are rejected with `InvalidArgs` -- system
conditions belong to Finit's state machine.
A service consuming such a condition:
service alarm {
description = "Foo alarm"
runlevel = "2345"
conditions = { "usr/data-ready" }
command = "alarm --arg foo"
}
asserted from any bus client with `Cond1.Set string:"data-ready"`, see
the examples below.
### Signals
| Signal | Body | Fires when |
|--------------------|------------------------|---------------------------------------|
| `ConditionChanged` | `ss` — name, new state | A condition is asserted or deasserted |
Authorization
-------------
Privileged methods accept `root`, and any caller belonging to the group given
to `--with-group` at build time. That is the same set the socket mode already
admits, so the two gates agree instead of the socket letting the group in and
every method turning it away.
On the **local** bus the kernel settles this at `connect()`: `SO_PEERCRED`
gives Finit the caller's uid and `SO_PEERGROUPS` its group set, both straight
from the kernel. Finit matches the group against `--with-group` itself, so
the check never touches NSS -- `getpwuid`/`getgrouplist` can block on a slow
LDAP or SSSD backend, and PID 1 must never block. Because the group set is
the caller's real credentials, privilege escalation through the bus is
impossible.
On the **system** bus one connection carries every caller, so `SO_PEERCRED`
describes `dbus-daemon` rather than whoever asked. Finit asks the bus driver
`GetConnectionUnixUser` about the message sender and holds the call until the
answer arrives -- nothing blocks, the reply comes back through the same event
loop as everything else. The broker does not report the caller's groups in
that reply, so **privileged methods over the system bus are root-only**; group
membership is honoured on the local bus only. Lifting that is future work,
see `libink/README.md`.
Answers are cached per sender. A bus never reuses a unique name while it
runs, so an answer holds for as long as that bus does; Finit empties the cache
when the broker goes away, since a new one numbers its clients from scratch.
A caller Finit cannot identify is refused, so the failure mode is a denial
rather than an escalation.
When a privileged method is rejected the error name is exactly
`org.freedesktop.DBus.Error.AccessDenied`, and the body carries a short reason
string.
`initctl` integration
---------------------
`initctl` transparently routes through D-Bus when the bus socket is present,
and falls back to the legacy `INIT_SOCKET` transport otherwise. Concretely,
the following subcommands use the bus first:
| Subcommand | Method |
|----------------------------|--------------------------------------------------|
| `initctl start` | `Manager1.Start(svc)` |
| `initctl stop` | `Manager1.Stop(svc)` |
| `initctl restart` | `Manager1.Restart(svc)` |
| `initctl reload` | `Manager1.Reload()` |
| `initctl reload S` | `Service1.Reload()` (per-svc) |
| `initctl reboot` | `Manager1.Reboot(timeout)` |
| `initctl halt` | `Manager1.Halt(timeout)` |
| `initctl poweroff` | `Manager1.Poweroff(timeout)` |
| `initctl suspend` | `Manager1.Suspend()` |
| `initctl debug` | `Manager1.SetDebug()` |
| `initctl signal` (or `kill`) | `Manager1.Signal(svc, signo)` |
| `initctl runlevel` | `Properties.Get(Manager1.Runlevel/PrevRunlevel)` |
| `initctl cond set/get/clear` | `Cond1.{Set,Get,Clear}`, `clr` is an alias |
Two `initctl` subcommands are pure D-Bus features without legacy equivalents:
* `initctl monitor` — subscribes to every signal on the local bus and prints
one line per delivery (with timestamp, interface and member). Same idea as
`dbus-monitor`, but scoped to Finit and with no need to pass `--address`.
* `initctl cond` emits the standard `Cond1.ConditionChanged` signal as a side
effect, so subscribers observe user-driven state changes the same way they
observe service-driven ones.
Examples
--------
The examples below use `dbus-send` and `dbus-monitor`, which ship as part of
the [dbus][] reference implementation; they're widely packaged and don't pull
in any extra runtime. Any tool that speaks D-Bus over an AF_UNIX socket works
equally well — `gdbus`, Python's `jeepney`/`dasbus`, etc. — substitute their
syntax for setting the bus address. The wire protocol is the compatibility
surface: Finit's own **libink** is an internal implementation detail, external
clients should use any standard D-Bus library.
When `org.finit` is registered on the system bus you can replace
`--address=unix:path=/run/finit/bus` with `--system` in any example below.
List the running services:
```sh
dbus-send --address=unix:path=/run/finit/bus \
--type=method_call --print-reply --dest=org.finit \
/org/finit/manager \
org.finit.Manager1.ListServices
```
Read the current runlevel via the Properties interface:
```sh
dbus-send --address=unix:path=/run/finit/bus \
--type=method_call --print-reply --dest=org.finit \
/org/finit/manager \
org.freedesktop.DBus.Properties.Get \
string:org.finit.Manager1 string:Runlevel
```
Subscribe to every state change on the manager object:
```sh
dbus-monitor --address=unix:path=/run/finit/bus \
"type='signal',interface='org.finit.Manager1'"
```
Or use `initctl monitor`, which does the same without any address
plumbing.
Restart a service by its object path:
```sh
dbus-send --address=unix:path=/run/finit/bus \
--type=method_call --dest=org.finit \
/org/finit/service/sshd \
org.finit.Service1.Restart
```
Trigger a `usr/`-condition assertion that wakes any dependent service:
```sh
dbus-send --address=unix:path=/run/finit/bus \
--type=method_call --dest=org.finit \
/org/finit/cond \
org.finit.Cond1.Set string:"data-ready"
```
The `--dest=org.finit` argument is informational on the local
brokerless bus — Finit accepts any destination because there's no
broker to route by name — but `dbus-send` requires it syntactically.
[dbus]: https://gitlab.freedesktop.org/dbus/dbus
[D-Bus]: https://dbus.freedesktop.org/doc/dbus-specification.html
+105 -46
View File
@@ -1,80 +1,139 @@
Distro Recommendations
======================
By default Finit uses the following directories for configuration files:
Finit supports two directory layouts for managing service configurations:
1. **Simple layout** - all configuration files directly in `/etc/finit.d/`
2. **Distro layout** - separate `available/` and `enabled/` directories
The distro layout is recommended for distributions that need clear
separation between installed and enabled services, and want to use the
`initctl enable/disable` commands for service management.
## Simple Layout
Finit supports using a flat directory structure:
```
/etc/
|-- finit.d/ -- Regular (enabled) services
|-- finit.d/ -- All enabled services
| |-- lighttpd.conf
| `- *.conf
|-- finit.conf -- Bootstrap tasks and services
:
```
To enable a service one simply drops a small configuration file in the
`/etc/finit.d/` directory. This practice works with systems that
keep disabled services elsewhere, or generates them as needed from some
other tool.
In this layout, you enable a service by placing its `.conf` file in
`/etc/finit.d/` and disable it by removing the file. This works well for
embedded systems or custom setups where services are managed by other tools
or generated dynamically.
Distributions, however, may want a clearer separation of enabled and
available (installed but not enabled) services. They may even want to
customize the directories used, for brand labeling or uniformity.
> [!NOTE] No tooling support
> The `initctl enable/disable` commands **do not work** with this layout.
> You must manually manage files in `/etc/finit.d/`.
To that end Finit allows for a sub-directory `/etc/finit.d/available/`
where installed but disabled services can reside. Adding a symlink to a
configuration in this sub-directory enables the service, but it will not
be started.
## Distro Layout
To change the default configuration directory and configuration file
names the Finit `configure` script offers the following two options at
build time:
Distributions typically want clearer separation between available (installed)
and enabled services. Finit supports this through the `available/` and
`enabled/` subdirectories:
```
/etc/
|-- finit.d/
| |-- available/ -- Installed but disabled services
| | |-- httpd.conf
| | |-- ntpd.conf
| | `-- sshd.conf
| |-- enabled/ -- Enabled services (symlinks)
| | `- sshd.conf -- Symlink to ../available/sshd.conf
|-- finit.conf -- Bootstrap tasks and services
:
```
In this layout:
- **available/** contains all installed service configurations
- **enabled/** contains symlinks to configurations that should start at boot
- Services are enabled/disabled by creating/removing symlinks
> [!IMPORTANT] Recommended
> This is the recommended layout. In fact, the `initctl enable/disable`
> commands **require** this layout with both `available/` and `enabled/`
> directories and will not work without it.
## Managing Services with initctl
When using the distro layout, the `initctl` tool provides convenient commands
for managing service configurations:
```
list List all .conf in /etc/finit.d/
enable <CONF> Enable .conf by creating symlink in enabled/
disable <CONF> Disable .conf by removing symlink from enabled/
reload Reload *.conf in /etc/finit.d/ (activate changes)
```
Example usage:
```bash
# Enable sshd (creates enabled/sshd.conf -> ../available/sshd.conf)
initctl enable sshd
# Disable httpd (removes enabled/httpd.conf symlink)
initctl disable httpd
# Apply changes (reload configuration and start/stop services)
initctl reload
```
The `.conf` suffix is optional - `initctl` adds it automatically if missing.
> [!NOTE] Remember `initctl reload`
> Changes made with `enable`/`disable` take effect only after running
> `initctl reload`, unless Finit was built with `--enable-auto-reload`
> which automatically detects and applies configuration changes.
### Service Overrides
The `initctl` tool only operates on symlinks in the `enabled/` directory.
If you place a regular (non-symlink) `.conf` file directly in the parent
directory (e.g., `/etc/finit.d/`), `initctl` will ignore it. This allows
you to create system-level overrides that cannot be accidentally disabled
by package management tools.
## Customizing Directory Paths
Distributions can customize the directory names and locations at build time
using the Finit `configure` script:
```sh
./configure --with-rcsd=/etc/init.d --with-config=/etc/init.d/init.conf
```
> [!IMPORTANT]
> Remember `--prefix` et al as well, the default is likely *not* what
> you want. See the [build docs][1] for details.
This changes the default `/etc/finit.d/` to `/etc/init.d/` and moves the
bootstrap configuration file accordingly.
The resulting directory structure is depicted below. Please notice how
`/etc/finit.conf` now resides in the same sub-directory as a non-symlink
`/etc/init.d/init.conf`:
> [!IMPORTANT] Remember to set `--prefix`
> Remember to set `--prefix` and related options appropriately. The default
> prefix is `/usr/local`, which is likely not what you want for a system
> init. See the [build documentation][1] for details.
Example with custom paths:
```
/etc/
|-- init.d/
| |-- available/ -- Regular (disabled) services
| |-- available/ -- Installed services
| | |-- httpd.conf
| | |-- ntpd.conf
| | `-- sshd.conf
| |-- sshd.conf -- Symlink to available/sshd.conf
| |-- enabled/ -- Enabled services (symlinks)
| | `- sshd.conf -- Symlink to ../available/sshd.conf
| `- init.conf -- Bootstrap tasks and services
:
```
To facilitate the task of managing configurations, be it a service,
task, run, or other stanza, the `initctl` tool has a a few built-in
commands:
```
list List all .conf in /etc/finit.d/
enable <CONF> Enable .conf in /etc/finit.d/available/
disable <CONF> Disable .conf in /etc/finit.d/[enabled/]
reload Reload *.conf in /etc/finit.d/ (activates changes)
```
To enable a service like `sshd.conf`, above, use
initctl enable sshd
The `.conf` suffix is not needed, `initctl` adds it implicitly if it is
missing. The `disable` command works in a similar fashion.
Note, however, that `initctl` only operates on symlinks and it always
requires the `available/` sub-directory. Any non-symlink in the parent
directory, here `/etc/init.d/`, will be considered a system override by
`initctl`.
Notice how both the service directory and bootstrap configuration file now
use the custom `/etc/init.d/` path specified at build time.
[1]: build.md#configure
+136 -59
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][].
> [!TIP]
> As of Finit v4.4, `.conf` lines can be broken up using the standard UNIX
> continuation character (`\`), trailing comments are also supported. The
> latter means you must escape any hashes used in directives and descriptions
> (`\#`). For more on this and examples, see the [finit.conf(5)][] manual or
> the [Configuration](config/index.md) section.
> A block spans as many lines as it needs, so no continuation character is
> called for. For the full syntax, see the [finit.conf(5)][] manual or the
> [Configuration](config/index.md) section.
```ApacheConf
# Fallback if /etc/hostname is missing
host default
hostname = "default"
# Runlevel to start after bootstrap, 'S', default: 2
#runlevel 2
#runlevel = 2
# Support for setting global environment variables, using foo=bar syntax
# be careful though with variables like PATH, SHELL, LOGNAME, etc.
#PATH=/usr/bin:/bin:/usr/sbin:/sbin
# Global environment variables, be careful though with variables like
# PATH, SHELL, LOGNAME, etc.
#environment {
# PATH = "/usr/bin:/bin:/usr/sbin:/sbin"
#}
# 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 size=100k count=4
log {
size = 100k
count = 4
}
# Services to be monitored and respawned as needed
service [S12345] env:-/etc/conf.d/watchdog watchdog $WATCHDOG_OPTS $WATCHDOG_DEV -- System watchdog daemon
service [S12345] env:-/etc/conf.d/syslog syslogd -n $SYSLOGD_OPTS -- System log daemon
service [S12345] <pid/syslogd> env:-/etc/conf.d/klogd klogd -n $KLOGD_OPTS -- Kernel log daemon
service [2345] env:-/etc/conf.d/lldpd lldpd -d $LLDPD_OPTS -- LLDP daemon (IEEE 802.1ab)
service watchdog {
description = "System watchdog daemon"
runlevel = "S12345"
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
# 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
# a dependency on a system default route (gateway) to be set. A single
# <!> at the beginning means ntpd does not respect SIGHUP for restart.
service [2345] log:/var/log/ntpd.log <!net/route/default> ntpd -n -l -I eth0 -- NTP daemon
# a dependency on a system default route (gateway) to be set. ntpd
# does not respect SIGHUP, so Finit restarts it on reload instead.
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
# the service/run/task keyword and the command.
service :80 [2345] merecat -n -p 80 /var/www -- Web server
service :8080 [2345] merecat -n -p 8080 /var/www -- Old web server
# For multiple instances of the same service, add :ID to the block title.
service merecat:80 {
description = "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
#sysv [S] /etc/init.d/keyboard-setup -- Setting up preliminary keymap
#sysv [S] /etc/init.d/acpid -- Starting ACPI Daemon
#task [S] /etc/init.d/kbd -- Preparing console
#sysv keyboard-setup {
# description = "Setting up preliminary keymap"
# runlevel = "S"
# command = "/etc/init.d/keyboard-setup"
#}
# Hidden from boot progress, using empty `--` description
#sysv [S] /etc/init.d/keyboard-setup --
#sysv [S] /etc/init.d/acpid --
#task [S] /etc/init.d/kbd --
# Hidden from boot progress, using an empty description
#sysv acpid {
# description = ""
# runlevel = "S"
# command = "/etc/init.d/acpid"
#}
# Run start scripts from this directory
# runparts /etc/start.d
#runparts = "/etc/start.d"
# Virtual consoles run BusyBox getty, keep kernel default speed
tty [12345] /sbin/getty -L 0 /dev/tty1 linux nowait noclear
tty [2345] /sbin/getty -L 0 /dev/tty2 linux nowait noclear
tty [2345] /sbin/getty -L 0 /dev/tty3 linux nowait noclear
tty tty1 {
runlevel = "12345"
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
#tty [12345] /dev/ttyAMA0 noclear nowait
#tty [12345] /dev/ttyUSB0 noclear
#tty ttyAMA0 { runlevel = "12345" device = "/dev/ttyAMA0" noclear = true nowait = true }
#tty ttyUSB0 { runlevel = "12345" device = "/dev/ttyUSB0" noclear = true }
# 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.
Here's a quick overview of some of the most common components needed to start
a UNIX daemon:
```
service [LVLS] <COND> log env:[-]/etc/default/daemon daemon ARGS -- Example daemon
^ ^ ^ ^ ^ ^ ^ ^
| | | | | | | `---------- Optional description
| | | | | | `------------------ Daemon arguments
| | | | | `------------------------- Path to daemon
| | | | `---------------------------------------------------- Optional env. file
| | | `-------------------------------------------------------- Redirect output to log
| | `--------------------------------------------------------------- Optional conditions
| `---------------------------------------------------------------------- Optional Runlevels
`------------------------------------------------------------------------------ Supervised program (daemon)
service NAME { <-- Supervised program (daemon)
description = "Example daemon" <-- Optional description
runlevel = "2345" <-- Optional runlevels
conditions = { "net/route/default" } <-- Optional conditions
envfile = "-/etc/default/daemon" <-- Optional env. file
log { } <-- Redirect output to log
command = "daemon ARGS" <-- Path to daemon, and its arguments
}
```
Some components are optional: runlevel(s), condition(s) and description,
making it easy to create simple start scripts and still possible for more
advanced uses as well:
Only `command` is required, which makes simple cases short while leaving
room for more advanced uses:
service /usr/sbin/sshd -D
service sshd {
command = "/usr/sbin/sshd -D"
}
Dependencies are handled using [conditions](conditions.md). One of
the most common conditions is to wait for basic networking to become
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
`ntpd` until `syslogd` has started properly. Finit waits for `syslogd`
to create its PID file, by default `/var/run/syslogd.pid`.
service [2345] log <!pid/syslogd> ntpd -n -N -p pool.ntp.org
service [S12345] syslogd -n -- Syslog daemon
service ntpd {
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
run in the foreground. With `log` Finit redirects `stdout` + `stderr`
to the system log daemon using the command line `logger(1)` tool.
service syslogd {
description = "Syslog daemon"
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
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
and as soon as it comes back up again `ntpd` is restarted automatically.
@@ -134,4 +211,4 @@ and as soon as it comes back up again `ntpd` is restarted automatically.
[finit.conf(5)]: https://man.troglobit.com/man5/finit.conf.5.html
[infix]: https://kernelkit.github.io
[myLinux]: https://github.com/troglobit/myLinux/
[contrib]: https://github.com/troglobit/finit/tree/master/contrib
[contrib]: https://github.com/finit-project/finit/tree/master/contrib
+196 -20
View File
@@ -1,6 +1,10 @@
Finit Features
==============
This page highlights some of Finit's key features with examples and usage
scenarios. For complete documentation, configuration syntax, and advanced
options, see the [Configuration](config/index.md) section.
**Process Supervision**
Start, monitor and restart services should they fail.
@@ -14,9 +18,9 @@ waits for user input before handing over to `/bin/login`, which is
responsible for handling the actual authentication.
```conf
tty [12345] /dev/tty1 nowait linux
tty [12345] /dev/ttyAMA0 noclear vt100
tty [12345] /sbin/getty -L /dev/ttyAMA0 vt100
tty tty1 { runlevel = "12345" device = "/dev/tty1" term = "linux" nowait = true }
tty ttyAMA0 { runlevel = "12345" device = "/dev/ttyAMA0" term = "vt100" noclear = true }
tty getty { runlevel = "12345" command = "/sbin/getty -L /dev/ttyAMA0 vt100" }
```
Users of embedded systems may want to enable automatic serial console
@@ -25,7 +29,7 @@ system uses `ttyS0`, `ttyAMA0`, `ttyMXC0`, or anything else. Finit
figures it out by querying sysfs: `/sys/class/tty/console/active`.
```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
@@ -36,21 +40,21 @@ see the [TTY and Consoles](config/tty.md) section.
**Runlevels**
Support for SysV init-style [runlevels][5] is available, in the same
minimal style as everything else in Finit. The `[2345]` syntax can be
applied to service, task, run, and TTY stanzas.
minimal style as everything else in Finit. The `runlevel` setting
applies to service, task, run, and tty blocks alike.
Reserved runlevels are 0 and 6, halt and reboot, respectively just like
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
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
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
by using `run [S] cmd`. Changing runlevels at runtime is done like any
other init, e.g. <kbd>init 4</kbd>, but also using the more advanced
[`initctl`](initctl.md) tool.
by using a `run` block with `runlevel = "S"`. Changing runlevels at
runtime is done like any other init, e.g. <kbd>init 4</kbd>, but also
using the more advanced [`initctl`](initctl.md) tool.
**Conditions**
@@ -82,8 +86,17 @@ the condition `<hw/model/foo>` when starting other scripts. Here is an
example:
```
run [S] /path/to/ident --
task [2] <hw/model/foo> /path/to/foo-init -- Initializing Foo board
run ident {
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]
@@ -113,7 +126,7 @@ hook into the boot process or respond to various I/O.
For more information, see the [Plugins](plugins.md) section.
**Automatic Reload**
**Automatic Reload**{ #automatic-reload }
By default, Finit monitors `/etc/finit.d/` and `/etc/finit.d/enabled/`
registering any changes to `.conf` files. To activate a change the user
@@ -130,6 +143,83 @@ unnecessary overhead, which can be removed at build-time using:
configure --enable-auto-reload
**Linux Capabilities**
Finit supports Linux capabilities, allowing services to run with minimal
required privileges instead of running as root. This improves security by
following the principle of least privilege.
```conf
service nginx {
runlevel = "2345"
user = "www-data"
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
the ability to bind to privileged ports (80, 443) through the
`cap_net_bind_service` capability.
The `capabilities` list uses the IAB (Inheritable, Ambient, Bounding) format:
- `^` = Ambient (recommended) - capabilities survive exec()
- `%` = Inheritable only - requires file capabilities
- `!` = Bounding - block from acquiring capability
Multiple capabilities can be specified as comma-separated:
```conf
capabilities = { "^cap_net_raw", "^cap_net_admin", "!cap_sys_admin" }
```
See the [Linux Capabilities](config/capabilities.md) section for detailed
information, examples, and security best practices.
**PAM Sessions**
A service can run inside a PAM session, so the `session` stack in
`/etc/pam.d` applies to it, e.g. limits from `pam_limits`:
```conf
service weston {
user = "weston"
pam = "weston-autologin"
command = "/usr/bin/weston --continue-without-input"
}
```
Without a `user` the session is for root. Requires a build with PAM
support, which is the default; see `--disable-pam`.
See the [PAM Sessions](config/pam.md) section for the pam.d file the
example needs, and for how PAM and Finit settings interact.
**Supplementary Groups**
Finit supports supplementary groups for services, allowing them to access
resources owned by multiple groups without running as root. This complements
capabilities for fine-grained privilege control.
```conf
service @caddy:caddy,ssl-cert /usr/bin/caddy run
```
In this example, the Caddy web server runs as user `caddy` with primary group
`caddy`, but also has access to resources owned by the `ssl-cert` group (such
as TLS certificates).
Finit automatically reads the user's supplementary group membership from
`/etc/group`. Additional groups can be specified explicitly using the syntax
`@user:group,sup1,sup2,...`.
See the [Non-privileged Services](config/services.md#non-privileged-services)
section for more information.
**Cgroups**
Finit supports cgroups v2 and comes with the following default groups in
@@ -152,15 +242,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
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 [...] <...> /path/to/foo args -- description
service [...] <...> /path/to/bar args -- description
service bar {
cgroup root {}
command = "/path/to/bar args"
}
See the [Cgroups](config/cgroups.md) section for more information, e.g.,
how to configure per-group limits.
@@ -175,4 +269,86 @@ commands for details.
> disabled early at boot.
**Service Management**
Finit includes the `initctl` tool for managing services and system state at
runtime. Key capabilities include:
- **Enable/Disable services**: Manage which services start at boot by moving
configuration files between `/etc/finit.d/available` and
`/etc/finit.d/enabled`
- **Start/Stop/Restart**: Control individual services without requiring a
full system reboot
- **Status monitoring**: View service state, PID, uptime, and resource usage
- **Condition management**: Set and clear user-defined conditions to control
service dependencies
- **Cgroup monitoring**: Real-time process and resource monitoring with
`initctl top`, similar to the traditional `top` command but cgroup-aware
Example commands:
```bash
initctl enable myservice # Enable service for next boot
initctl start myservice # Start service now
initctl status # Show all services
initctl top # Interactive resource monitor
initctl cond set usr/custom # Set custom condition
```
`initctl` rides Finit's built-in [D-Bus API](dbus.md) when available,
which is also open to any other bus client, including `dbus-send` and
language bindings.
See the [Commands & Status](initctl.md) section for complete documentation.
**Rescue Mode**
Finit provides a built-in rescue mode for system recovery and maintenance.
When booting with the `rescue` kernel parameter, the system enters a
protected maintenance shell.
If the bundled `sulogin` program is available (from Finit, util-linux, or
BusyBox), you'll be prompted for the root password before accessing the
maintenance shell. This provides secure access for system recovery.
If `sulogin` is not available, Finit falls back to reading
`/lib/finit/rescue.conf` and boots the system in a limited maintenance mode.
```
# Kernel command line
linux /vmlinuz root=/dev/sda1 rescue
```
In rescue mode, `initctl` will not work. After fixing the problem, use
`reboot -f` to force reboot.
Rescue mode can be disabled at build time with `configure --without-rescue`.
See the [Rescue Mode](config/rescue.md) section for more information.
**Switch Root**
Finit supports switching from an initramfs to a real root filesystem using
the built-in `initctl switch-root` command. This allows Finit to serve as
the init system in an initramfs for early boot tasks (LUKS unlock, LVM
activation, network boot) before transitioning to the real root.
```bash
# In initramfs, after mounting the real root:
initctl switch-root /mnt/root
```
The switch-root operation:
1. Runs the `HOOK_SWITCH_ROOT` hook for cleanup
2. Stops all services gracefully
3. Moves virtual filesystems (`/dev`, `/proc`, `/sys`, `/run`) to the new root
4. Deletes initramfs contents to free memory
5. Pivots to the new root and execs the new init
See the [Switch Root](switchroot.md) section for complete documentation.
[5]: https://en.wikipedia.org/wiki/Runlevel
+22 -15
View File
@@ -1,7 +1,7 @@
Introduction
============
![Alpine screenshot](img/alpine-screenshot2.png){ align=right }
![Alpine screenshot](img/alpine-screenshot2.png){ align=right width=40% }
> Reverse engineered from the [EeePC fastinit][]
> "gaps filled with frog DNA …"
@@ -17,26 +17,32 @@ PID file monitoring, or [conditions](conditions.md).
Features
--------
* [Runlevels][5], defined per service
* One-shot tasks, services (daemons), or [SysV init][4] start/stop scripts
* Runparts and `/etc/rc.local` support
* [Runlevels](config/runlevels.md), defined per service
* [One-shot tasks](config/task-and-run.md), [services](config/services.md) (daemons), or [SysV init][4] start/stop scripts
* [Runparts](config/runparts.md) and `/etc/rc.local` support
* Process supervision similar to [systemd][]
* Sourcing environment files
* Conditions for network/process/custom dependencies
* Readiness notification; PID files (native) for synchronizing system
* Fine-grained privilege control:
- [Linux capabilities](config/capabilities.md) for minimal required privileges
- [Supplementary groups](config/services.md#non-privileged-services) for multi-group resource access
* [Sourcing environment files](config/service-env.md)
* [Conditions](conditions.md) for network/process/custom dependencies
* [Readiness notification](config/service-sync.md); PID files (native) for synchronizing system
startup, support for systemd [sd_notify()][], or [s6 style][] too
* Limited support for [tmpfiles.d(5)][] (no aging, attributes, or subvolumes)
* Pre/Post script actions
* [Pre/Post script actions](config/service-wrappers.md)
* Rudimentary [templating support](config/templating.md)
* Tooling to enable/disable services
* Built-in getty
* [Tooling](initctl.md) to enable/disable services
* [D-Bus API](dbus.md), `org.finit` control and signals for bus clients
* [Automatic reload](features.md#automatic-reload) of modified configuration files (optional)
* [Built-in getty](config/tty.md)
* Built-in watchdog, with support for hand-over to [watchdogd][]
* Built-in support for Debian/BusyBox [`/etc/network/interfaces`][3]
* Cgroups v2, both configuration and monitoring in `initctl top`
* Plugin support for customization
* Proper rescue mode with bundled `sulogin` for protected maintenance shell
* [Cgroups v2](config/cgroups.md), both configuration and monitoring in [`initctl top`](initctl.md)
* [Plugin support](plugins.md) for customization
* Proper [rescue mode](config/rescue.md) with bundled `sulogin` for protected maintenance shell
* [Switch root](switchroot.md) support for initramfs-to-real-root transitions
* Integration with [watchdogd][] for full system supervision
* Logging to kernel ring buffer before `syslogd` has started, see the
* [Logging](config/logging.md) to kernel ring buffer before `syslogd` has started, see the
recommended [sysklogd][] project for complete logging integration
and how to log to the kernel ring buffer from scripts using `logger`
@@ -63,7 +69,8 @@ and proposed extensions.
[3]: https://manpages.debian.org/unstable/ifupdown2/interfaces.5.en.html
[4]: https://en.wikipedia.org/wiki/Init
[5]: https://en.wikipedia.org/wiki/Runlevel
[6]: https://github.com/troglobit/finit
[6]: https://github.com/finit-project/finit
[dinit]: https://davmac.org/projects/dinit/
[systemd]: https://www.freedesktop.org/wiki/Software/systemd/
[sd_notify()]: https://www.freedesktop.org/software/systemd/man/sd_notify.html
[s6 style]: https://skarnet.org/software/s6/notifywhenup.html
+32 -9
View File
@@ -5,6 +5,11 @@ Finit also implements a modern API to query status, and start/stop
services, called `initctl`. Unlike `telinit` the `initctl` tool does
not return until the given command has fully completed.
Since Finit v5, `initctl` talks to Finit over the built-in
[D-Bus API](dbus.md) when the bus socket is available, falling back
to the legacy `INIT_SOCKET` transport. The `monitor` command streams
the `org.finit` signals until interrupted.
```
Usage: initctl [OPTIONS] [COMMAND]
@@ -14,6 +19,7 @@ Options:
-f, --force Ignore missing files and arguments, never prompt
-h, --help This help text
-j, --json JSON output in 'status' and 'cond' commands
-n, --noerr Ignore error, e.g., already started/enabled/...
-1, --once Only one lap in commands like 'top'
-p, --plain Use plain table headings, no ctrl chars
-q, --quiet Silent, only return status of command
@@ -26,15 +32,8 @@ Commands:
help This help text
version Show program version
ls | list List all .conf in /etc/finit.d
create <CONF> Create .conf in /etc/finit.d/available
delete <CONF> Delete .conf in /etc/finit.d/available
show <CONF> Show .conf in /etc/finit.d/available
edit <CONF> Edit .conf in /etc/finit.d/available
touch <CONF> Change .conf in /etc/finit.d/available
enable <CONF> Enable .conf in /etc/finit.d/available
disable <CONF> Disable .conf in /etc/finit.d/enabled
reload Reload *.conf in /etc/finit.d (activate changes)
show Show /etc/finit.conf
reload Reload /etc/finit.conf (activate changes)
cond set <COND> Set (assert) user-defined conditions +usr/COND
cond get <COND> Get status of user-defined condition, see $? and -v
@@ -50,6 +49,7 @@ Commands:
Note: Finit .conf file(s) are *not* reloaded!
restart <NAME>[:ID] Restart (stop/start) service by name
kill <NAME>[:ID] <S> Send signal S to service by name, with optional ID
monitor Stream D-Bus signals (service state, conditions) until ^C
ident [NAME] Show matching identities for NAME, or all
status <NAME>[:ID] Show service status, by name
status Show status of services, default command
@@ -65,8 +65,10 @@ Commands:
halt Halt system
poweroff Halt and power off system
suspend Suspend system
switch-root ROOT [INIT] Switch to new root filesystem (initramfs)
utmp show Raw dump of UTMP/WTMP db
```
For services *not* supporting `SIGHUP` the `<!>` notation in the .conf
@@ -143,3 +145,24 @@ Apr 8 12:37:46 alpine authpriv.info dropbear[2300]: Exit (root) from <192.168.1
Apr 8 15:02:11 alpine authpriv.info dropbear[2634]: Child connection from 192.168.121.1:48576
Apr 8 15:02:12 alpine authpriv.notice dropbear[2634]: Password auth succeeded for 'root' from 192.168.121.1:48576
```
Switch Root
-----------
The `switch-root` command is used when running Finit in an initramfs to
transition to the real root filesystem. This is similar to the standalone
`switch_root(8)` utility but integrated into Finit.
```
initctl switch-root /mnt/root [/sbin/init]
```
Requirements:
- Must be run during runlevel S (bootstrap) or runlevel 1
- The new root must be a mount point (different device than /)
- Can only be used when Finit is running as PID 1 in an initramfs
For complete documentation and usage examples, see the dedicated
[Switch Root](switchroot.md) section.
+534 -11
View File
@@ -1,14 +1,537 @@
keventd
=======
Bundled Device Manager
======================
The kernel event daemon bundled with Finit is a simple uevent monitor
for `/sys/class/power_supply`. It provides the `sys/pwr/ac` condition,
which can be useful to prevent power hungry services like anacron to run
when a laptop is only running on battery, for instance.
The kernel event daemon `keventd` is a built-in device manager bundled
with Finit. It replaces the need for external device managers like
mdev, mdevd, or udevd on systems where a lighter-weight solution is
preferred, particularly on embedded systems.
Since keventd is not an integral part of Finit yet it is not enabled by
default. Enable it using `./configure --with-keventd`. The bundled
`contrib/` build scripts for Debian, Alpine, and Void have this enabled.
It is enabled by default since Finit v5, and needs libblkid from
util-linux (the `libblkid-dev` package, or whatever your distribution
calls it) to read the filesystem UUID and label behind the
`/dev/disk/by-uuid/` and `/dev/disk/by-label/` symlinks. To disable it
and use an external device manager instead:
This daemon is planned to be extended with monitoring of other uevents,
patches and ideas are welcome in the issue tracker.
./configure --without-keventd
Features
--------
When started, keventd listens on a `NETLINK_KOBJECT_UEVENT` socket for
kernel events and handles:
- **Device node creation**: creates and removes `/dev` nodes with
correct permissions on device add/remove events
- **udev rules**: matches events against rules read from
`/lib/udev/rules.d/`, `/run/udev/rules.d/`, and `/etc/udev/rules.d/`,
with a curated ruleset derived from eudev installed by default
- **Persistent symlinks**: creates `by-id` and `by-path` symlinks under
`/dev/disk/`, `/dev/input/`, and elsewhere, for stable device naming
- **Firmware loading**: responds to kernel firmware requests by searching
`/lib/firmware/` and writing firmware data to sysfs
- **Module loading**: parses `MODALIAS` from uevents and spawns `modprobe`
to load the appropriate kernel module
- **Coldplug**: with the `-c` flag, walks `/sys/devices` and triggers
add events for all devices present at boot
- **Power supply monitoring**: tracks AC power status and provides the
`sys/pwr/ac` condition
- **Device conditions**: sets `dev/*` conditions in the Finit condition
system when device nodes appear or disappear
- **Netlink rebroadcast**: rebroadcasts processed uevents to netlink
group 0x4 for [libudev-zero][] consumers (enabled by default)
- **D-Bus interface**: `org.finit.Device1` -- settle, trigger, device
info, rules reload, and queue state over its own bus socket
Device Nodes
------------
On receiving an `add` event with `MAJOR`, `MINOR`, and `DEVNAME`
fields, keventd creates the corresponding device node in `/dev` using
`mknod()`. Parent directories are created automatically (e.g.,
`/dev/input/` for `/dev/input/event0`).
On `remove` events, the device node and its associated symlinks and
conditions are cleaned up.
### Default Permissions
keventd applies permissions based on built-in rules that match on
device subsystem and name:
| **Subsystem** | **Pattern** | **Mode** | **Owner:Group** |
|---------------|--------------------------------------------|----------|-----------------|
| block | sd*, vd*, nvme*, mmcblk*, loop*, dm-*, md* | 0660 | root:disk |
| tty | tty[0-9]* | 0620 | root:tty |
| tty | ttyS*, ttyUSB*, ttyACM* | 0660 | root:dialout |
| input | event*, mouse*, mice | 0660 | root:root |
| sound | * | 0660 | root:audio |
| video4linux | * | 0660 | root:video |
| drm | card*, render* | 0660 | root:video |
| (any) | null, zero, full, random, urandom | 0666 | root:root |
| (any) | console | 0600 | root:root |
| (default) | | 0660 | root:root |
udev Rules
----------
Rules are read from `/lib/udev/rules.d/`, `/run/udev/rules.d/`, and
`/etc/udev/rules.d/`, plus any directory passed with `-r`. Files need a
`.rules` suffix. Like udev, all files sort together by filename,
whichever directory they live in, and a file masks one of the same name
in an earlier directory: `/etc/` overrides `/run/`, which overrides
`/lib/`, and the `-r` directory overrides them all. A symlink to
`/dev/null` in `/etc/udev/rules.d/` disables the `/lib/` file of the
same name.
The syntax is the one described in [udev(7)][]. What follows is the
subset keventd understands, not a second copy of the grammar. To pick up
edited rules, call `RulesReload` on
[`org.finit.Device1`](#d-bus-interface-orgfinitdevice1). `initctl reload`
does not touch them.
### Match Keys
| **Key** | **Matches** |
|---------------------------------------------------|--------------------------------------------------------------------------|
| `ACTION` | `add`, `remove`, `change`, `move`, `bind`, `unbind`, `online`, `offline` |
| `DEVPATH` | kernel device path, without the `/sys` prefix |
| `KERNEL` | sysfs name of the device |
| `NAME` | device node name |
| `SUBSYSTEM`, `DRIVER` | subsystem and bound driver |
| `ATTR{file}` | sysfs attribute of the device |
| `SYSCTL{param}` | value under `/proc/sys/` |
| `ENV{key}` | uevent property, including ones set by earlier rules |
| `CONST{key}` | `arch`; `virt` reports `container` or nothing |
| `TAG` | tag set by an earlier `TAG+=` |
| `TEST{octal}` | file exists, optionally with the given permission bits |
| `PROGRAM` | exit status of a program, output kept for `RESULT` and `%c` |
| `RESULT` | output of the last `PROGRAM` |
| `KERNELS`, `SUBSYSTEMS`, `DRIVERS`, `ATTRS{file}` | the device or any of its parents, see below |
Values match literally, as an `fnmatch()` glob, or as `a|b|c` alternatives.
Both `==` and `!=` work everywhere.
`SECLABEL`, `OPTIONS`, and `TAGS` parse but do nothing. Rules using them load
without complaint and the key is skipped, so `OPTIONS+="static_node=..."`,
`link_priority=`, `string_escape=`, and `watch` have no effect, and a `TAGS==`
match never succeeds.
### The Parent Chain
`ATTR{}` reads an attribute of the device the event fired for. `ATTRS{}` also
looks at the parents, walking up the sysfs tree until one matches. For a
keyboard behind a hub:
/sys/devices/.../usb2/ <- root hub
usb2/2-1/ <- hub
usb2/2-1/2-1.4/ <- keyboard, event fires here
`ATTRS{authorized_default}=="1"` finds no such attribute on the keyboard or
the hub, reaches the root hub, and matches there. `KERNELS`, `SUBSYSTEMS`,
and `DRIVERS` do the same for a parent's name, subsystem, and driver.
Each of these keys walks the chain on its own. udev requires all the parent
keys in one rule to match on the *same* parent, so a rule pairing
`SUBSYSTEMS=="usb"` with `ATTRS{idVendor}=="1d6b"` is looser here: the two may
land on different ancestors. Where that matters, match on one attribute
specific enough to identify the device by itself.
### Assignments
| **Key** | **Effect** |
|-----------------------------|--------------------------------------------------------------------------|
| `NAME=` | name of the device node |
| `SYMLINK=`, `+=` | symlinks to create, space separated, relative to `/dev` |
| `OWNER=`, `GROUP=`, `MODE=` | ownership and permissions of the node |
| `ENV{key}=`, `+=`, `-=` | set, append to, or clear a property |
| `TAG+=`, `-=` | tags for later `TAG==` matching |
| `RUN+=` | program to run once the event is handled, `RUN{builtin}+=` for a builtin |
| `IMPORT{type}` | `program`, `file`, `db`, `builtin`, `parent`, `cmdline` |
| `ATTR{file}=` | write to a sysfs attribute |
| `SYSCTL{param}=` | write to `/proc/sys/` |
| `LABEL=`, `GOTO=` | skip ahead to a label |
`:=` locks `MODE`, `OWNER`, and `GROUP` against later rules the way udev
does. On `NAME` and `ENV{}` it behaves like plain `=` and locks nothing,
and `SYMLINK:=` is dropped without a word.
`IMPORT{builtin}` and `RUN{builtin}` can call `blkid`, `hwdb`,
`input_id`, `kmod`, `net_id`, `path_id`, and `usb_id`.
### Substitutions
| **Long** | **Short** | **Expands to** |
|--------------------|---------------|------------------------------------|
| `$kernel` | `%k` | sysfs name of the device |
| `$devpath` | `%p` | kernel device path |
| `$name` | `%N`, `%D` | device node name |
| `$major`, `$minor` | `%M`, `%m` | device numbers |
| `$driver` | `%d` | bound driver |
| `$attr{file}` | `%s{file}` | sysfs attribute of the device |
| `$env{key}` | | uevent property |
| `$result` | `%c`, `%c{N}` | `PROGRAM` output, or its Nth field |
| `$root` | | `/dev` |
| `$sys` | | `/sys` |
| | `%n` | trailing digits of the sysfs name |
| | `%b` | `major:minor` |
udev's `$id`, `$parent`, `$links`, and `%E{}` are not implemented, and `%b`
here is the device number pair rather than udev's parent bus id. An unknown
specifier is left in the string as written.
Persistent Symlinks
-------------------
Symlinks come from two places. A handful are built into keventd and are
created for every device in the subsystem, whatever rules are loaded.
For block devices, under `/dev/disk/`:
- **by-id**: based on the device serial number and model, read from
sysfs attributes (`/sys/.../device/vendor`, `model`, `serial`)
- **by-path**: based on the device topology path
For input devices, under `/dev/input/`:
- **by-id**: based on the device name from sysfs
- **by-path**: based on the physical device path
The rest come from `SYMLINK+=` in the udev rules, so what you get
depends on the ruleset installed. The curated rules Finit ships add,
among others:
| **Directory** | **Links** |
|----------------|--------------------------------------------------------|
| `/dev/disk/` | `by-uuid`, `by-label`, `by-partuuid`, `by-partlabel`, `by-diskseq`, and further `by-id` and `by-path` names for NVMe, virtio, MMC, WWN, and FireWire |
| `/dev/input/` | longer `by-id` and `by-path` names than the built-in ones, down to the USB interface number |
| `/dev/serial/` | `by-id`, `by-path` |
| `/dev/snd/` | `by-id`, `by-path` |
| `/dev/v4l/` | `by-id`, `by-path` |
| `/dev/tape/` | `by-id`, `by-path` |
| `/dev/dri/` | `by-path` |
They also set up a few fixed names: `/dev/rtc`, `/dev/cdrom`, and
`/dev/virtio-ports/<name>`.
The `by-uuid` and `by-label` links are why keventd needs libblkid. The
rules read the filesystem metadata off the device with
`IMPORT{builtin}="blkid"`, and libblkid is what that builtin calls.
Built-in and rule-provided symlinks alike are tracked internally and
removed when the corresponding device is unplugged.
Firmware Loading
----------------
When a kernel driver requests firmware (via `request_firmware()`), the
kernel sends a uevent with a `FIRMWARE=` field. keventd handles this
by:
1. Searching for the firmware file in order:
- `/lib/firmware/updates/<kernel-version>/<name>`
- `/lib/firmware/updates/<name>`
- `/lib/firmware/<kernel-version>/<name>`
- `/lib/firmware/<name>`
2. Writing `1` to `/sys/<devpath>/loading` to signal start
3. Copying the firmware data to `/sys/<devpath>/data`
4. Writing `0` to `/sys/<devpath>/loading` on success (or `-1` on failure)
This is particularly important early in boot when drivers for graphics
cards, network adapters, and other hardware need firmware before they
can operate.
Module Loading
--------------
When a device add event includes a `MODALIAS` field, keventd spawns
`modprobe -bq <modalias>` to load the matching kernel module. Module
loading is done asynchronously (keventd does not wait for modprobe to
complete) to avoid blocking other event processing.
Coldplug
--------
To handle devices that were present before keventd started, it supports
a coldplug mode activated with the `-c` flag. This walks the entire
`/sys/devices` tree and writes `add` to each `uevent` file, causing the
kernel to re-emit add events for all existing devices.
This replaces the separate `coldplug` script previously used with mdev.
When `-c` is used, keventd defers its pidfile (and the `pid/keventd`
condition Finit asserts from it) until the coldplug event queue has
been fully drained. Services that depend on `<pid/keventd>` can
therefore assume `/dev` is populated and persistent symlinks are live,
without needing a separate `settle` step.
Netlink Rebroadcast
-------------------
The Linux kernel sends uevents to netlink multicast group 1 (bit 0) of
`NETLINK_KOBJECT_UEVENT`. Only the device manager listens on this raw
kernel group. Userspace consumers — applications using libudev —
expect to receive processed events on a separate netlink group.
systemd/udevd established the convention of rebroadcasting processed
events to a separate group, and [libudev-zero][], a daemonless drop-in
replacement for libudev, listens on group `0x4` for these events.
Without a device manager rebroadcasting, graphical applications,
Wayland/X11 compositors, libinput, and anything else using libudev to
monitor device hotplug will never see any events.
keventd rebroadcasts by default to netlink group 4 (`0x4`). A second
netlink socket is created at startup, and after each uevent has been
fully processed (device nodes created, symlinks set up, modules loaded),
the original raw event is sent to the configured group(s).
Rebroadcasting after processing ensures that device nodes and symlinks
already exist by the time consumers receive the notification.
Use `-g GROUP` to override the default group mask, or `-G` to disable
rebroadcast entirely. Bit 0 of the group mask is always forced off to
prevent a feedback loop with the kernel's own multicast group.
### Background
The netlink uevent architecture uses separate multicast groups to
isolate the kernel-to-device-manager channel from the device-manager-to-
application channel:
| **Group** | **Bit** | **Purpose** |
|-----------|---------|---------------------------------------------|
| 1 | 0 | Kernel events (device manager listens here) |
| 4 | 2 | Processed events (libudev consumers listen) |
This two-group design was established by systemd/udevd and is the de
facto standard. [mdevd][] implements the same mechanism via its `-O`
flag, and keventd follows the same convention.
For more details, see:
- [libudev-zero][] — daemonless replacement for libudev
- [mdevd][] — mdev-compatible device manager with rebroadcast support
Conditions
----------
keventd provides conditions in two namespaces:
### Device Conditions (`dev/`)
When a device node is created, keventd asserts a corresponding condition
in `/run/finit/cond/dev/`. This allows services to wait for specific
devices:
service mdadm {
description = "RAID monitor"
runlevel = "2345"
conditions = { "dev/sda" }
command = "/usr/sbin/mdadm --monitor /dev/md0"
}
service gps-daemon {
description = "GPS daemon"
runlevel = "2345"
conditions = { "dev/ttyUSB0" }
command = "/usr/sbin/gps-daemon"
}
Network interfaces are not device nodes and do not live in the `dev/`
namespace -- a `/dev/wan` node created by a user must not be confused
with a `wan` interface. To wait for an interface, use the `netlink`
plugin's `net/<iface>/exist` condition, plus `net/<iface>/up` (admin
up) and `net/<iface>/running` (carrier present) to gate on link state:
service dhcpcd {
description = "DHCP client"
runlevel = "2345"
conditions = { "net/wan/exist" }
command = "/usr/sbin/dhcpcd"
}
keventd provides the parallel `class/net/<iface>` condition, like for
any other sysfs class device.
When the device is removed, the condition is cleared and Finit stops
the dependent services.
### Class Conditions (`class/`)
Many devices live in sysfs without a `/dev/` node -- DSA switch ports,
IIO sensors, LEDs, backlight, PHYs, regulators. For those, keventd
asserts `class/<subsystem>/<sysname>` on every `add` event so services
can still wait for them:
service blink-blue {
description = "LED driver"
runlevel = "2345"
conditions = { "class/leds/blue" }
command = "/usr/sbin/blink-blue"
}
The condition is cleared on `remove`.
### Driver Conditions (`driver/`)
`driver/<name>` is asserted while the driver `<name>` is bound to at
least one device, from the kernel's `bind`/`unbind` uevents. Use this
to gate on slow-probing hardware whose readiness isn't marked by a
class device or `/dev` node, such as a switch core or complex PHY:
service dsa-probe {
description = "DSA topology probe"
runlevel = "2345"
conditions = { "driver/mt7530" }
command = "/usr/sbin/dsa-probe"
}
A driver bound to several devices keeps the condition asserted until
the last device is unbound. To wait for one specific device instance,
gate on what its probe creates instead: the `class/` condition or
`/dev` node of the child device.
### Power Supply Conditions (`sys/pwr/`)
keventd monitors the `power_supply` subsystem and provides:
- `sys/pwr/ac` — asserted when AC power is connected
This is useful for preventing power-hungry services from running on
battery:
service cron {
description = "Cron daemon"
runlevel = "2345"
conditions = { "sys/pwr/ac", "pid/syslogd" }
command = "cron -f"
}
Usage
-----
keventd [-cdGhnpSv] [-g GROUP] [-r DIR] [-t SECONDS]
Options:
-c Run coldplug at startup
-d Enable debug mode (foreground, verbose)
-g GROUP Override netlink rebroadcast group (default: 4)
-G Disable netlink rebroadcast entirely
-h Show help text
-n Run in foreground (no daemon)
-p Passive mode: power supply events only
-r DIR Extra rules directory, overrides the standard udev paths
-S Settle mode: wait for kernel uevent queue to quiet, then exit
-t SEC Settle timeout (default 30s, used with -S)
-v Show version
In normal operation, Finit starts keventd automatically via its system
configuration. The `-d` flag is useful for debugging device issues --
it runs keventd in the foreground and logs all received uevents.
`keventd -S` is a one-shot command, not a flag to the running daemon.
It is the `udevadm settle` equivalent for migration scenarios:
keventd -S -t 10 && start-graphical-session
With D-Bus support it asks the running keventd over
[`Device1.Settle`](#d-bus-interface-orgfinitdevice1), which tracks the
event queue where the events actually flow. When no bus answers --
another device manager, or a build without D-Bus -- it falls back to
polling `/sys/kernel/uevent_seqnum` until the kernel's sequence
counter has been stable for 200ms, then exits zero. After the
`-t SECONDS` timeout (default 30) it exits non-zero instead. The
fallback works regardless of which device manager is active, or even
if none is.
Prefer the condition-based model (`<dev/X>`, `<class/...>`,
`<driver/...>`) over settle when you control the service definition --
settle is racy with slow probes that fire after the queue appears
quiet. It is provided for legacy boot scripts and init transitions
where condition wiring isn't feasible.
Debug logging can also be toggled at runtime by sending `SIGUSR1`:
kill -USR1 $(pidof keventd)
D-Bus Interface (`org.finit.Device1`)
-------------------------------------
With D-Bus support (default, `--disable-dbus` opts out) keventd serves
`org.finit.Device1` at `/org/finit/device` on its own brokerless bus,
`unix:path=/run/keventd/bus`, the same way Finit serves
[`org.finit`](dbus.md) on `/run/finit/bus`. There is no forwarding
between the two -- clients that want both connect to both.
| Method | In sig | Out sig | Priv. | Notes |
|---------------|--------|---------|-------|----------------------------------------------|
| `Settle` | `u` | `b` | no | Wait until the event queue drains, timeout in seconds; `false` on timeout |
| `Trigger` | `ss` | — | yes | Replay events: action, subsystem glob (empty = all) |
| `Info` | `s` | `a{ss}` | no | `/run/udev/data` properties for a devpath |
| `RulesReload` | — | `u` | yes | Re-read the rules directories, returns rule count |
| Property | Sig | Notes |
|-------------------|-----|-------------------------------------------|
| `QueueEmpty` | `b` | No device events in flight |
| `SeqnumProcessed` | `t` | Highest kernel seqnum keventd has handled |
| Signal | Body | Fires when |
|-------------------|-------------------------|---------------------------------|
| `DeviceProcessed` | `ss` — devpath, action | An event has been fully handled: node, symlinks, database |
Example, wait up to ten seconds for the queue to drain:
```sh
dbus-send --address=unix:path=/run/keventd/bus \
--type=method_call --print-reply --dest=org.finit \
/org/finit/device org.finit.Device1.Settle uint32:10
```
In passive mode (`-p`) `Trigger` and `RulesReload` refuse -- device
events are not handled here -- while `Settle` and the queue-state
properties remain meaningful.
keventd's conditions are symlinks to Finit's `reconf` generation
marker, so they read the current generation by construction and never
enter `flux` -- like user-defined conditions, they need no reassert
after `initctl reload`. Device state does not change because Finit
re-read its configuration.
Integration with Finit
----------------------
keventd is a standalone daemon started by Finit as an internal service.
It communicates with Finit exclusively through the filesystem-based
condition system — creating and removing symlinks in `/run/finit/cond/`.
This means keventd can also be tested independently:
# Run in debug mode to see all kernel events
keventd -d
# Run with coldplug to populate /dev from scratch
keventd -c -n
# Run without rebroadcast (e.g., headless embedded system)
keventd -c -G
Only one device manager should be active at a time. This is settled
at build time: with the hotplug plugin enabled, Finit starts keventd
in passive mode (`-p`), leaving device management to the udevd, mdevd,
or mdev service from `system/10-hotplug.conf` and monitoring only
power supply events. Without the hotplug plugin, keventd runs as the
system device manager (`keventd -c`).
[libudev-zero]: https://github.com/illiliti/libudev-zero
[mdevd]: https://skarnet.org/software/mdevd/
[udev(7)]: https://man7.org/linux/man-pages/man7/udev.7.html
+21 -3
View File
@@ -35,7 +35,15 @@ For your convenience a set of *optional* plugins are available:
> the `/etc/fstab` file and in the Linux kernel config.
* *dbus.so*: Setup and start system message bus, D-Bus, at boot.
_Optional plugin._
Enabled by default, and a no-op on systems without a `dbus-daemon`.
The daemon is declared in `20-dbus.conf` and its directories in
`tmpfiles.d/dbus.conf`, both overridable from `/etc`; the plugin
itself only handles what has to inspect the running system.
Not to be confused with Finit's built-in [D-Bus API](dbus.md): the
plugin (`--disable-dbus-plugin`) starts the external system bus
daemon, while the built-in support (`--disable-dbus`) is the
`org.finit` interface served by Finit itself.
* *hook-scripts.so*: Trigger the execution of scripts from plugin hook
points (see [Hooks](#hooks)). _Optional plugin._
@@ -70,8 +78,8 @@ For your convenience a set of *optional* plugins are available:
name of the module to load. Any line starting with the standard UNIX
comment character, `#`, or `;`, is skipped.
Modules are by default loaded in runlevel `S` using the `task` stanza.
Each module gets a unique `name:modprobe.foo`, and optional`:ID`. The
Modules are by default loaded in runlevel `S` using a `task` block.
Each module is named `modprobe.foo`, with an optional `:ID`. The
runlevel can be changed per file using:
set runlevel 2345
@@ -205,6 +213,16 @@ hook points:
new runlevel have been been stopped. When the hook has completed,
Finit continues to start all services in the new runlevel.
### Switch Root Hooks
* `HOOK_SWITCH_ROOT`, `hook/sys/switchroot`: Called when
`initctl switch-root` is issued, before the transition begins. Use
this hook to save state, unmount initramfs-only filesystems, or perform
cleanup before switching to the new root. Only runs when Finit is
operating as PID 1 in an initramfs.
See the [Switch Root](switchroot.md) section for more information.
### Shutdown Hooks
* `HOOK_NETWORK_DN`, `hook/net/down`: Called right after having changed
+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`:
```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`
+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.
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
run [S] /etc/init.d/acpid start -- Starting ACPI Daemon
task [S] /etc/init.d/kbd start -- Preparing console
service [S12345] <pid/syslogd> klogd -n -x -- Kernel log daemon
service syslogd {
description = "System log daemon"
runlevel = "S12345"
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 [2] /dev/tty2
tty [2] /dev/tty3
tty [2] /dev/tty4
tty [2] /dev/tty5
tty [2] /dev/tty6
tty tty1 { runlevel = "12345" device = "/dev/tty1" }
tty tty2 { runlevel = "2" device = "/dev/tty2" }
tty tty3 { runlevel = "2" device = "/dev/tty3" }
tty tty4 { runlevel = "2" device = "/dev/tty4" }
tty tty5 { runlevel = "2" device = "/dev/tty5" }
tty tty6 { runlevel = "2" device = "/dev/tty6" }
```
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.
```
task [S] echo "foo" | cat >/tmp/bar
run [S] echo "$HOME" >/tmp/secret
task foo {
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
+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
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
enabled just prior to changing from single user mode.)
```shell
runparts /etc/rc.d/
```aconf
runparts = "/etc/rc.d/"
```
Right after the runlevel change when all services have started properly,
`/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
end of the boot, before calling the `HOOK_SYSTEM_UP`. See more in the
[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
There are two syntax variants, type 1 and type 2. The former is the
traditional one used also for `sysv` start/stop scripts, and the latter
is inspired by systemd, with a twist -- it lets Finit guess the pifdile
to look for based on the standard path and the basename of the command.
There are two variants. The former names the pidfile to watch, as for
`sysv` start/stop scripts, and the latter is inspired by systemd, with a
twist -- it lets Finit guess the pidfile based on the standard path and
the basename of the command.
service pid:!/run/serv.pid serv -- Forking service, type 1
service type:forking serv -- Forking service, type 2
service serv {
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
`/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
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
Same as previous, but we tell Finit to create the PID file, because we
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
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
+193
View File
@@ -0,0 +1,193 @@
Switch Root
===========
Finit supports switching from an initramfs to a real root filesystem
using the `initctl switch-root` command. This is useful for systems
that use an initramfs for early boot (LUKS, LVM, network boot, etc.)
and need to transition to the real root before starting services.
Usage
-----
```sh
initctl switch-root NEWROOT [INIT]
```
- `NEWROOT`: Path to the mounted new root filesystem (e.g., `/mnt/root`)
- `INIT`: Optional path to init on the new root (default: `/sbin/init`)
Requirements
------------
1. Must be run during runlevel S (bootstrap) or runlevel 1
2. `NEWROOT` must be a mount point (different device than /)
3. `INIT` must exist and be executable on the new root
4. Finit must be running as PID 1 (in initramfs)
How It Works
------------
1. Runs `HOOK_SWITCH_ROOT` for any cleanup scripts/plugins
2. Runs `HOOK_SHUTDOWN` to notify plugins
3. Stops all services and kills remaining processes
4. Exits all plugins gracefully
5. Moves `/dev`, `/proc`, `/sys`, `/run` to new root
6. Deletes initramfs contents (if on tmpfs/ramfs) to free memory
7. Moves new root mount to `/`
8. Chroots to new root
9. Reopens `/dev/console` for stdin/stdout/stderr
10. Execs new init as PID 1
Example: Initramfs finit.conf
-----------------------------
Configuration file `/etc/finit.conf` in the initramfs:
```
# /etc/finit.conf in initramfs
# Mount the real 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
run switch-root {
description = "Switching to real root"
runlevel = "S"
command = "/sbin/initctl switch-root /mnt/root"
}
```
For more complex setups (LUKS, LVM, etc.):
```
# Unlock LUKS volume
# The tty setting is required so cryptsetup can prompt for a passphrase
run cryptsetup {
description = "Unlocking encrypted root"
runlevel = "S"
tty = "@console"
command = "/sbin/cryptsetup open /dev/sda2 cryptroot"
}
# Activate LVM
run lvm {
description = "Activating LVM volumes"
runlevel = "S"
command = "/sbin/lvm vgchange -ay"
}
# Mount root
run mount-root {
description = "Mounting root"
runlevel = "S"
command = "/bin/mount /dev/vg0/root /mnt/root"
}
# Switch root
run switch-root {
description = "Switching to real root"
runlevel = "S"
command = "/sbin/initctl switch-root /mnt/root"
}
```
Example: Using Runlevel 1 for Switch Root
-----------------------------------------
For more complex initramfs setups where ordering of tasks becomes
difficult in runlevel S, you can perform the switch-root in runlevel 1:
```
# /etc/finit.conf in initramfs
# Start mdevd for device handling
service mdevd {
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)
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
# The switch-root runs cleanly in runlevel 1
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
switch-root operation (runlevel 1), making task ordering simpler.
Hooks
-----
The `HOOK_SWITCH_ROOT` hook runs before the switch begins. Use it for:
- Saving state to the new root
- Unmounting initramfs-only mounts
- Cleanup tasks
Plugins can register for `HOOK_SWITCH_ROOT` just like other hooks:
```c
static void my_switch_root_hook(void *arg)
{
/* Cleanup before switch_root */
}
static plugin_t plugin = {
.name = "my-plugin",
.hook[HOOK_SWITCH_ROOT] = {
.cb = my_switch_root_hook
}
};
PLUGIN_INIT(plugin_init)
{
plugin_register(&plugin);
}
```
Conditions
----------
After switch_root, the new finit instance starts fresh. No conditions
or state are preserved across the switch. The new finit will:
1. Re-read `/etc/finit.conf` from the new root
2. Re-initialize all conditions
3. Start services according to the new configuration
See Also
--------
- [switch_root(8)](https://man7.org/linux/man-pages/man8/switch_root.8.html) - util-linux switch_root utility
- [Kernel initramfs documentation](https://docs.kernel.org/filesystems/ramfs-rootfs-initramfs.html)
+18 -11
View File
@@ -1,23 +1,30 @@
Bundled Watchdog Daemon
=======================
When built `--with-watchdog` a separate service is built and installed
in `/libexec/finit/watchdogd`. If this exists at runtime, and the WDT
device node exists, Finit will start it and treat it as the elected
watchdog service to delegate its reboot to. This delegation is to
A separate service is built and installed in `/libexec/finit/watchdogd`,
unless disabled with `--without-watchdog`. If this exists at runtime,
and the WDT device node exists, Finit will start it and treat it as the
elected watchdog service to delegate its reboot to. This delegation is to
ensure that the system is rebooted by a hardware watchdog timer -- on
many embedded systems this is crucial to ensure all circuits on the
board are properly reset for the next boot, in effect ensuring the
system works the same after both a power-on and reboot event.
> [!NOTE]
> The watchdog reboot delegation can be enabled with the `reboot-watchdog`
> configuration option in `/etc/finit.conf`. By default this is disabled
> and the system reboots directly via the SoC using the kernel's `reboot(2)`
> syscall. See [Runlevels](config/runlevels.md) for details.
The delegation is performed at the very last steps of system shutdown,
if reboot has been selected and an elected watchdog is known, first a
`SIGPWR` is sent to advise watchdogd of the pending reboot. Then, when
the necessary steps of preparing the system for shutdown (umount etc.)
are completed, Finit sends `SIGTERM` to watchdogd and puts itself in a
10 sec timeout loop waiting for the WDT to reset the board. If a reset
is not done before the timeout, Finit falls back to`reboot(RB_AUTOBOOT)`
which tells the kernel to do the reboot.
if reboot has been selected, `reboot-watchdog` is enabled, and an elected
watchdog is known. First a `SIGPWR` is sent to advise watchdogd of the
pending reboot. Then, when the necessary steps of preparing the system
for shutdown (umount etc.) are completed, Finit sends `SIGTERM` to
watchdogd and puts itself in a 10 sec timeout loop waiting for the WDT
to reset the board. If a reset is not done before the timeout, Finit
falls back to `reboot(RB_AUTOBOOT)` which tells the kernel to do the
reboot.
An external watchdog service can also be used. The more advanced cousin
[watchdogd](https://github.com/troglobit/watchdogd/) is the recommended
+4
View File
@@ -0,0 +1,4 @@
.deps/
keventd
Makefile
Makefile.in
+67
View File
@@ -0,0 +1,67 @@
AM_CPPFLAGS = -D_XOPEN_SOURCE=600 -D_BSD_SOURCE -D_GNU_SOURCE -D_DEFAULT_SOURCE
AM_CPPFLAGS += -I$(top_srcdir)/src
pkglibexec_PROGRAMS = keventd
keventd_SOURCES = keventd.c keventd.h uevent.c \
udevdb.c udevdb.h \
sysfs.c sysfs.h \
rules.c rules.h \
builtin.c builtin.h \
$(top_srcdir)/src/util.c $(top_srcdir)/src/util.h
# Per-target flags exist even when their conditional is false, so
# assign the base unconditionally and append under DBUS
keventd_CPPFLAGS = $(AM_CPPFLAGS)
keventd_CFLAGS = -W -Wall -Wextra -std=gnu99
# Silence noisy false positives from PATH_MAX-into-PATH_MAX sysfs path
# concatenations; real concerns (e.g. USB strings into small buffers) are
# handled via explicit checks where present. Matches systemd-udev policy.
keventd_CFLAGS += -Wno-format-truncation
keventd_CFLAGS += $(lite_CFLAGS)
keventd_CFLAGS += $(uev_CFLAGS)
keventd_CFLAGS += $(blkid_CFLAGS)
keventd_LDADD = $(lite_LIBS)
keventd_LDADD += $(uev_LIBS)
keventd_LDADD += $(blkid_LIBS)
if DBUS
keventd_SOURCES += dbus.c
keventd_CPPFLAGS += -I$(top_srcdir)/libink
keventd_LDADD += $(top_builddir)/libink/libink.la
endif
# Curated udev rules derived from eudev 3.2.14 (GPL-2.0-or-later).
# Installed to /lib/udev/rules.d/ so existing third-party rules and
# local overrides under /etc/udev/rules.d/ coexist with standard udev
# precedence. See .notes/curated-rules-manifest.md.
if UDEV_RULES
udevrulesdir = $(prefix)/lib/udev/rules.d
dist_udevrules_DATA = \
rules.d/50-udev-default.rules \
rules.d/60-autosuspend.rules \
rules.d/60-block.rules \
rules.d/60-cdrom_id.rules \
rules.d/60-drm.rules \
rules.d/60-evdev.rules \
rules.d/60-fido-id.rules \
rules.d/60-input-id.rules \
rules.d/60-persistent-alsa.rules \
rules.d/60-persistent-input.rules \
rules.d/60-persistent-storage.rules \
rules.d/60-persistent-storage-tape.rules \
rules.d/60-persistent-v4l.rules \
rules.d/60-sensor.rules \
rules.d/60-serial.rules \
rules.d/64-btrfs.rules \
rules.d/70-camera.rules \
rules.d/70-joystick.rules \
rules.d/70-memory.rules \
rules.d/70-mouse.rules \
rules.d/70-touchpad.rules \
rules.d/75-net-description.rules \
rules.d/75-probe_mtd.rules \
rules.d/78-sound-card.rules \
rules.d/80-drivers.rules \
rules.d/80-net-name-slot.rules \
rules.d/81-net-dhcp.rules
endif

Some files were not shown because too many files have changed in this diff Show More