Files
systemd/src/basic/proc-cmdline.c
Daan De Meyer 0c15577abe basic + fundamental: Clean up includes
Split out of #37344.
2025-05-25 10:06:07 +02:00

427 lines
14 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdlib.h>
#include "alloc-util.h"
#include "extract-word.h"
#include "fileio.h"
#include "getopt-defs.h"
#include "initrd-util.h"
#include "log.h"
#include "parse-util.h"
#include "proc-cmdline.h"
#include "process-util.h"
#include "string-util.h"
#include "strv.h"
#include "virt.h"
int proc_cmdline_filter_pid1_args(char **argv, char ***ret) {
enum {
COMMON_GETOPT_ARGS,
SYSTEMD_GETOPT_ARGS,
SHUTDOWN_GETOPT_ARGS,
};
static const struct option options[] = {
COMMON_GETOPT_OPTIONS,
SYSTEMD_GETOPT_OPTIONS,
SHUTDOWN_GETOPT_OPTIONS,
};
static const char *short_options = SYSTEMD_GETOPT_SHORT_OPTIONS;
_cleanup_strv_free_ char **filtered = NULL;
int state, r;
assert(argv);
assert(ret);
/* Currently, we do not support '-', '+', and ':' at the beginning. */
assert(!IN_SET(short_options[0], '-', '+', ':'));
/* Filter out all known options. */
state = no_argument;
STRV_FOREACH(p, strv_skip(argv, 1)) {
int prev_state = state;
const char *a = *p;
/* Reset the state for the next step. */
state = no_argument;
if (prev_state == required_argument ||
(prev_state == optional_argument && a[0] != '-'))
/* Handled as an argument of the previous option, filtering out the string. */
continue;
if (a[0] != '-') {
/* Not an option, accepting the string. */
r = strv_extend(&filtered, a);
if (r < 0)
return r;
continue;
}
if (a[1] == '-') {
if (a[2] == '\0') {
/* "--" is specified, accepting remaining strings. */
r = strv_extend_strv(&filtered, strv_skip(p, 1), /* filter_duplicates = */ false);
if (r < 0)
return r;
break;
}
/* long option, e.g. --foo */
FOREACH_ELEMENT(option, options) {
const char *q = startswith(a + 2, option->name);
if (!q || !IN_SET(q[0], '=', '\0'))
continue;
/* Found matching option, updating the state if necessary. */
if (q[0] == '\0' && option->has_arg == required_argument)
state = required_argument;
break;
}
continue;
}
/* short option(s), e.g. -x or -xyz */
while (a && *++a != '\0')
for (const char *q = short_options; *q != '\0'; q++) {
if (*q != *a)
continue;
/* Found matching short option. */
if (q[1] == ':') {
/* An argument is required or optional, and remaining part
* is handled as argument if exists. */
state = a[1] != '\0' ? no_argument :
q[2] == ':' ? optional_argument : required_argument;
a = NULL; /* Not necessary to parse remaining part. */
}
break;
}
}
*ret = TAKE_PTR(filtered);
return 0;
}
int proc_cmdline(char **ret) {
const char *e;
assert(ret);
/* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
e = secure_getenv("SYSTEMD_PROC_CMDLINE");
if (e)
return strdup_to(ret, e);
if (detect_container() > 0)
return pid_get_cmdline(1, SIZE_MAX, 0, ret);
return read_full_file("/proc/cmdline", ret, /* ret_size= */ NULL);
}
static int proc_cmdline_strv_internal(char ***ret, bool filter_pid1_args) {
const char *e;
int r;
assert(ret);
/* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */
e = secure_getenv("SYSTEMD_PROC_CMDLINE");
if (e)
return strv_split_full(ret, e, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
if (detect_container() > 0) {
_cleanup_strv_free_ char **args = NULL;
r = pid_get_cmdline_strv(1, /* flags = */ 0, &args);
if (r < 0)
return r;
if (filter_pid1_args)
return proc_cmdline_filter_pid1_args(args, ret);
*ret = TAKE_PTR(args);
return 0;
} else {
_cleanup_free_ char *s = NULL;
r = read_full_file("/proc/cmdline", &s, NULL);
if (r < 0)
return r;
return strv_split_full(ret, s, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE);
}
}
int proc_cmdline_strv(char ***ret) {
return proc_cmdline_strv_internal(ret, /* filter_pid1_args = */ false);
}
static char *mangle_word(const char *word, ProcCmdlineFlags flags) {
char *c;
c = startswith(word, "rd.");
if (c) {
/* Filter out arguments that are intended only for the initrd */
if (!in_initrd())
return NULL;
if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX))
return c;
} else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd())
/* And optionally filter out arguments that are intended only for the host */
return NULL;
return (char*) word;
}
static int proc_cmdline_parse_strv(char **args, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
int r;
assert(parse_item);
STRV_FOREACH(word, args) {
char *key, *value;
key = mangle_word(*word, flags);
if (!key)
continue;
value = strchr(key, '=');
if (value)
*(value++) = '\0';
r = parse_item(key, value, data);
if (r < 0)
return r;
}
return 0;
}
int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) {
_cleanup_strv_free_ char **args = NULL;
int r;
assert(parse_item);
/* The PROC_CMDLINE_VALUE_OPTIONAL and PROC_CMDLINE_TRUE_WHEN_MISSING flags don't really make sense
* for proc_cmdline_parse(), let's make this clear. */
assert(!(flags & (PROC_CMDLINE_VALUE_OPTIONAL|PROC_CMDLINE_TRUE_WHEN_MISSING)));
r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
if (r < 0)
return r;
return proc_cmdline_parse_strv(args, parse_item, data, flags);
}
static bool relaxed_equal_char(char a, char b) {
return a == b ||
(a == '_' && b == '-') ||
(a == '-' && b == '_');
}
char* proc_cmdline_key_startswith(const char *s, const char *prefix) {
assert(s);
assert(prefix);
/* Much like startswith(), but considers "-" and "_" the same */
for (; *prefix != 0; s++, prefix++)
if (!relaxed_equal_char(*s, *prefix))
return NULL;
return (char*) s;
}
bool proc_cmdline_key_streq(const char *x, const char *y) {
assert(x);
assert(y);
/* Much like streq(), but considers "-" and "_" the same */
for (; *x != 0 || *y != 0; x++, y++)
if (!relaxed_equal_char(*x, *y))
return false;
return true;
}
static int cmdline_get_key(char **args, const char *key, ProcCmdlineFlags flags, char **ret_value) {
_cleanup_free_ char *v = NULL;
bool found = false;
int r;
assert(key);
STRV_FOREACH(p, args) {
const char *word;
word = mangle_word(*p, flags);
if (!word)
continue;
if (ret_value) {
const char *e;
e = proc_cmdline_key_startswith(word, key);
if (!e)
continue;
if (*e == '=') {
r = free_and_strdup(&v, e+1);
if (r < 0)
return r;
found = true;
} else if (*e == 0 && FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL))
found = true;
} else {
if (proc_cmdline_key_streq(word, key)) {
found = true;
break; /* we found what we were looking for */
}
}
}
if (ret_value)
*ret_value = TAKE_PTR(v);
return found;
}
int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_value) {
_cleanup_strv_free_ char **args = NULL;
int r;
/* Looks for a specific key on the kernel command line. Supports three modes:
*
* a) The "ret_value" parameter is used. In this case a parameter beginning with the "key" string followed by
* "=" is searched for, and the value following it is returned in "ret_value".
*
* b) as above, but the PROC_CMDLINE_VALUE_OPTIONAL flag is set. In this case if the key is found as a separate
* word (i.e. not followed by "=" but instead by whitespace or the end of the command line), then this is
* also accepted, and "value" is returned as NULL.
*
* c) The "ret_value" parameter is NULL. In this case a search for the exact "key" parameter is performed.
*
* In all three cases, > 0 is returned if the key is found, 0 if not. */
/* PROC_CMDLINE_TRUE_WHEN_MISSING doesn't really make sense for proc_cmdline_get_key(). */
assert(!FLAGS_SET(flags, PROC_CMDLINE_TRUE_WHEN_MISSING));
if (isempty(key))
return -EINVAL;
if (FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL) && !ret_value)
return -EINVAL;
r = proc_cmdline_strv_internal(&args, /* filter_pid1_args = */ true);
if (r < 0)
return r;
return cmdline_get_key(args, key, flags, ret_value);
}
int proc_cmdline_get_bool(const char *key, ProcCmdlineFlags flags, bool *ret) {
_cleanup_free_ char *v = NULL;
int r;
assert(ret);
r = proc_cmdline_get_key(key, (flags & ~PROC_CMDLINE_TRUE_WHEN_MISSING) | PROC_CMDLINE_VALUE_OPTIONAL, &v);
if (r < 0)
return r;
if (r == 0) { /* key not specified at all */
*ret = FLAGS_SET(flags, PROC_CMDLINE_TRUE_WHEN_MISSING);
return 0;
}
if (v) { /* key with parameter passed */
r = parse_boolean(v);
if (r < 0)
return r;
*ret = r;
} else /* key without parameter passed */
*ret = true;
return 1;
}
static int cmdline_get_key_ap(ProcCmdlineFlags flags, char* const* args, va_list ap) {
int r, ret = 0;
for (;;) {
char **v;
const char *k, *e;
k = va_arg(ap, const char*);
if (!k)
break;
assert_se(v = va_arg(ap, char**));
STRV_FOREACH(p, args) {
const char *word;
word = mangle_word(*p, flags);
if (!word)
continue;
e = proc_cmdline_key_startswith(word, k);
if (e && *e == '=') {
r = free_and_strdup(v, e + 1);
if (r < 0)
return r;
ret++;
}
}
}
return ret;
}
int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) {
_cleanup_strv_free_ char **args = NULL;
int r;
va_list ap;
/* The PROC_CMDLINE_VALUE_OPTIONAL and PROC_CMDLINE_TRUE_WHEN_MISSING flags don't really make sense
* for proc_cmdline_get_key_many, let's make this clear. */
assert(!(flags & (PROC_CMDLINE_VALUE_OPTIONAL|PROC_CMDLINE_TRUE_WHEN_MISSING)));
/* This call may clobber arguments on failure! */
r = proc_cmdline_strv(&args);
if (r < 0)
return r;
va_start(ap, flags);
r = cmdline_get_key_ap(flags, args, ap);
va_end(ap);
return r;
}
bool proc_cmdline_value_missing(const char *key, const char *value) {
assert(key);
if (!value) {
log_warning("Missing argument for %s= kernel command line switch, ignoring.", key);
return true;
}
return false;
}