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>
* 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>
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>
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>
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>
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>
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>
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>
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>
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.
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>
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>
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>
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>
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>
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#416Closes#476
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
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>
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>
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>
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>
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>
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.
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>
In containerized or virtualized environments, standard mount point
directories may not exist at boot. Ensure they are created before
attempting to mount.
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.
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.
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.
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.
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.
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.
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>
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>
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.
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.
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.
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.
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>
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>