diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
commit | 55944e5e40b1be2afc4855d8d2baf4b73d1876b5 (patch) | |
tree | 33f869f55a1b149e9b7c2b7e201867ca5dd52992 /src/basic/proc-cmdline.c | |
parent | Initial commit. (diff) | |
download | systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.tar.xz systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.zip |
Adding upstream version 255.4.upstream/255.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/basic/proc-cmdline.c')
-rw-r--r-- | src/basic/proc-cmdline.c | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c new file mode 100644 index 0000000..522d8de --- /dev/null +++ b/src/basic/proc-cmdline.c @@ -0,0 +1,501 @@ +/* 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 "getopt-defs.h" +#include "initrd-util.h" +#include "macro.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 */ + for (size_t i = 0; i < ELEMENTSOF(options); i++) { + const char *q = startswith(a + 2, options[i].name); + if (!q || !IN_SET(q[0], '=', '\0')) + continue; + + /* Found matching option, updating the state if necessary. */ + if (q[0] == '\0' && options[i].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) { + char *m; + + m = strdup(e); + if (!m) + return -ENOMEM; + + *ret = m; + return 0; + } + + if (detect_container() > 0) + return pid_get_cmdline(1, SIZE_MAX, 0, ret); + + return read_virtual_file("/proc/cmdline", SIZE_MAX, ret, 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))); + + /* We parse the EFI variable first, because later settings have higher priority. */ + + if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) { + _cleanup_free_ char *line = NULL; + + 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 = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return r; + + r = proc_cmdline_parse_strv(args, parse_item, data, flags); + if (r < 0) + return r; + + args = strv_free(args); + } + } + + 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; + _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. */ + + /* 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; + + if (FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) /* Shortcut */ + return cmdline_get_key(args, key, flags, ret_value); + + r = cmdline_get_key(args, key, flags, ret_value ? &v : NULL); + if (r < 0) + return r; + if (r > 0) { + if (ret_value) + *ret_value = TAKE_PTR(v); + + return r; + } + + r = systemd_efi_options_variable(&line); + if (r == -ENODATA) { + if (ret_value) + *ret_value = NULL; + + return false; /* Not found */ + } + if (r < 0) + return r; + + args = strv_free(args); + r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE); + 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, ret = 0; + 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! */ + + if (!FLAGS_SET(flags, PROC_CMDLINE_IGNORE_EFI_OPTIONS)) { + _cleanup_free_ char *line = NULL; + + r = systemd_efi_options_variable(&line); + if (r < 0 && r != -ENODATA) + log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m"); + if (r >= 0) { + r = strv_split_full(&args, line, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX|EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return r; + + va_start(ap, flags); + r = cmdline_get_key_ap(flags, args, ap); + va_end(ap); + if (r < 0) + return r; + + ret = r; + args = strv_free(args); + } + } + + 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); + if (r < 0) + return r; + + return ret + r; +} |