From ecb690c9eca4a9b618514de6e1fbeba1de0e1a4c Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 1 Jul 2025 09:02:00 +0200 Subject: [PATCH] service: support quoted arguments to run/task/service options Fixes #441 Signed-off-by: Joachim Wiberg --- src/cgroup.c | 15 ++++++++++ src/service.c | 82 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/cgroup.c b/src/cgroup.c index 9e99848a..d7058f75 100644 --- a/src/cgroup.c +++ b/src/cgroup.c @@ -88,6 +88,21 @@ static void cgset(const char *path, char *ctrl, char *prop) } *val++ = 0; + /* unquote value, if quoted */ + if (val[0] == '"' || val[0] == '\'') { + char q = val[0]; + char *end; + + end = strchr(&val[1], q); + if (!end) { + errx(1, "Syntax error, unterminated quote in %s/%s.%s=%s", path, ctrl, prop, val); + return; + } + + val++; + *end = 0; + } + /* disallow sneaky relative paths */ if (strstr(ctrl, "..") || strstr(prop, "..")) { errx(1, "Possible security violation; '..' not allowed in cgroup config!"); diff --git a/src/service.c b/src/service.c index 023b9bc1..4a050f8b 100644 --- a/src/service.c +++ b/src/service.c @@ -1190,6 +1190,83 @@ void service_runlevel(int newlevel) networking(0); } +/* + * Parse run/task/service arguments with support for quoted strings, both + * single and double quotes to allow arguments containing spaces. + * + * Returns next argument or NULL if end of line + * Updates *line to point past the parsed argument + */ +static char *parse_args(char **line) +{ + char *start, *end = NULL, *arg; + char in_quote = 0; + int has_colon = 0; + + if (!line || !*line) + return NULL; + + /* Skip leading whitespace */ + while (**line && (**line == ' ' || **line == '\t')) + (*line)++; + + if (!**line) + return NULL; + + start = *line; + + /* Parse the token */ + while (**line) { + if (in_quote) { + if (**line == in_quote) { + /* End quote found */ + in_quote = 0; + } + } else { + if (**line == '\'' || **line == '"') { + /* Start quote */ + in_quote = **line; + } else if (**line == ':') { + /* Found colon - this might be key:value format */ + has_colon = 1; + } else if (**line == ' ' || **line == '\t') { + /* Whitespace - check if we're in key:value mode */ + if (has_colon) { + /* Look ahead to see if there's a comma after whitespace */ + char *lookahead = *line; + while (*lookahead && (*lookahead == ' ' || *lookahead == '\t')) + lookahead++; + if (*lookahead == ',') { + /* Continue parsing - this space is part of the token */ + (*line)++; + continue; + } + } + /* End token at whitespace */ + end = *line; + break; + } + } + (*line)++; + } + + /* Set end if we reached end of string */ + if (!end) + end = *line; + + /* Null terminate the argument */ + if (end > start) { + arg = start; + if (*end) { + *end = '\0'; + *line = end + 1; + } + return arg; + } + + return NULL; +} + /* * log:/path/to/logfile,priority:facility.level,tag:ident */ @@ -1564,7 +1641,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) } } - cmd = strtok_r(line, " \t", &args); + args = line; + cmd = parse_args(&args); if (!cmd) { incomplete: errx(1, "Incomplete service '%s', cannot register", cfg); @@ -1644,7 +1722,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) break; /* Check if valid command follows... */ - cmd = strtok_r(NULL, " ", &args); + cmd = parse_args(&args); if (!cmd) goto incomplete; }