diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 838cc7e4cf..f9025247d8 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -36,10 +36,13 @@ All tools: * `$SD_EVENT_PROFILE_DELAYS=1` — if set, the sd-event event loop implementation will print latency information at runtime. -* `$SYSTEMD_PROC_CMDLINE` — if set, may contain a string that is used as kernel - command line instead of the actual one readable from /proc/cmdline. This is - useful for debugging, in order to test generators and other code against - specific kernel command lines. +* `$SYSTEMD_PROC_CMDLINE` — if set, the contents are used as the kernel command + line instead of the actual one in /proc/cmdline. This is useful for + debugging, in order to test generators and other code against specific kernel + command lines. + +* `$SYSTEMD_EFI_OPTIONS` — if set, used instead of the string in SystemdOptions + EFI variable. Analogous to `$SYSTEMD_PROC_CMDLINE`. * `$SYSTEMD_IN_INITRD` — takes a boolean. If set, overrides initrd detection. This is useful for debugging and testing initrd-only programs in the main diff --git a/src/basic/efivars.c b/src/basic/efivars.c index 5264ab1b50..53875de5a0 100644 --- a/src/basic/efivars.c +++ b/src/basic/efivars.c @@ -222,4 +222,29 @@ int efi_set_variable_string(sd_id128_t vendor, const char *name, const char *v) return efi_set_variable(vendor, name, u16, (char16_strlen(u16) + 1) * sizeof(char16_t)); } +int efi_systemd_options_variable(char **line) { + const char *e; + int r; + + assert(line); + + /* For testing purposes it is sometimes useful to be able to override this */ + e = secure_getenv("SYSTEMD_EFI_OPTIONS"); + if (e) { + char *m; + + m = strdup(e); + if (!m) + return -ENOMEM; + + *line = m; + return 0; + } + + r = efi_get_variable_string(EFI_VENDOR_SYSTEMD, "SystemdOptions", line); + if (r == -ENOENT) + return -ENODATA; + + return r; +} #endif diff --git a/src/basic/efivars.h b/src/basic/efivars.h index e8cb5b8a9a..22edbed985 100644 --- a/src/basic/efivars.h +++ b/src/basic/efivars.h @@ -13,8 +13,9 @@ #include "efi/loader-features.h" #include "time-util.h" -#define EFI_VENDOR_LOADER SD_ID128_MAKE(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f) -#define EFI_VENDOR_GLOBAL SD_ID128_MAKE(8b,e4,df,61,93,ca,11,d2,aa,0d,00,e0,98,03,2b,8c) +#define EFI_VENDOR_LOADER SD_ID128_MAKE(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f) +#define EFI_VENDOR_GLOBAL SD_ID128_MAKE(8b,e4,df,61,93,ca,11,d2,aa,0d,00,e0,98,03,2b,8c) +#define EFI_VENDOR_SYSTEMD SD_ID128_MAKE(8c,f2,64,4b,4b,0b,42,8f,93,87,6d,87,60,50,dc,67) #define EFI_VARIABLE_NON_VOLATILE 0x0000000000000001 #define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x0000000000000002 #define EFI_VARIABLE_RUNTIME_ACCESS 0x0000000000000004 @@ -27,6 +28,8 @@ int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p); int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size); int efi_set_variable_string(sd_id128_t vendor, const char *name, const char *p); +int efi_systemd_options_variable(char **line); + #else static inline char* efi_variable_path(sd_id128_t vendor, const char *name) { @@ -49,4 +52,8 @@ static inline int efi_set_variable_string(sd_id128_t vendor, const char *name, c return -EOPNOTSUPP; } +static inline int efi_systemd_options_variable(char **line) { + return -ENODATA; +} + #endif diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index 23cda75708..44d1e9aec4 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -5,6 +5,7 @@ #include #include "alloc-util.h" +#include "efivars.h" #include "extract-word.h" #include "fileio.h" #include "macro.h" @@ -117,6 +118,17 @@ int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineF assert(parse_item); + /* We parse the EFI variable first, because later settings have higher priority. */ + + r = efi_systemd_options_variable(&line); + if (r < 0 && r != -ENODATA) + log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m"); + + 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; @@ -156,34 +168,14 @@ bool proc_cmdline_key_streq(const char *x, const char *y) { return true; } -int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_value) { - _cleanup_free_ char *line = NULL, *ret = NULL; +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; - /* 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. */ - - 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; + assert(line); + assert(key); p = line; for (;;) { @@ -226,6 +218,48 @@ int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_val return found; } +int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_value) { + _cleanup_free_ char *line = 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; + + r = cmdline_get_key(line, key, flags, ret_value); + if (r != 0) /* Either error or true if found. */ + return r; + + line = mfree(line); + r = efi_systemd_options_variable(&line); + if (r == -ENODATA) + 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; diff --git a/src/test/test-proc-cmdline.c b/src/test/test-proc-cmdline.c index 1a00642f86..3231e4a3e6 100644 --- a/src/test/test-proc-cmdline.c +++ b/src/test/test-proc-cmdline.c @@ -30,8 +30,9 @@ static void test_proc_cmdline_override(void) { log_info("/* %s */", __func__); assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\"") == 0); + assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=differnt") == 0); - /* Test if the override works */ + /* First test if the overrides for /proc/cmdline still work */ _cleanup_free_ char *line = NULL, *value = NULL; assert_se(proc_cmdline(&line) >= 0); @@ -45,6 +46,19 @@ static void test_proc_cmdline_override(void) { assert_se(proc_cmdline_get_key("and_one_more", 0, &value) > 0 && streq_ptr(value, "zzz aaa")); value = mfree(value); + + assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=") == 0); + assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\"") == 0); + + assert_se(streq(line, "foo_bar=quux wuff-piep=tuet zumm some_arg_with_space='foo bar' and_one_more=\"zzz aaa\"")); + assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + + assert_se(proc_cmdline_get_key("some_arg_with_space", 0, &value) > 0 && streq_ptr(value, "foo bar")); + value = mfree(value); + + assert_se(proc_cmdline_get_key("and_one_more", 0, &value) > 0 && streq_ptr(value, "zzz aaa")); + value = mfree(value); } static int parse_item_given(const char *key, const char *value, void *data) { @@ -140,6 +154,24 @@ static void test_proc_cmdline_get_bool(void) { log_info("/* %s */", __func__); assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar bar-waldo=1 x_y-z=0 quux=miep\nda=yes\nthe=1") == 0); + assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=") == 0); + + assert_se(proc_cmdline_get_bool("", &value) == -EINVAL); + assert_se(proc_cmdline_get_bool("abc", &value) == 0 && value == false); + assert_se(proc_cmdline_get_bool("foo_bar", &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("foo-bar", &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("bar-waldo", &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("bar_waldo", &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("x_y-z", &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("x-y-z", &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("x-y_z", &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("x_y_z", &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("quux", &value) == -EINVAL && value == false); + assert_se(proc_cmdline_get_bool("da", &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("the", &value) > 0 && value == true); + + assert_se(putenv((char*) "SYSTEMD_PROC_CMDLINE=") == 0); + assert_se(putenv((char*) "SYSTEMD_EFI_OPTIONS=foo_bar bar-waldo=1 x_y-z=0 quux=miep\nda=yes\nthe=1") == 0); assert_se(proc_cmdline_get_bool("", &value) == -EINVAL); assert_se(proc_cmdline_get_bool("abc", &value) == 0 && value == false);