mirror of
https://github.com/troglobit/finit.git
synced 2026-10-31 13:00:32 +00:00
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:
1 parent
22fbb408eb
commit
ffb1db7d2d
5 files changed
+216
-11
No files matched your search
+55
@@ -288,6 +288,7 @@ static cfg_opt_t svc_opts[] = {
|
|||||||
CFG_INT ("config-dir-mode", 0, CFGF_NODEFAULT),
|
CFG_INT ("config-dir-mode", 0, CFGF_NODEFAULT),
|
||||||
CFG_STR ("runtime-dir-preserve", NULL, CFGF_NODEFAULT),
|
CFG_STR ("runtime-dir-preserve", NULL, CFGF_NODEFAULT),
|
||||||
CFG_STR_LIST("conflicts", NULL, CFGF_NODEFAULT),
|
CFG_STR_LIST("conflicts", NULL, CFGF_NODEFAULT),
|
||||||
|
CFG_STR_LIST("provides", NULL, CFGF_NODEFAULT),
|
||||||
CFG_STR ("if", NULL, CFGF_NODEFAULT),
|
CFG_STR ("if", NULL, CFGF_NODEFAULT),
|
||||||
CFG_STR ("tty", NULL, CFGF_NODEFAULT),
|
CFG_STR ("tty", NULL, CFGF_NODEFAULT),
|
||||||
/*
|
/*
|
||||||
@@ -1382,6 +1383,58 @@ static int if_translate(const char *str, char *buf, size_t len, char *file, cons
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* provides names conditions this service asserts on top of its own
|
||||||
|
* pid/<ident>, so blocks with distinct titles can supply one barrier
|
||||||
|
* to everything downstream. Like the per-service directories it has
|
||||||
|
* no legacy token and is stored on the registered svc.
|
||||||
|
*
|
||||||
|
* A claim on a condition that is already owned is dropped, with the
|
||||||
|
* service itself left alone: the overlap is a configuration bug, and
|
||||||
|
* an init system is more useful degraded than refusing to boot.
|
||||||
|
*/
|
||||||
|
static void provides_translate(cfg_t *sec, svc_t *svc, char *file)
|
||||||
|
{
|
||||||
|
unsigned int i, num;
|
||||||
|
|
||||||
|
num = cfg_size(sec, "provides");
|
||||||
|
for (i = 0; i < num; i++) {
|
||||||
|
const char *cond = cfg_getnstr(sec, "provides", i);
|
||||||
|
svc_t *owner;
|
||||||
|
|
||||||
|
if (!strchr(cond, '/')) {
|
||||||
|
logit(LOG_ERR, "%s: %s: provides '%s' is not a condition,"
|
||||||
|
" it needs a namespace, e.g. usr/%s, ignoring",
|
||||||
|
file, cfg_title(sec), cond, cond);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(cond) >= MAX_COND_LEN) {
|
||||||
|
logit(LOG_ERR, "%s: %s: provides '%s' is too long,"
|
||||||
|
" ignoring", file, cfg_title(sec), cond);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (svc->num_provides >= MAX_NUM_PROVIDES) {
|
||||||
|
logit(LOG_WARNING, "%s: %s: too many provides, max %d,"
|
||||||
|
" ignoring '%s'", file, cfg_title(sec),
|
||||||
|
MAX_NUM_PROVIDES, cond);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
owner = svc_cond_owner(cond, svc);
|
||||||
|
if (owner) {
|
||||||
|
logit(LOG_WARNING, "%s: %s: provides condition <%s>"
|
||||||
|
" already registered by service %s, ignoring",
|
||||||
|
file, cfg_title(sec), cond,
|
||||||
|
svc_ident(owner, NULL, 0));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
strlcpy(svc->provides[svc->num_provides++], cond, MAX_COND_LEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* These have no legacy token, they are validated by service_set_dir()
|
* These have no legacy token, they are validated by service_set_dir()
|
||||||
* and stored directly on the registered svc. Empty means unset,
|
* and stored directly on the registered svc. Empty means unset,
|
||||||
@@ -1644,6 +1697,7 @@ static void svc_translate(cfg_t *sec, int type, struct rlimit rlimit[], char *fi
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
dirs_translate(sec, svc, file);
|
dirs_translate(sec, svc, file);
|
||||||
|
provides_translate(sec, svc, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -2199,6 +2253,7 @@ int conf_reload(void)
|
|||||||
cgroup_mark_all();
|
cgroup_mark_all();
|
||||||
svc_mark_dynamic();
|
svc_mark_dynamic();
|
||||||
conf_reset_env();
|
conf_reset_env();
|
||||||
|
svc_provides_reset();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reset global rlimit to bootstrap values from conf_init().
|
* Reset global rlimit to bootstrap values from conf_init().
|
||||||
|
|||||||
+13
-11
@@ -337,6 +337,7 @@ static int dump_one_cond(const char *fpath, const struct stat *sb, int tflag, st
|
|||||||
const char *cond, *asserted;
|
const char *cond, *asserted;
|
||||||
char *nm = "init";
|
char *nm = "init";
|
||||||
pid_t pid = 1;
|
pid_t pid = 1;
|
||||||
|
svc_t *svc;
|
||||||
|
|
||||||
if (tflag != FTW_F)
|
if (tflag != FTW_F)
|
||||||
return 0;
|
return 0;
|
||||||
@@ -350,17 +351,18 @@ static int dump_one_cond(const char *fpath, const struct stat *sb, int tflag, st
|
|||||||
if (dump_filter && dump_filter[0] && strncmp(cond, dump_filter, strlen(dump_filter)))
|
if (dump_filter && dump_filter[0] && strncmp(cond, dump_filter, strlen(dump_filter)))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (strncmp("pid/", cond, 4) == 0) {
|
/*
|
||||||
svc_t *svc;
|
* Any namespace can be claimed with provides, so ask who owns
|
||||||
|
* the condition before falling back to what the namespace
|
||||||
svc = client_svc_find_by_cond(cond);
|
* implies on its own.
|
||||||
if (!svc) {
|
*/
|
||||||
nm = "unknown";
|
svc = client_svc_find_by_cond(cond);
|
||||||
pid = 0;
|
if (svc) {
|
||||||
} else {
|
nm = svc_ident(svc, NULL, 0);
|
||||||
nm = svc_ident(svc, NULL, 0);
|
pid = svc->pid;
|
||||||
pid = svc->pid;
|
} else if (strncmp("pid/", cond, 4) == 0) {
|
||||||
}
|
nm = "unknown";
|
||||||
|
pid = 0;
|
||||||
} else if (strncmp("usr/", cond, 4) == 0) {
|
} else if (strncmp("usr/", cond, 4) == 0) {
|
||||||
nm = "static";
|
nm = "static";
|
||||||
pid = 0;
|
pid = 0;
|
||||||
|
|||||||
@@ -2391,6 +2391,7 @@ svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
|
|||||||
svc->dir_mode[i] = 0755;
|
svc->dir_mode[i] = 0755;
|
||||||
}
|
}
|
||||||
svc->dir_preserve = SVC_DIR_PRESERVE_NO;
|
svc->dir_preserve = SVC_DIR_PRESERVE_NO;
|
||||||
|
svc->num_provides = 0;
|
||||||
|
|
||||||
if (!svc_is_tty(svc) && ctty) {
|
if (!svc_is_tty(svc) && ctty) {
|
||||||
char *dev = ctty;
|
char *dev = ctty;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ EXTRA_DIST += bootstrap-crash.sh
|
|||||||
EXTRA_DIST += cond-start-task.sh
|
EXTRA_DIST += cond-start-task.sh
|
||||||
EXTRA_DIST += conf-format.sh
|
EXTRA_DIST += conf-format.sh
|
||||||
EXTRA_DIST += conf-command.sh
|
EXTRA_DIST += conf-command.sh
|
||||||
|
EXTRA_DIST += conf-provides.sh
|
||||||
EXTRA_DIST += conf-dup-title.sh
|
EXTRA_DIST += conf-dup-title.sh
|
||||||
EXTRA_DIST += conf-dirs.sh
|
EXTRA_DIST += conf-dirs.sh
|
||||||
EXTRA_DIST += conf-if.sh
|
EXTRA_DIST += conf-if.sh
|
||||||
@@ -84,6 +85,7 @@ TESTS += bootstrap-crash.sh
|
|||||||
TESTS += cond-start-task.sh
|
TESTS += cond-start-task.sh
|
||||||
TESTS += conf-format.sh
|
TESTS += conf-format.sh
|
||||||
TESTS += conf-command.sh
|
TESTS += conf-command.sh
|
||||||
|
TESTS += conf-provides.sh
|
||||||
TESTS += conf-dup-title.sh
|
TESTS += conf-dup-title.sh
|
||||||
TESTS += conf-dirs.sh
|
TESTS += conf-dirs.sh
|
||||||
TESTS += conf-if.sh
|
TESTS += conf-if.sh
|
||||||
|
|||||||
Executable
+145
@@ -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'
|
||||||
Reference in new issue
Block a user