diff options
Diffstat (limited to 'src/basic/proc-cmdline.c')
-rw-r--r-- | src/basic/proc-cmdline.c | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c new file mode 100644 index 0000000..a3aee28 --- /dev/null +++ b/src/basic/proc-cmdline.c @@ -0,0 +1,376 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <stdbool.h> +#include <stddef.h> + +#include "alloc-util.h" +#include "efivars.h" +#include "extract-word.h" +#include "fileio.h" +#include "macro.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "special.h" +#include "string-util.h" +#include "util.h" +#include "virt.h" + +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) { + char *m; + + m = strdup(e); + if (!m) + return -ENOMEM; + + *ret = m; + return 0; + } + + if (detect_container() > 0) + return get_process_cmdline(1, SIZE_MAX, 0, ret); + + return read_virtual_file("/proc/cmdline", SIZE_MAX, ret, NULL); +} + +static int proc_cmdline_extract_first(const char **p, char **ret_word, ProcCmdlineFlags flags) { + const char *q = *p; + int r; + + for (;;) { + _cleanup_free_ char *word = NULL; + const char *c; + + r = extract_first_word(&q, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return r; + if (r == 0) + break; + + /* Filter out arguments that are intended only for the initrd */ + c = startswith(word, "rd."); + if (c) { + if (!in_initrd()) + continue; + + if (FLAGS_SET(flags, PROC_CMDLINE_STRIP_RD_PREFIX)) { + r = free_and_strdup(&word, c); + if (r < 0) + return r; + } + + } else if (FLAGS_SET(flags, PROC_CMDLINE_RD_STRICT) && in_initrd()) + continue; /* And optionally filter out arguments that are intended only for the host */ + + *p = q; + *ret_word = TAKE_PTR(word); + return 1; + } + + *p = q; + *ret_word = NULL; + return 0; +} + +int proc_cmdline_parse_given(const char *line, proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) { + const char *p; + int r; + + assert(parse_item); + + /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_parse(), let's make this + * clear. */ + assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL)); + + p = line; + for (;;) { + _cleanup_free_ char *word = NULL; + char *value; + + r = proc_cmdline_extract_first(&p, &word, flags); + if (r < 0) + return r; + if (r == 0) + break; + + value = strchr(word, '='); + if (value) + *(value++) = 0; + + r = parse_item(word, value, data); + if (r < 0) + return r; + } + + return 0; +} + +int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineFlags flags) { + _cleanup_free_ char *line = NULL; + int r; + + assert(parse_item); + + /* We parse the EFI variable first, because later settings have higher priority. */ + + if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) { + r = systemd_efi_options_variable(&line); + if (r < 0) { + if (r != -ENODATA) + log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m"); + } else { + r = proc_cmdline_parse_given(line, parse_item, data, flags); + if (r < 0) + return r; + + line = mfree(line); + } + } + + r = proc_cmdline(&line); + if (r < 0) + return r; + + return proc_cmdline_parse_given(line, 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(const char *line, const char *key, ProcCmdlineFlags flags, char **ret_value) { + _cleanup_free_ char *ret = NULL; + bool found = false; + const char *p; + int r; + + assert(line); + assert(key); + + p = line; + for (;;) { + _cleanup_free_ char *word = NULL; + + r = proc_cmdline_extract_first(&p, &word, flags); + if (r < 0) + return r; + if (r == 0) + break; + + if (ret_value) { + const char *e; + + e = proc_cmdline_key_startswith(word, key); + if (!e) + continue; + + if (*e == '=') { + r = free_and_strdup(&ret, e+1); + if (r < 0) + return r; + + found = true; + + } else if (*e == 0 && FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL)) + found = true; + + } else { + if (streq(word, key)) { + found = true; + break; /* we found what we were looking for */ + } + } + } + + if (ret_value) + *ret_value = TAKE_PTR(ret); + + return found; +} + +int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_value) { + _cleanup_free_ char *line = NULL, *v = NULL; + int r; + + /* Looks for a specific key on the kernel command line and (with lower priority) the EFI variable. + * 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. */ + + if (isempty(key)) + return -EINVAL; + + if (FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL) && !ret_value) + return -EINVAL; + + r = proc_cmdline(&line); + if (r < 0) + return r; + + if (FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) /* Shortcut */ + return cmdline_get_key(line, key, flags, ret_value); + + r = cmdline_get_key(line, key, flags, ret_value ? &v : NULL); + if (r < 0) + return r; + if (r > 0) { + if (ret_value) + *ret_value = TAKE_PTR(v); + + return r; + } + + line = mfree(line); + r = systemd_efi_options_variable(&line); + if (r == -ENODATA) { + if (ret_value) + *ret_value = NULL; + + return false; /* Not found */ + } + if (r < 0) + return r; + + return cmdline_get_key(line, key, flags, ret_value); +} + +int proc_cmdline_get_bool(const char *key, bool *ret) { + _cleanup_free_ char *v = NULL; + int r; + + assert(ret); + + r = proc_cmdline_get_key(key, PROC_CMDLINE_VALUE_OPTIONAL, &v); + if (r < 0) + return r; + if (r == 0) { /* key not specified at all */ + *ret = false; + 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; +} + +int proc_cmdline_get_key_many_internal(ProcCmdlineFlags flags, ...) { + _cleanup_free_ char *line = NULL; + bool processing_efi = true; + const char *p; + va_list ap; + int r, ret = 0; + + /* The PROC_CMDLINE_VALUE_OPTIONAL flag doesn't really make sense for proc_cmdline_get_key_many(), let's make + * this clear. */ + assert(!FLAGS_SET(flags, PROC_CMDLINE_VALUE_OPTIONAL)); + + /* This call may clobber arguments on failure! */ + + if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) { + r = systemd_efi_options_variable(&line); + if (r < 0 && r != -ENODATA) + log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m"); + } + + p = line; + for (;;) { + _cleanup_free_ char *word = NULL; + + r = proc_cmdline_extract_first(&p, &word, flags); + if (r < 0) + return r; + if (r == 0) { + /* We finished with this command line. If this was the EFI one, then let's proceed with the regular one */ + if (processing_efi) { + processing_efi = false; + + line = mfree(line); + r = proc_cmdline(&line); + if (r < 0) + return r; + + p = line; + continue; + } + + break; + } + + va_start(ap, flags); + + for (;;) { + char **v; + const char *k, *e; + + k = va_arg(ap, const char*); + if (!k) + break; + + assert_se(v = va_arg(ap, char**)); + + e = proc_cmdline_key_startswith(word, k); + if (e && *e == '=') { + r = free_and_strdup(v, e + 1); + if (r < 0) { + va_end(ap); + return r; + } + + ret++; + } + } + + va_end(ap); + } + + return ret; +} |