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
+55
View File
@@ -288,6 +288,7 @@ static cfg_opt_t svc_opts[] = {
CFG_INT ("config-dir-mode", 0, CFGF_NODEFAULT),
CFG_STR ("runtime-dir-preserve", 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 ("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;
}
/*
* 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()
* 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;
dirs_translate(sec, svc, file);
provides_translate(sec, svc, file);
}
/*
@@ -2199,6 +2253,7 @@ int conf_reload(void)
cgroup_mark_all();
svc_mark_dynamic();
conf_reset_env();
svc_provides_reset();
/*
* Reset global rlimit to bootstrap values from conf_init().
+13 -11
View File
@@ -337,6 +337,7 @@ static int dump_one_cond(const char *fpath, const struct stat *sb, int tflag, st
const char *cond, *asserted;
char *nm = "init";
pid_t pid = 1;
svc_t *svc;
if (tflag != FTW_F)
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)))
return 0;
if (strncmp("pid/", cond, 4) == 0) {
svc_t *svc;
svc = client_svc_find_by_cond(cond);
if (!svc) {
nm = "unknown";
pid = 0;
} else {
nm = svc_ident(svc, NULL, 0);
pid = svc->pid;
}
/*
* Any namespace can be claimed with provides, so ask who owns
* the condition before falling back to what the namespace
* implies on its own.
*/
svc = client_svc_find_by_cond(cond);
if (svc) {
nm = svc_ident(svc, NULL, 0);
pid = svc->pid;
} else if (strncmp("pid/", cond, 4) == 0) {
nm = "unknown";
pid = 0;
} else if (strncmp("usr/", cond, 4) == 0) {
nm = "static";
pid = 0;
+1
View File
@@ -2391,6 +2391,7 @@ svc_t *service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
svc->dir_mode[i] = 0755;
}
svc->dir_preserve = SVC_DIR_PRESERVE_NO;
svc->num_provides = 0;
if (!svc_is_tty(svc) && ctty) {
char *dev = ctty;