service: don't take a command named like an option for one

A run/task/service whose command starts like an option without a ':'
lost its command, e.g. the example in finit.conf(5):

    run [2345] @joe:users logger "Hello world"

was registered as the log option, running "Hello world".  Same for
pidof, pidproxy, cgroupfs-mount, and in block format too.

The option parser matches with MATCH_CMD(), a prefix match, also for
the options with no trailing ':' to end the prefix: log, pid, cgroup,
respawn, nowarn, and the rest.

Match these against the whole token, allowing only the separator that
starts a value: "log:", "pid:", "cgroup." and "cgroup:".

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg committed 2026-11-13 16:00:25 +01:00
1 parent 34a0a4b1ff
commit f15e95d1cc
3 files changed
+44 -9

No files matched your search

+3
View File
@@ -156,6 +156,9 @@ All relevant changes are documented in this file.
- Fix #512: `--` in the `command` of a block-format .conf, it was taken as the - Fix #512: `--` in the `command` of a block-format .conf, it was taken as the
start of the description, so `command = "cmd -- arg"` ran `cmd` with start of the description, so `command = "cmd -- arg"` ran `cmd` with
description `arg`. Found by Alexandru Paniș description `arg`. Found by Alexandru Paniș
- Fix a command whose name starts like a service option, e.g. `logger` or
`pidof`. It was taken as the `log` or `pid` option and the next argument
ran as the command, which also broke the `logger` example in finit.conf(5)
- Reject a .conf file that declares the same block title twice. The title is - 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 the service identity and libConfuse merges two sections sharing one, so the
file loaded as a single service holding a mix of both declarations, file loaded as a single service holding a mix of both declarations,
+31 -9
View File
@@ -1812,6 +1812,24 @@ static char *parse_args(char **line)
return NULL; return NULL;
} }
/*
* Match an option that takes a value, @opt on its own or followed by
* one of the characters in @sep, e.g. "log" and "log:/path". Returns
* the value, or NULL, so a command like logger or pidof is not taken
* for an option.
*/
static char *is_opt(char *tok, const char *opt, const char *sep)
{
size_t len = strlen(opt);
if (strncasecmp(tok, opt, len))
return NULL;
if (tok[len] && !strchr(sep, tok[len]))
return NULL;
return &tok[len];
}
/* /*
* log:/path/to/logfile,priority:facility.level,tag:ident * log:/path/to/logfile,priority:facility.level,tag:ident
*/ */
@@ -2265,6 +2283,10 @@ svc_t *service_register_desc(int type, char *cfg, const char *desc,
return NULL; return NULL;
} }
/*
* Options ending in ':' can be prefix matched, the rest must match
* the whole token or a command like logger is taken for one.
*/
while (cmd) { while (cmd) {
char *arg; char *arg;
@@ -2279,19 +2301,19 @@ svc_t *service_register_desc(int type, char *cfg, const char *desc,
cond = &cmd[1]; cond = &cmd[1];
else if (cmd[0] == ':') /* :ID */ else if (cmd[0] == ':') /* :ID */
id = &cmd[1]; id = &cmd[1];
else if (MATCH_CMD(cmd, "log", arg)) else if (is_opt(cmd, "log", ":"))
log = cmd; log = cmd;
else if (MATCH_CMD(cmd, "pid", arg)) else if (is_opt(cmd, "pid", ":"))
pid = cmd; pid = cmd;
else if (MATCH_CMD(cmd, "name:", arg)) else if (MATCH_CMD(cmd, "name:", arg))
name = cmd; name = cmd;
else if (MATCH_CMD(cmd, "notify:", arg)) else if (MATCH_CMD(cmd, "notify:", arg))
notify = arg; notify = arg;
else if (MATCH_CMD(cmd, "type:forking", arg)) else if (!strcasecmp(cmd, "type:forking"))
forking = 1; forking = 1;
else if (MATCH_CMD(cmd, "manual:yes", arg)) else if (!strcasecmp(cmd, "manual:yes"))
manual = 1; manual = 1;
else if (MATCH_CMD(cmd, "remain:yes", arg)) else if (!strcasecmp(cmd, "remain:yes"))
remain = 1; remain = 1;
else if (MATCH_CMD(cmd, "restart:", arg)) { else if (MATCH_CMD(cmd, "restart:", arg)) {
if (MATCH_CMD(arg, "always", arg)) if (MATCH_CMD(arg, "always", arg))
@@ -2303,9 +2325,9 @@ svc_t *service_register_desc(int type, char *cfg, const char *desc,
restart_tmo = atoi(arg) * 1000; restart_tmo = atoi(arg) * 1000;
else if (MATCH_CMD(cmd, "restart_sec:", arg)) else if (MATCH_CMD(cmd, "restart_sec:", arg))
restart_tmo = atoi(arg) * 1000; restart_tmo = atoi(arg) * 1000;
else if (MATCH_CMD(cmd, "norestart", arg)) else if (!strcasecmp(cmd, "norestart"))
restart_max = 0; restart_max = 0;
else if (MATCH_CMD(cmd, "nowarn", arg)) else if (!strcasecmp(cmd, "nowarn"))
nowarn = 1; nowarn = 1;
else if (MATCH_CMD(cmd, "oncrash:", arg)) { else if (MATCH_CMD(cmd, "oncrash:", arg)) {
if (MATCH_CMD(arg, "reboot", arg)) if (MATCH_CMD(arg, "reboot", arg))
@@ -2313,7 +2335,7 @@ svc_t *service_register_desc(int type, char *cfg, const char *desc,
if (MATCH_CMD(arg, "script", arg)) if (MATCH_CMD(arg, "script", arg))
oncrash_action = SVC_ONCRASH_SCRIPT; oncrash_action = SVC_ONCRASH_SCRIPT;
} }
else if (MATCH_CMD(cmd, "respawn", arg)) else if (!strcasecmp(cmd, "respawn"))
respawn = 1; respawn = 1;
else if (MATCH_CMD(cmd, "halt:", arg)) else if (MATCH_CMD(cmd, "halt:", arg))
halt = arg; halt = arg;
@@ -2338,7 +2360,7 @@ svc_t *service_register_desc(int type, char *cfg, const char *desc,
else if (MATCH_CMD(cmd, "tty:", arg)) else if (MATCH_CMD(cmd, "tty:", arg))
ctty = arg; ctty = arg;
/* catch both cgroup: and cgroup. handled in parse_cgroup() */ /* catch both cgroup: and cgroup. handled in parse_cgroup() */
else if (MATCH_CMD(cmd, "cgroup", arg)) else if ((arg = is_opt(cmd, "cgroup", ".:")))
cgroup = arg; cgroup = arg;
else if (MATCH_CMD(cmd, "conflict:", arg)) else if (MATCH_CMD(cmd, "conflict:", arg))
conflict = arg; conflict = arg;
+10
View File
@@ -89,6 +89,16 @@ retry 'assert_cmd pick "serv -np -i pick -- extra"'
assert_desc "Dashes" pick assert_desc "Dashes" pick
assert_num_children 1 serv assert_num_children 1 serv
# Options are matched as whole tokens, so a command that merely starts
# like one, pidof vs. pid, is still the command.
say 'A command named like an option is the command'
run "echo 'task lookalike {' > $FINIT_CONF"
run "echo ' command = \"pidof serv\"' >> $FINIT_CONF"
run "echo '}' >> $FINIT_CONF"
run "initctl reload"
retry 'assert_cmd lookalike "pidof serv"'
# A tty block takes the same list, its command is the getty to run. # A tty block takes the same list, its command is the getty to run.
# Asserting it is loaded is enough here, a getty in the test namespace # Asserting it is loaded is enough here, a getty in the test namespace
# has no terminal to attach to. # has no terminal to attach to.