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>
This commit is contained in:
Joachim Wiberg
2026-08-05 17:58:43 +02:00
parent 22fbb408eb
commit ffb1db7d2d
5 changed files with 216 additions and 11 deletions
+2
View File
@@ -33,6 +33,7 @@ EXTRA_DIST += bootstrap-crash.sh
EXTRA_DIST += cond-start-task.sh
EXTRA_DIST += conf-format.sh
EXTRA_DIST += conf-command.sh
EXTRA_DIST += conf-provides.sh
EXTRA_DIST += conf-dup-title.sh
EXTRA_DIST += conf-dirs.sh
EXTRA_DIST += conf-if.sh
@@ -84,6 +85,7 @@ TESTS += bootstrap-crash.sh
TESTS += cond-start-task.sh
TESTS += conf-format.sh
TESTS += conf-command.sh
TESTS += conf-provides.sh
TESTS += conf-dup-title.sh
TESTS += conf-dirs.sh
TESTS += conf-if.sh
+145
View File
@@ -0,0 +1,145 @@
#!/bin/sh
# Verify 'provides': a block asserts conditions beyond its own
# pid/<ident>, so variants with distinct titles can supply one
# barrier. A claim on a condition somebody already owns is refused,
# with the service itself still registered.
set -eu
TEST_DIR=$(dirname "$0")
# shellcheck disable=SC2034
# Only the udev variant qualifies, mdevd is not a known service here,
# so syslogd:udev is the one that gets to supply pid/syslogd.
BOOTSTRAP="service anchor {
runlevel = \"S12345\"
command = \"serv -np -i anchor\"
}
service syslogd:udev {
runlevel = \"S12345\"
if = \"anchor\"
provides = \"pid/syslogd\"
command = \"serv -np -i sysudev\"
}
service syslogd:mdev {
runlevel = \"S12345\"
if = \"mdevd\"
provides = \"pid/syslogd\"
command = \"serv -np -i sysmdev\"
}
service downstream {
runlevel = \"S12345\"
conditions = { \"pid/syslogd\" }
command = \"serv -np -i downstream\"
}"
# initctl status prints a detail block for a single match and a table
# only for several, so count lines in the full listing instead.
assert_loaded()
{
assert "Service $1 loaded: $2" \
"$(texec initctl -t status | awk -v n="$1" '$2 == n' | wc -l)" -eq "$2"
}
# The IDENT column of 'initctl cond dump' names the owner of a
# condition, which for a provided one is the service that claimed it.
assert_provider()
{
assert "Condition $1 provided by $2" \
"$(texec initctl cond dump | awk -v c="<$1>" '$4 == c {print $2}')" = "$2"
}
test_teardown()
{
say "Running test teardown."
run "rm -f $FINIT_CONF"
}
# shellcheck source=/dev/null
. "$TEST_DIR/lib/setup.sh"
say 'The qualifying variant is loaded, the other is pruned by if'
retry 'assert_status syslogd:udev running'
assert_loaded syslogd:mdev 0
say 'It asserts the condition it provides, on top of its own'
retry 'assert_cond pid/syslogd'
assert_cond pid/syslogd:udev
say 'A service waiting on the provided condition starts'
retry 'assert_status downstream running'
say 'initctl cond dump names the provider, not "unknown"'
assert_provider pid/syslogd syslogd:udev
say 'Stopping the provider clears what it provided'
run "initctl stop syslogd:udev"
retry 'assert_nocond pid/syslogd'
run "initctl start syslogd:udev"
retry 'assert_cond pid/syslogd'
# With a BOOTSTRAP the test is released in runlevel S, where a reload
# is ignored, so wait for the runlevel change before rewriting.
say 'Waiting for bootstrap to finish before rewriting the configuration'
retry "test \"\$(texec sh -c \"initctl runlevel | awk '{print \\\$2;}'\")\" = 2" 20 1
# The claim is refused, but the service is still registered and runs.
# Both blocks qualify here, which is the configuration bug the warning
# exists for.
say 'A second claim on the same condition is refused, the service still runs'
run "echo 'service first {' > $FINIT_CONF"
run "echo ' provides = \"usr/barrier\"' >> $FINIT_CONF"
run "echo ' command = \"serv -np -i first\"' >> $FINIT_CONF"
run "echo '}' >> $FINIT_CONF"
run "echo 'service second {' >> $FINIT_CONF"
run "echo ' provides = \"usr/barrier\"' >> $FINIT_CONF"
run "echo ' command = \"serv -np -i second\"' >> $FINIT_CONF"
run "echo '}' >> $FINIT_CONF"
run "initctl reload"
retry 'assert_status first running'
assert_status second running
assert_provider usr/barrier first
# A real service by that identity outranks a claim on it.
say 'An identity beats a claim on the same condition'
run "echo 'service realsvc {' > $FINIT_CONF"
run "echo ' command = \"serv -np -i realsvc\"' >> $FINIT_CONF"
run "echo '}' >> $FINIT_CONF"
run "echo 'service pretender {' >> $FINIT_CONF"
run "echo ' provides = \"pid/realsvc\"' >> $FINIT_CONF"
run "echo ' command = \"serv -np -i pretender\"' >> $FINIT_CONF"
run "echo '}' >> $FINIT_CONF"
run "initctl reload"
retry 'assert_status pretender running'
assert_provider pid/realsvc realsvc
say 'A value without a namespace is rejected, the service still runs'
run "echo 'service nonamespace {' > $FINIT_CONF"
run "echo ' provides = \"syslogd\"' >> $FINIT_CONF"
run "echo ' command = \"serv -np -i nonamespace\"' >> $FINIT_CONF"
run "echo '}' >> $FINIT_CONF"
run "initctl reload"
retry 'assert_status nonamespace running'
# Claims are dropped before the .conf files are re-read, so a service
# re-registering cannot lose to a claim another one has not yet
# dropped. Without that, ownership flips on every reload.
say 'Ownership survives a reload, it does not flip between variants'
run "echo 'service keeper {' > $FINIT_CONF"
run "echo ' provides = \"usr/kept\"' >> $FINIT_CONF"
run "echo ' command = \"serv -np -i keeper\"' >> $FINIT_CONF"
run "echo '}' >> $FINIT_CONF"
run "echo 'service loser {' >> $FINIT_CONF"
run "echo ' provides = \"usr/kept\"' >> $FINIT_CONF"
run "echo ' command = \"serv -np -i loser\"' >> $FINIT_CONF"
run "echo '}' >> $FINIT_CONF"
run "initctl reload"
retry 'assert_provider usr/kept keeper'
run "initctl reload"
retry 'assert_provider usr/kept keeper'
run "initctl reload"
retry 'assert_provider usr/kept keeper'