From f582cbca0029d3867620dfa7df86534d2a241275 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 9 Dec 2016 01:11:42 +0100 Subject: firstboot: add kernel cmdline option to disable firstboot wizard If booting with systemd.firstboot=0 the wizard will be skipped. --- man/kernel-command-line.xml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'man/kernel-command-line.xml') diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 1fa31a14b7..c86e45265f 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -353,6 +353,15 @@ systemd-hibernate-resume-generator8. + + + systemd.firstboot= + + Takes a boolean argument, defaults to on. If off, + systemd-firstboot.service8 + will not query the user for basic system settings, even if the system boots up for the first time and the + relevant settings are not initialized yet. + @@ -376,7 +385,8 @@ systemd-modules-load.service8, systemd-backlight@.service8, systemd-rfkill.service8, - systemd-hibernate-resume-generator8 + systemd-hibernate-resume-generator8, + systemd-firstboot.service8 -- cgit v1.2.3-54-g00ecf From 1d84ad944520fc3e062ef518c4db4e1d3a1866af Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 12 Dec 2016 18:29:15 +0100 Subject: util-lib: various improvements to kernel command line parsing This improves kernel command line parsing in a number of ways: a) An kernel option "foo_bar=xyz" is now considered equivalent to "foo-bar-xyz", i.e. when comparing kernel command line option names "-" and "_" are now considered equivalent (this only applies to the option names though, not the option values!). Most of our kernel options used "-" as word separator in kernel command line options so far, but some used "_". With this change, which was a source of confusion for users (well, at least of one user: myself, I just couldn't remember that it's systemd.debug-shell, not systemd.debug_shell). Considering both as equivalent is inspired how modern kernel module loading normalizes all kernel module names to use underscores now too. b) All options previously using a dash for separating words in kernel command line options now use an underscore instead, in all documentation and in code. Since a) has been implemented this should not create any compatibility problems, but normalizes our documentation and our code. c) All kernel command line options which take booleans (or are boolean-like) have been reworked so that "foobar" (without argument) is now equivalent to "foobar=1" (but not "foobar=0"), thus normalizing the handling of our boolean arguments. Specifically this means systemd.debug-shell and systemd_debug_shell=1 are now entirely equivalent. d) All kernel command line options which take an argument, and where no argument is specified will now result in a log message. e.g. passing just "systemd.unit" will no result in a complain that it needs an argument. This is implemented in the proc_cmdline_missing_value() function. e) There's now a call proc_cmdline_get_bool() similar to proc_cmdline_get_key() that parses booleans (following the logic explained in c). f) The proc_cmdline_parse() call's boolean argument has been replaced by a new flags argument that takes a common set of bits with proc_cmdline_get_key(). g) All kernel command line APIs now begin with the same "proc_cmdline_" prefix. h) There are now tests for much of this. Yay! --- man/kernel-command-line.xml | 22 ++-- man/systemd-debug-generator.xml | 2 +- man/systemd-modules-load.service.xml | 4 +- man/systemd-udevd.service.xml | 16 +-- src/basic/cgroup-util.c | 39 ++----- src/basic/log.c | 25 ++-- src/basic/proc-cmdline.c | 133 ++++++++++++++++++---- src/basic/proc-cmdline.h | 33 +++++- src/core/main.c | 116 +++++++++++-------- src/cryptsetup/cryptsetup-generator.c | 46 ++++---- src/debug-generator/debug-generator.c | 61 +++++----- src/firstboot/firstboot.c | 19 ++-- src/fsck/fsck.c | 12 +- src/fstab-generator/fstab-generator.c | 36 ++++-- src/gpt-auto-generator/gpt-auto-generator.c | 11 +- src/hibernate-resume/hibernate-resume-generator.c | 25 ++-- src/journal/journald-server.c | 56 +++++++-- src/modules-load/modules-load.c | 8 +- src/quotacheck/quotacheck.c | 7 +- src/test/test-proc-cmdline.c | 96 +++++++++++++++- src/udev/net/link-config.c | 9 +- src/udev/udevd.c | 41 +++++-- 22 files changed, 561 insertions(+), 256 deletions(-) (limited to 'man/kernel-command-line.xml') diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index c86e45265f..78e45e66a9 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -102,7 +102,7 @@ systemd.mask= systemd.wants= - systemd.debug-shell + systemd.debug_shell Additional parameters understood by systemd-debug-generator8, @@ -236,14 +236,14 @@ - udev.log-priority= - rd.udev.log-priority= - udev.children-max= - rd.udev.children-max= - udev.exec-delay= - rd.udev.exec-delay= - udev.event-timeout= - rd.udev.event-timeout= + udev.log_priority= + rd.udev.log_priority= + udev.children_max= + rd.udev.children_max= + udev.exec_delay= + rd.udev.exec_delay= + udev.event_timeout= + rd.udev.event_timeout= net.ifnames= @@ -332,8 +332,8 @@ - modules-load= - rd.modules-load= + modules_load= + rd.modules_load= Load a specific kernel module early at boot. For diff --git a/man/systemd-debug-generator.xml b/man/systemd-debug-generator.xml index 5c5e9fc4a1..0a66b9bbf9 100644 --- a/man/systemd-debug-generator.xml +++ b/man/systemd-debug-generator.xml @@ -70,7 +70,7 @@ the initial transaction. This is useful to start one or more additional units at boot. May be specified more than once. - If the option is + If the option is specified, the debug shell service debug-shell.service is pulled into the boot transaction. It will spawn a debug shell on tty9 during early diff --git a/man/systemd-modules-load.service.xml b/man/systemd-modules-load.service.xml index b25929b2e4..ee097d7f5c 100644 --- a/man/systemd-modules-load.service.xml +++ b/man/systemd-modules-load.service.xml @@ -73,8 +73,8 @@ - modules-load= - rd.modules-load= + modules_load= + rd.modules_load= Takes a comma-separated list of kernel modules to statically load during early boot. The option prefixed with diff --git a/man/systemd-udevd.service.xml b/man/systemd-udevd.service.xml index 243fd06471..81b957b0c8 100644 --- a/man/systemd-udevd.service.xml +++ b/man/systemd-udevd.service.xml @@ -133,22 +133,22 @@ Parameters starting with "rd." will be read when systemd-udevd is used in an initrd. - udev.log-priority= - rd.udev.log-priority= + udev.log_priority= + rd.udev.log_priority= Set the log level. - udev.children-max= - rd.udev.children-max= + udev.children_max= + rd.udev.children_max= Limit the number of events executed in parallel. - udev.exec-delay= - rd.udev.exec-delay= + udev.exec_delay= + rd.udev.exec_delay= Delay the execution of RUN instructions by the given number of seconds. This option might be useful when @@ -157,8 +157,8 @@ - udev.event-timeout= - rd.udev.event-timeout= + udev.event_timeout= + rd.udev.event_timeout= Wait for events to finish up to the given number of seconds. This option might be useful if events are diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index dc13025115..d2d18f13f0 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -2361,6 +2361,7 @@ int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p) { bool cg_is_unified_wanted(void) { static thread_local int wanted = -1; int r, unified; + bool b; /* If the hierarchy is already mounted, then follow whatever * was chosen for it. */ @@ -2374,20 +2375,11 @@ bool cg_is_unified_wanted(void) { if (wanted >= 0) return wanted; - r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy", NULL); - if (r > 0) - return (wanted = true); - else { - _cleanup_free_ char *value = NULL; - - r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy=", &value); - if (r < 0) - return false; - if (r == 0) - return (wanted = false); + r = proc_cmdline_get_bool("systemd.unified_cgroup_hierarchy", &b); + if (r < 0) + return false; - return (wanted = parse_boolean(value) > 0); - } + return (wanted = r > 0 ? b : false); } bool cg_is_legacy_wanted(void) { @@ -2397,6 +2389,7 @@ bool cg_is_legacy_wanted(void) { bool cg_is_unified_systemd_controller_wanted(void) { static thread_local int wanted = -1; int r, unified; + bool b; /* If the unified hierarchy is requested in full, no need to * bother with this. */ @@ -2415,23 +2408,11 @@ bool cg_is_unified_systemd_controller_wanted(void) { if (wanted >= 0) return wanted; - r = get_proc_cmdline_key("systemd.legacy_systemd_cgroup_controller", NULL); - if (r > 0) - wanted = false; - else { - _cleanup_free_ char *value = NULL; - - r = get_proc_cmdline_key("systemd.legacy_systemd_cgroup_controller=", &value); - if (r < 0) - return false; - - if (r == 0) - wanted = false; - else - wanted = parse_boolean(value) <= 0; - } + r = proc_cmdline_get_bool("systemd.legacy_systemd_cgroup_controller", &b); + if (r < 0) + return false; - return wanted; + return (wanted = r > 0 ? b : false); } bool cg_is_legacy_systemd_controller_wanted(void) { diff --git a/src/basic/log.c b/src/basic/log.c index 557212c022..1362b1c086 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -981,24 +981,30 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (streq(key, "debug") && !value) log_set_max_level(LOG_DEBUG); - else if (streq(key, "systemd.log_target") && value) { + else if (proc_cmdline_key_streq(key, "systemd.log_target")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (log_set_target_from_string(value) < 0) log_warning("Failed to parse log target '%s'. Ignoring.", value); - } else if (streq(key, "systemd.log_level") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.log_level")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (log_set_max_level_from_string(value) < 0) log_warning("Failed to parse log level '%s'. Ignoring.", value); - } else if (streq(key, "systemd.log_color") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.log_color")) { - if (log_show_color_from_string(value) < 0) + if (log_show_color_from_string(value ?: "1") < 0) log_warning("Failed to parse log color setting '%s'. Ignoring.", value); - } else if (streq(key, "systemd.log_location") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.log_location")) { - if (log_show_location_from_string(value) < 0) + if (log_show_location_from_string(value ?: "1") < 0) log_warning("Failed to parse log location setting '%s'. Ignoring.", value); } @@ -1009,10 +1015,9 @@ void log_parse_environment(void) { const char *e; if (get_ctty_devnr(0, NULL) < 0) - /* Only try to read the command line in daemons. - We assume that anything that has a controlling - tty is user stuff. */ - (void) parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); + /* Only try to read the command line in daemons. We assume that anything that has a controlling tty is + user stuff. */ + (void) proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); e = secure_getenv("SYSTEMD_LOG_TARGET"); if (e && log_set_target_from_string(e) < 0) diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index 98c94ed5e2..6ecb6c3f0d 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -56,9 +56,8 @@ int proc_cmdline(char **ret) { return read_one_line_file("/proc/cmdline", ret); } -int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, void *data), - void *data, - bool strip_prefix) { +int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, unsigned flags) { + _cleanup_free_ char *line = NULL; const char *p; int r; @@ -72,7 +71,7 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, voi p = line; for (;;) { _cleanup_free_ char *word = NULL; - char *value = NULL, *unprefixed; + char *value, *key, *q; r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); if (r < 0) @@ -80,17 +79,23 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, voi if (r == 0) break; - /* Filter out arguments that are intended only for the - * initrd */ - unprefixed = startswith(word, "rd."); - if (unprefixed && !in_initrd()) - continue; + key = word; + + /* Filter out arguments that are intended only for the initrd */ + q = startswith(word, "rd."); + if (q) { + if (!in_initrd()) + continue; + + if (flags & PROC_CMDLINE_STRIP_RD_PREFIX) + key = q; + } - value = strchr(word, '='); + value = strchr(key, '='); if (value) *(value++) = 0; - r = parse_item(strip_prefix && unprefixed ? unprefixed : word, value, data); + r = parse_item(key, value, data); if (r < 0) return r; } @@ -98,13 +103,64 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, voi return 0; } -int get_proc_cmdline_key(const char *key, char **value) { +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; +} + +int proc_cmdline_get_key(const char *key, unsigned flags, char **value) { _cleanup_free_ char *line = NULL, *ret = NULL; bool found = false; const char *p; int r; - assert(key); + /* Looks for a specific key on the kernel command line. Supports two modes: + * + * a) The "value" parameter is used. In this case a parameter beginning with the "key" string followed by "=" + * is searched, and the value following this is returned in "value". + * + * b) as above, but the PROC_CMDLINE_VALUE_OPTIONAL flag is set. In this case if the 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 "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 & PROC_CMDLINE_VALUE_OPTIONAL) && !value) + return -EINVAL; r = proc_cmdline(&line); if (r < 0) @@ -121,21 +177,26 @@ int get_proc_cmdline_key(const char *key, char **value) { if (r == 0) break; - /* Filter out arguments that are intended only for the - * initrd */ + /* Automatically filter out arguments that are intended only for the initrd, if we are not in the + * initrd. */ if (!in_initrd() && startswith(word, "rd.")) continue; if (value) { - e = startswith(word, key); + e = proc_cmdline_key_startswith(word, key); if (!e) continue; - r = free_and_strdup(&ret, e); - if (r < 0) - return r; + if (*e == '=') { + r = free_and_strdup(&ret, e+1); + if (r < 0) + return r; + + found = true; + + } else if (*e == 0 && (flags & PROC_CMDLINE_VALUE_OPTIONAL)) + found = true; - found = true; } else { if (streq(word, key)) found = true; @@ -148,20 +209,42 @@ int get_proc_cmdline_key(const char *key, char **value) { } return found; +} + +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) { + *ret = false; + return 0; + } + + if (v) { /* parameter passed */ + r = parse_boolean(v); + if (r < 0) + return r; + *ret = r; + } else /* no parameter passed */ + *ret = true; + return 1; } int shall_restore_state(void) { - _cleanup_free_ char *value = NULL; + bool ret; int r; - r = get_proc_cmdline_key("systemd.restore_state=", &value); + r = proc_cmdline_get_bool("systemd.restore_state", &ret); if (r < 0) return r; - if (r == 0) - return true; - return parse_boolean(value); + return r > 0 ? ret : true; } static const char * const rlmap[] = { diff --git a/src/basic/proc-cmdline.h b/src/basic/proc-cmdline.h index 6d6ee95c11..ebfed355e9 100644 --- a/src/basic/proc-cmdline.h +++ b/src/basic/proc-cmdline.h @@ -19,11 +19,36 @@ along with systemd; If not, see . ***/ +#include + +#include "log.h" + +enum { + PROC_CMDLINE_STRIP_RD_PREFIX = 1, + PROC_CMDLINE_VALUE_OPTIONAL = 2, +}; + +typedef int (*proc_cmdline_parse_t)(const char *key, const char *value, void *data); + int proc_cmdline(char **ret); -int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, void *data), - void *data, - bool strip_prefix); -int get_proc_cmdline_key(const char *parameter, char **value); + +int proc_cmdline_parse(const proc_cmdline_parse_t parse, void *userdata, unsigned flags); + +int proc_cmdline_get_key(const char *parameter, unsigned flags, char **value); +int proc_cmdline_get_bool(const char *key, bool *ret); + +char *proc_cmdline_key_startswith(const char *s, const char *prefix); +bool proc_cmdline_key_streq(const char *x, const char *y); int shall_restore_state(void); const char* runlevel_to_target(const char *rl); + +/* A little helper call, to be used in proc_cmdline_parse_t callbacks */ +static inline bool proc_cmdline_value_missing(const char *key, const char *value) { + if (!value) { + log_warning("Missing argument for %s= kernel command line switch, ignoring.", key); + return true; + } + + return false; +} diff --git a/src/core/main.c b/src/core/main.c index 6ec996577c..56a81ab94a 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -337,60 +337,73 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat assert(key); - if (streq(key, "systemd.unit") && value) { + if (STR_IN_SET(key, "systemd.unit", "rd.systemd.unit")) { - if (!in_initrd()) - return free_and_strdup(&arg_default_unit, value); + if (proc_cmdline_value_missing(key, value)) + return 0; - } else if (streq(key, "rd.systemd.unit") && value) { - - if (in_initrd()) - return free_and_strdup(&arg_default_unit, value); + if (!unit_name_is_valid(value, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) + log_warning("Unit name specified on %s= is not valid, ignoring: %s", key, value); + else if (in_initrd() == !!startswith(key, "rd.")) { + if (free_and_strdup(&arg_default_unit, value) < 0) + return log_oom(); + } - } else if (streq(key, "systemd.dump_core") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.dump_core")) { - r = parse_boolean(value); + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse dump core switch %s. Ignoring.", value); else arg_dump_core = r; - } else if (streq(key, "systemd.crash_chvt") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.crash_chvt")) { - if (parse_crash_chvt(value) < 0) + if (!value) + arg_crash_chvt = 0; /* turn on */ + else if (parse_crash_chvt(value) < 0) log_warning("Failed to parse crash chvt switch %s. Ignoring.", value); - } else if (streq(key, "systemd.crash_shell") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.crash_shell")) { - r = parse_boolean(value); + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse crash shell switch %s. Ignoring.", value); else arg_crash_shell = r; - } else if (streq(key, "systemd.crash_reboot") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.crash_reboot")) { - r = parse_boolean(value); + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse crash reboot switch %s. Ignoring.", value); else arg_crash_reboot = r; - } else if (streq(key, "systemd.confirm_spawn") && value) { - - arg_confirm_spawn = mfree(arg_confirm_spawn); + } else if (proc_cmdline_key_streq(key, "systemd.confirm_spawn")) { + char *s; - r = parse_confirm_spawn(value, &arg_confirm_spawn); + r = parse_confirm_spawn(value, &s); if (r < 0) log_warning_errno(r, "Failed to parse confirm_spawn switch %s. Ignoring.", value); + else { + free(arg_confirm_spawn); + arg_confirm_spawn = s; + } - } else if (streq(key, "systemd.show_status") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.show_status")) { - r = parse_show_status(value, &arg_show_status); - if (r < 0) - log_warning("Failed to parse show status switch %s. Ignoring.", value); + if (value) { + r = parse_show_status(value, &arg_show_status); + if (r < 0) + log_warning("Failed to parse show status switch %s. Ignoring.", value); + } else + arg_show_status = SHOW_STATUS_YES; - } else if (streq(key, "systemd.default_standard_output") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.default_standard_output")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; r = exec_output_from_string(value); if (r < 0) @@ -398,7 +411,10 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat else arg_default_std_output = r; - } else if (streq(key, "systemd.default_standard_error") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.default_standard_error")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; r = exec_output_from_string(value); if (r < 0) @@ -406,24 +422,42 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat else arg_default_std_error = r; - } else if (streq(key, "systemd.setenv") && value) { + } else if (streq(key, "systemd.setenv")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (env_assignment_is_valid(value)) { char **env; env = strv_env_set(arg_default_environment, value); - if (env) - arg_default_environment = env; - else - log_warning_errno(ENOMEM, "Setting environment variable '%s' failed, ignoring: %m", value); + if (!env) + return log_oom(); + + arg_default_environment = env; } else log_warning("Environment variable name '%s' is not valid. Ignoring.", value); - } else if (streq(key, "systemd.machine_id") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.machine_id")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = set_machine_id(value); + if (r < 0) + log_warning("MachineID '%s' is not valid. Ignoring.", value); - r = set_machine_id(value); - if (r < 0) - log_warning("MachineID '%s' is not valid. Ignoring.", value); + } else if (proc_cmdline_key_streq(key, "systemd.default_timeout_start_sec")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_sec(value, &arg_default_timeout_start_usec); + if (r < 0) + log_warning_errno(r, "Failed to parse default start timeout: %s, ignoring.", value); + + if (arg_default_timeout_start_usec <= 0) + arg_default_timeout_start_usec = USEC_INFINITY; } else if (streq(key, "quiet") && !value) { @@ -445,15 +479,6 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat target = runlevel_to_target(key); if (target) return free_and_strdup(&arg_default_unit, target); - - } else if (streq(key, "systemd.default_timeout_start_sec") && value) { - - r = parse_sec(value, &arg_default_timeout_start_usec); - if (r < 0) - log_warning_errno(r, "Failed to parse default start timeout: %s, ignoring.", value); - - if (arg_default_timeout_start_usec <= 0) - arg_default_timeout_start_usec = USEC_INFINITY; } return 0; @@ -1341,10 +1366,9 @@ static int fixup_environment(void) { * However if TERM was configured through the kernel * command line then leave it alone. */ - r = get_proc_cmdline_key("TERM=", &term); + r = proc_cmdline_get_key("TERM", 0, &term); if (r < 0) return r; - if (r == 0) { term = strdup(default_term_for_tty("/dev/console")); if (!term) @@ -1591,7 +1615,7 @@ int main(int argc, char *argv[]) { } if (arg_system) { - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); } diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c index 68029865a0..e2a43f7195 100644 --- a/src/cryptsetup/cryptsetup-generator.c +++ b/src/cryptsetup/cryptsetup-generator.c @@ -278,27 +278,30 @@ static crypto_device *get_crypto_device(const char *uuid) { } static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { - int r; - crypto_device *d; _cleanup_free_ char *uuid = NULL, *uuid_value = NULL; + crypto_device *d; + int r; - if (streq(key, "luks") && value) { + if (streq(key, "luks")) { - r = parse_boolean(value); + r = value ? parse_boolean(value) : 1; if (r < 0) - log_warning("Failed to parse luks switch %s. Ignoring.", value); + log_warning("Failed to parse luks= kernel command line switch %s. Ignoring.", value); else arg_enabled = r; - } else if (streq(key, "luks.crypttab") && value) { + } else if (streq(key, "luks.crypttab")) { - r = parse_boolean(value); + r = value ? parse_boolean(value) : 1; if (r < 0) - log_warning("Failed to parse luks crypttab switch %s. Ignoring.", value); + log_warning("Failed to parse luks.crypttab= kernel command line switch %s. Ignoring.", value); else arg_read_crypttab = r; - } else if (streq(key, "luks.uuid") && value) { + } else if (streq(key, "luks.uuid")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; d = get_crypto_device(startswith(value, "luks-") ? value+5 : value); if (!d) @@ -306,7 +309,10 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat d->create = arg_whitelist = true; - } else if (streq(key, "luks.options") && value) { + } else if (streq(key, "luks.options")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); if (r == 2) { @@ -314,13 +320,14 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (!d) return log_oom(); - free(d->options); - d->options = uuid_value; - uuid_value = NULL; + free_and_replace(d->options, uuid_value); } else if (free_and_strdup(&arg_default_options, value) < 0) return log_oom(); - } else if (streq(key, "luks.key") && value) { + } else if (streq(key, "luks.key")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); if (r == 2) { @@ -328,13 +335,14 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (!d) return log_oom(); - free(d->keyfile); - d->keyfile = uuid_value; - uuid_value = NULL; + free_and_replace(d->keyfile, uuid_value); } else if (free_and_strdup(&arg_default_keyfile, value) < 0) return log_oom(); - } else if (streq(key, "luks.name") && value) { + } else if (streq(key, "luks.name")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); if (r == 2) { @@ -478,7 +486,7 @@ int main(int argc, char *argv[]) { if (!arg_disks) goto cleanup; - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); if (r < 0) { log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); r = EXIT_FAILURE; diff --git a/src/debug-generator/debug-generator.c b/src/debug-generator/debug-generator.c index 28ebe36b38..1d8bc71e57 100644 --- a/src/debug-generator/debug-generator.c +++ b/src/debug-generator/debug-generator.c @@ -39,56 +39,53 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat assert(key); if (streq(key, "systemd.mask")) { + char *n; - if (!value) - log_error("Missing argument for systemd.mask= kernel command line parameter."); - else { - char *n; + if (proc_cmdline_value_missing(key, value)) + return 0; - r = unit_name_mangle(value, UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_error_errno(r, "Failed to glob unit name: %m"); + r = unit_name_mangle(value, UNIT_NAME_NOGLOB, &n); + if (r < 0) + return log_error_errno(r, "Failed to glob unit name: %m"); - r = strv_consume(&arg_mask, n); - if (r < 0) - return log_oom(); - } + r = strv_consume(&arg_mask, n); + if (r < 0) + return log_oom(); } else if (streq(key, "systemd.wants")) { + char *n; - if (!value) - log_error("Missing argument for systemd.want= kernel command line parameter."); - else { - char *n; + if (proc_cmdline_value_missing(key, value)) + return 0; - r = unit_name_mangle(value, UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_error_errno(r, "Failed to glob unit name: %m"); + r = unit_name_mangle(value, UNIT_NAME_NOGLOB, &n); + if (r < 0) + return log_error_errno(r, "Failed to glob unit name: %m"); - r = strv_consume(&arg_wants, n); - if (r < 0) - return log_oom(); - } + r = strv_consume(&arg_wants, n); + if (r < 0) + return log_oom(); - } else if (streq(key, "systemd.debug-shell")) { + } else if (proc_cmdline_key_streq(key, "systemd.debug_shell")) { if (value) { r = parse_boolean(value); if (r < 0) - log_error("Failed to parse systemd.debug-shell= argument '%s', ignoring.", value); + log_error("Failed to parse systemd.debug_shell= argument '%s', ignoring.", value); else arg_debug_shell = r; } else arg_debug_shell = true; + } else if (streq(key, "systemd.unit")) { - if (!value) - log_error("Missing argument for systemd.unit= kernel command line parameter."); - else { - r = free_and_strdup(&arg_default_unit, value); - if (r < 0) - return log_error_errno(r, "Failed to set default unit %s: %m", value); - } + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_default_unit, value); + if (r < 0) + return log_error_errno(r, "Failed to set default unit %s: %m", value); + } else if (!value) { const char *target; @@ -173,7 +170,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 7c615abbc5..fd7051f21e 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -826,7 +826,7 @@ static int parse_argv(int argc, char *argv[]) { } int main(int argc, char *argv[]) { - _cleanup_free_ char *enabled = NULL; + bool enabled; int r; r = parse_argv(argc, argv); @@ -839,15 +839,14 @@ int main(int argc, char *argv[]) { umask(0022); - r = get_proc_cmdline_key("systemd.firstboot=", &enabled); - if (r < 0) - return r; - if (r > 0) { - r = parse_boolean(enabled); - if (r == 0) - goto finish; - if (r < 0) - log_warning_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring: %s", enabled); + r = proc_cmdline_get_bool("systemd.firstboot", &enabled); + if (r < 0) { + log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring."); + goto finish; + } + if (r > 0 && !enabled) { + r = 0; /* disabled */ + goto finish; } r = process_locale(); diff --git a/src/fsck/fsck.c b/src/fsck/fsck.c index be25c6a2b2..2100681e17 100644 --- a/src/fsck/fsck.c +++ b/src/fsck/fsck.c @@ -99,7 +99,10 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat assert(key); - if (streq(key, "fsck.mode") && value) { + if (streq(key, "fsck.mode")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (streq(value, "auto")) arg_force = arg_skip = false; @@ -110,7 +113,10 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat else log_warning("Invalid fsck.mode= parameter '%s'. Ignoring.", value); - } else if (streq(key, "fsck.repair") && value) { + } else if (streq(key, "fsck.repair")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (streq(value, "preen")) arg_repair = "-a"; @@ -293,7 +299,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index f6a912ae06..84163abbc5 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -612,27 +612,36 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat * instance should take precedence. In the case of multiple rootflags= * or usrflags= the arguments should be concatenated */ - if (STR_IN_SET(key, "fstab", "rd.fstab") && value) { + if (STR_IN_SET(key, "fstab", "rd.fstab")) { - r = parse_boolean(value); + r = value ? parse_boolean(value) : 1; if (r < 0) log_warning("Failed to parse fstab switch %s. Ignoring.", value); else arg_fstab_enabled = r; - } else if (streq(key, "root") && value) { + } else if (streq(key, "root")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (free_and_strdup(&arg_root_what, value) < 0) return log_oom(); - } else if (streq(key, "rootfstype") && value) { + } else if (streq(key, "rootfstype")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (free_and_strdup(&arg_root_fstype, value) < 0) return log_oom(); - } else if (streq(key, "rootflags") && value) { + } else if (streq(key, "rootflags")) { char *o; + if (proc_cmdline_value_missing(key, value)) + return 0; + o = arg_root_options ? strjoin(arg_root_options, ",", value) : strdup(value); @@ -642,19 +651,28 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat free(arg_root_options); arg_root_options = o; - } else if (streq(key, "mount.usr") && value) { + } else if (streq(key, "mount.usr")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (free_and_strdup(&arg_usr_what, value) < 0) return log_oom(); - } else if (streq(key, "mount.usrfstype") && value) { + } else if (streq(key, "mount.usrfstype")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (free_and_strdup(&arg_usr_fstype, value) < 0) return log_oom(); - } else if (streq(key, "mount.usrflags") && value) { + } else if (streq(key, "mount.usrflags")) { char *o; + if (proc_cmdline_value_missing(key, value)) + return 0; + o = arg_usr_options ? strjoin(arg_usr_options, ",", value) : strdup(value); @@ -689,7 +707,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index a60905de94..f94f9f7827 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -912,15 +912,18 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat assert(key); - if (STR_IN_SET(key, "systemd.gpt_auto", "rd.systemd.gpt_auto") && value) { + if (STR_IN_SET(key, "systemd.gpt_auto", "rd.systemd.gpt_auto")) { - r = parse_boolean(value); + r = value ? parse_boolean(value) : 1; if (r < 0) log_warning("Failed to parse gpt-auto switch \"%s\". Ignoring.", value); else arg_enabled = r; - } else if (streq(key, "root") && value) { + } else if (streq(key, "root")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; /* Disable root disk logic if there's a root= value * specified (unless it happens to be "gpt-auto") */ @@ -1018,7 +1021,7 @@ int main(int argc, char *argv[]) { return EXIT_SUCCESS; } - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/hibernate-resume/hibernate-resume-generator.c b/src/hibernate-resume/hibernate-resume-generator.c index 9f59c04b26..a97fe668d5 100644 --- a/src/hibernate-resume/hibernate-resume-generator.c +++ b/src/hibernate-resume/hibernate-resume-generator.c @@ -31,15 +31,22 @@ #include "util.h" static const char *arg_dest = "/tmp"; -static char *arg_resume_dev = NULL; +static char *arg_resume_device = NULL; static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { - if (streq(key, "resume") && value) { - free(arg_resume_dev); - arg_resume_dev = fstab_node_to_udev_node(value); - if (!arg_resume_dev) + if (streq(key, "resume")) { + char *s; + + if (proc_cmdline_value_missing(key, value)) + return 0; + + s = fstab_node_to_udev_node(value); + if (!s) return log_oom(); + + free(arg_resume_device); + arg_resume_device = s; } return 0; @@ -49,10 +56,10 @@ static int process_resume(void) { _cleanup_free_ char *name = NULL, *lnk = NULL; int r; - if (!arg_resume_dev) + if (!arg_resume_device) return 0; - r = unit_name_from_path_instance("systemd-hibernate-resume", arg_resume_dev, ".service", &name); + r = unit_name_from_path_instance("systemd-hibernate-resume", arg_resume_device, ".service", &name); if (r < 0) return log_error_errno(r, "Failed to generate unit name: %m"); @@ -88,12 +95,12 @@ int main(int argc, char *argv[]) { if (!in_initrd()) return EXIT_SUCCESS; - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); r = process_resume(); - free(arg_resume_dev); + free(arg_resume_device); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 5c6941ebd6..6bdb375fb7 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -1532,60 +1532,93 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat assert(s); - if (streq(key, "systemd.journald.forward_to_syslog")) { + if (proc_cmdline_key_streq(key, "systemd.journald.forward_to_syslog")) { + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse forward to syslog switch \"%s\". Ignoring.", value); else s->forward_to_syslog = r; - } else if (streq(key, "systemd.journald.forward_to_kmsg")) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.forward_to_kmsg")) { + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse forward to kmsg switch \"%s\". Ignoring.", value); else s->forward_to_kmsg = r; - } else if (streq(key, "systemd.journald.forward_to_console")) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.forward_to_console")) { + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse forward to console switch \"%s\". Ignoring.", value); else s->forward_to_console = r; - } else if (streq(key, "systemd.journald.forward_to_wall")) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.forward_to_wall")) { + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse forward to wall switch \"%s\". Ignoring.", value); else s->forward_to_wall = r; - } else if (streq(key, "systemd.journald.max_level_console") && value) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_console")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = log_level_from_string(value); if (r < 0) log_warning("Failed to parse max level console value \"%s\". Ignoring.", value); else s->max_level_console = r; - } else if (streq(key, "systemd.journald.max_level_store") && value) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_store")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = log_level_from_string(value); if (r < 0) log_warning("Failed to parse max level store value \"%s\". Ignoring.", value); else s->max_level_store = r; - } else if (streq(key, "systemd.journald.max_level_syslog") && value) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_syslog")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = log_level_from_string(value); if (r < 0) log_warning("Failed to parse max level syslog value \"%s\". Ignoring.", value); else s->max_level_syslog = r; - } else if (streq(key, "systemd.journald.max_level_kmsg") && value) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_kmsg")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = log_level_from_string(value); if (r < 0) log_warning("Failed to parse max level kmsg value \"%s\". Ignoring.", value); else s->max_level_kmsg = r; - } else if (streq(key, "systemd.journald.max_level_wall") && value) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_wall")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = log_level_from_string(value); if (r < 0) log_warning("Failed to parse max level wall value \"%s\". Ignoring.", value); else s->max_level_wall = r; + } else if (startswith(key, "systemd.journald")) log_warning("Unknown journald kernel command line option \"%s\". Ignoring.", key); @@ -1898,7 +1931,10 @@ int server_init(Server *s) { journal_reset_metrics(&s->runtime_storage.metrics); server_parse_config_file(s); - parse_proc_cmdline(parse_proc_cmdline_item, s, true); + + r = proc_cmdline_parse(parse_proc_cmdline_item, s, PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); if (!!s->rate_limit_interval ^ !!s->rate_limit_burst) { log_debug("Setting both rate limit interval and burst from "USEC_FMT",%u to 0,0", diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c index 0901fea8dc..615998a6f6 100644 --- a/src/modules-load/modules-load.c +++ b/src/modules-load/modules-load.c @@ -62,7 +62,11 @@ static int add_modules(const char *p) { static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; - if (streq(key, "modules-load") && value) { + if (proc_cmdline_key_streq(key, "modules_load")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = add_modules(value); if (r < 0) return r; @@ -226,7 +230,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/quotacheck/quotacheck.c b/src/quotacheck/quotacheck.c index 2714cde5c7..2495bcbefd 100644 --- a/src/quotacheck/quotacheck.c +++ b/src/quotacheck/quotacheck.c @@ -34,7 +34,10 @@ static bool arg_force = false; static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { - if (streq(key, "quotacheck.mode") && value) { + if (streq(key, "quotacheck.mode")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (streq(value, "auto")) arg_force = arg_skip = false; @@ -88,7 +91,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/test/test-proc-cmdline.c b/src/test/test-proc-cmdline.c index 4101678f19..12dac8585b 100644 --- a/src/test/test-proc-cmdline.c +++ b/src/test/test-proc-cmdline.c @@ -35,8 +35,8 @@ static int parse_item(const char *key, const char *value, void *data) { return 0; } -static void test_parse_proc_cmdline(void) { - assert_se(parse_proc_cmdline(parse_item, &obj, true) >= 0); +static void test_proc_cmdline_parse(void) { + assert_se(proc_cmdline_parse(parse_item, &obj, true) >= 0); } static void test_runlevel_to_target(void) { @@ -55,11 +55,101 @@ static void test_runlevel_to_target(void) { assert_se(streq_ptr(runlevel_to_target("rd.rescue"), SPECIAL_RESCUE_TARGET)); } +static void test_proc_cmdline_get_key(void) { + _cleanup_free_ char *value = NULL; + + putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm"); + + assert_se(proc_cmdline_get_key("", 0, &value) == -EINVAL); + assert_se(proc_cmdline_get_key("abc", 0, NULL) == 0); + assert_se(proc_cmdline_get_key("abc", 0, &value) == 0 && value == NULL); + assert_se(proc_cmdline_get_key("abc", PROC_CMDLINE_VALUE_OPTIONAL, &value) == 0 && value == NULL); + + assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + assert_se(proc_cmdline_get_key("foo_bar", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + assert_se(proc_cmdline_get_key("foo-bar", 0, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + assert_se(proc_cmdline_get_key("foo-bar", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + assert_se(proc_cmdline_get_key("foo-bar", 0, NULL) == 0); + assert_se(proc_cmdline_get_key("foo-bar", PROC_CMDLINE_VALUE_OPTIONAL, NULL) == -EINVAL); + + assert_se(proc_cmdline_get_key("wuff-piep", 0, &value) > 0 && streq_ptr(value, "tuet")); + value = mfree(value); + assert_se(proc_cmdline_get_key("wuff-piep", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "tuet")); + value = mfree(value); + assert_se(proc_cmdline_get_key("wuff_piep", 0, &value) > 0 && streq_ptr(value, "tuet")); + value = mfree(value); + assert_se(proc_cmdline_get_key("wuff_piep", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "tuet")); + value = mfree(value); + assert_se(proc_cmdline_get_key("wuff_piep", 0, NULL) == 0); + assert_se(proc_cmdline_get_key("wuff_piep", PROC_CMDLINE_VALUE_OPTIONAL, NULL) == -EINVAL); + + assert_se(proc_cmdline_get_key("zumm", 0, &value) == 0 && value == NULL); + assert_se(proc_cmdline_get_key("zumm", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && value == NULL); + assert_se(proc_cmdline_get_key("zumm", 0, NULL) > 0); +} + +static void test_proc_cmdline_get_bool(void) { + bool value = false; + + putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar bar-waldo=1 x_y-z=0 quux=miep"); + + 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); +} + +static void test_proc_cmdline_key_streq(void) { + + assert_se(proc_cmdline_key_streq("", "")); + assert_se(proc_cmdline_key_streq("a", "a")); + assert_se(!proc_cmdline_key_streq("", "a")); + assert_se(!proc_cmdline_key_streq("a", "")); + assert_se(proc_cmdline_key_streq("a", "a")); + assert_se(!proc_cmdline_key_streq("a", "b")); + assert_se(proc_cmdline_key_streq("x-y-z", "x-y-z")); + assert_se(proc_cmdline_key_streq("x-y-z", "x_y_z")); + assert_se(proc_cmdline_key_streq("x-y-z", "x-y_z")); + assert_se(proc_cmdline_key_streq("x-y-z", "x_y-z")); + assert_se(proc_cmdline_key_streq("x_y-z", "x-y_z")); + assert_se(!proc_cmdline_key_streq("x_y-z", "x-z_z")); +} + +static void test_proc_cmdline_key_startswith(void) { + + assert_se(proc_cmdline_key_startswith("", "")); + assert_se(proc_cmdline_key_startswith("x", "")); + assert_se(!proc_cmdline_key_startswith("", "x")); + assert_se(proc_cmdline_key_startswith("x", "x")); + assert_se(!proc_cmdline_key_startswith("x", "y")); + assert_se(!proc_cmdline_key_startswith("foo-bar", "quux")); + assert_se(proc_cmdline_key_startswith("foo-bar", "foo")); + assert_se(proc_cmdline_key_startswith("foo-bar", "foo-bar")); + assert_se(proc_cmdline_key_startswith("foo-bar", "foo_bar")); + assert_se(proc_cmdline_key_startswith("foo-bar", "foo_")); + assert_se(!proc_cmdline_key_startswith("foo-bar", "foo_xx")); +} + int main(void) { log_parse_environment(); log_open(); - test_parse_proc_cmdline(); + test_proc_cmdline_parse(); + test_proc_cmdline_key_streq(); + test_proc_cmdline_key_startswith(); + test_proc_cmdline_get_key(); + test_proc_cmdline_get_bool(); test_runlevel_to_target(); return 0; diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index 1dca375279..3af87f1388 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -192,14 +192,9 @@ static int load_link(link_config_ctx *ctx, const char *filename) { } static bool enable_name_policy(void) { - _cleanup_free_ char *value = NULL; - int r; - - r = get_proc_cmdline_key("net.ifnames=", &value); - if (r > 0 && streq(value, "0")) - return false; + bool b; - return true; + return proc_cmdline_get_bool("net.ifnames", &b) <= 0 || b; } int link_config_load(link_config_ctx *ctx) { diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 895c6f271b..dd23054b0d 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -1357,10 +1357,10 @@ static int listen_fds(int *rctrl, int *rnetlink) { /* * read the kernel command line, in case we need to get into debug mode - * udev.log-priority= syslog priority - * udev.children-max= events are fully serialized if set to 1 - * udev.exec-delay= delay execution of every executed program - * udev.event-timeout= seconds to wait before terminating an event + * udev.log_priority= syslog priority + * udev.children_max= events are fully serialized if set to 1 + * udev.exec_delay= delay execution of every executed program + * udev.event_timeout= seconds to wait before terminating an event */ static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r = 0; @@ -1370,25 +1370,46 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (!value) return 0; - if (streq(key, "udev.log-priority") && value) { + if (proc_cmdline_key_streq(key, "udev.log_priority")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = util_log_priority(value); if (r >= 0) log_set_max_level(r); - } else if (streq(key, "udev.event-timeout") && value) { + + } else if (proc_cmdline_key_streq(key, "udev.event_timeout")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = safe_atou64(value, &arg_event_timeout_usec); if (r >= 0) { arg_event_timeout_usec *= USEC_PER_SEC; arg_event_timeout_warn_usec = (arg_event_timeout_usec / 3) ? : 1; } - } else if (streq(key, "udev.children-max") && value) + + } else if (proc_cmdline_key_streq(key, "udev.children_max")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = safe_atou(value, &arg_children_max); - else if (streq(key, "udev.exec-delay") && value) + + } else if (proc_cmdline_key_streq(key, "udev.exec_delay")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = safe_atoi(value, &arg_exec_delay); - else if (startswith(key, "udev.")) + + } else if (startswith(key, "udev.")) log_warning("Unknown udev kernel command line option \"%s\"", key); if (r < 0) log_warning_errno(r, "Failed to parse \"%s=%s\", ignoring: %m", key, value); + return 0; } @@ -1649,7 +1670,7 @@ int main(int argc, char *argv[]) { if (r <= 0) goto exit; - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); if (r < 0) log_warning_errno(r, "failed to parse kernel command line, ignoring: %m"); -- cgit v1.2.3-54-g00ecf From 91214a37ef4eb8042d2598aa89bae52b410d11a7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 13 Dec 2016 12:45:19 +0100 Subject: fstab-generator: add support for volatile boots This adds support for a new kernel command line option "systemd.volatile=" that provides the same functionality that systemd-nspawn's --volatile= switch provides, but for host systems (i.e. systems booting with a kernel). It takes the same parameter and has the same effect. In order to implement systemd.volatile=yes a new service systemd-volatile-root.service is introduced that only runs in the initrd and rearranges the root directory as needed to become a tmpfs instance. Note that systemd.volatile=state is implemented different: it simply generates a var.mount unit file that is part of the normal boot and has no effect on the initrd execution. The way this is implemented ensures that other explicit configuration for /var can always override the effect of these options. Specifically, the var.mount unit is generated in the "late" generator directory, so that it only is in effect if nothing else overrides it. --- .gitignore | 1 + Makefile-man.am | 7 ++ Makefile.am | 10 +++ man/kernel-command-line.xml | 23 +++++ man/systemd-fstab-generator.xml | 46 +++++++++- man/systemd-nspawn.xml | 15 ++-- man/systemd-volatile-root.service.xml | 79 +++++++++++++++++ src/fstab-generator/fstab-generator.c | 85 +++++++++++++++--- src/shared/volatile-util.c | 27 ++++++ src/shared/volatile-util.h | 2 + src/volatile-root/Makefile | 1 + src/volatile-root/volatile-root.c | 157 +++++++++++++++++++++++++++++++++ units/.gitignore | 1 + units/systemd-volatile-root.service.in | 21 +++++ 14 files changed, 456 insertions(+), 19 deletions(-) create mode 100644 man/systemd-volatile-root.service.xml create mode 120000 src/volatile-root/Makefile create mode 100644 src/volatile-root/volatile-root.c create mode 100644 units/systemd-volatile-root.service.in (limited to 'man/kernel-command-line.xml') diff --git a/.gitignore b/.gitignore index ec4b7bd672..f246d3e6d5 100644 --- a/.gitignore +++ b/.gitignore @@ -126,6 +126,7 @@ /systemd-update-utmp /systemd-user-sessions /systemd-vconsole-setup +/systemd-volatile-root /tags /test-acd /test-acl-util diff --git a/Makefile-man.am b/Makefile-man.am index 5e6eee5e32..27660ef1c2 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -142,6 +142,7 @@ MANPAGES += \ man/systemd-tty-ask-password-agent.1 \ man/systemd-udevd.service.8 \ man/systemd-update-done.service.8 \ + man/systemd-volatile-root.service.8 \ man/systemd.1 \ man/systemd.automount.5 \ man/systemd.device.5 \ @@ -482,6 +483,7 @@ MANPAGES_ALIAS += \ man/systemd-udevd.8 \ man/systemd-update-done.8 \ man/systemd-user.conf.5 \ + man/systemd-volatile-root.8 \ man/udev_device_get_action.3 \ man/udev_device_get_devlinks_list_entry.3 \ man/udev_device_get_devnode.3 \ @@ -837,6 +839,7 @@ man/systemd-udevd-kernel.socket.8: man/systemd-udevd.service.8 man/systemd-udevd.8: man/systemd-udevd.service.8 man/systemd-update-done.8: man/systemd-update-done.service.8 man/systemd-user.conf.5: man/systemd-system.conf.5 +man/systemd-volatile-root.8: man/systemd-volatile-root.service.8 man/udev_device_get_action.3: man/udev_device_get_syspath.3 man/udev_device_get_devlinks_list_entry.3: man/udev_device_has_tag.3 man/udev_device_get_devnode.3: man/udev_device_get_syspath.3 @@ -1790,6 +1793,9 @@ man/systemd-update-done.html: man/systemd-update-done.service.html man/systemd-user.conf.html: man/systemd-system.conf.html $(html-alias) +man/systemd-volatile-root.html: man/systemd-volatile-root.service.html + $(html-alias) + man/udev_device_get_action.html: man/udev_device_get_syspath.html $(html-alias) @@ -2804,6 +2810,7 @@ EXTRA_DIST += \ man/systemd-update-utmp.service.xml \ man/systemd-user-sessions.service.xml \ man/systemd-vconsole-setup.service.xml \ + man/systemd-volatile-root.service.xml \ man/systemd.automount.xml \ man/systemd.device.xml \ man/systemd.exec.xml \ diff --git a/Makefile.am b/Makefile.am index 56b8aa3fe8..92a3680461 100644 --- a/Makefile.am +++ b/Makefile.am @@ -397,6 +397,7 @@ rootlibexec_PROGRAMS = \ systemd-initctl \ systemd-shutdown \ systemd-remount-fs \ + systemd-volatile-root \ systemd-reply-password \ systemd-fsck \ systemd-ac-power \ @@ -538,6 +539,7 @@ nodist_systemunit_DATA = \ units/system-update-cleanup.service \ units/systemd-initctl.service \ units/systemd-remount-fs.service \ + units/systemd-volatile-root.service \ units/systemd-ask-password-wall.service \ units/systemd-ask-password-console.service \ units/systemd-sysctl.service \ @@ -602,6 +604,7 @@ EXTRA_DIST += \ units/system-update-cleanup.service.in \ units/systemd-initctl.service.in \ units/systemd-remount-fs.service.in \ + units/systemd-volatile-root.service.in \ units/systemd-update-utmp.service.in \ units/systemd-update-utmp-runlevel.service.in \ units/systemd-ask-password-wall.service.in \ @@ -3067,6 +3070,13 @@ systemd_remount_fs_SOURCES = \ systemd_remount_fs_LDADD = \ libsystemd-shared.la +# ------------------------------------------------------------------------------ +systemd_volatile_root_SOURCES = \ + src/volatile-root/volatile-root.c + +systemd_volatile_root_LDADD = \ + libsystemd-shared.la + # ------------------------------------------------------------------------------ systemd_cgroups_agent_SOURCES = \ src/cgroups-agent/cgroups-agent.c diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 78e45e66a9..7e1d408ded 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -124,6 +124,28 @@ + + systemd.volatile= + + This parameter controls whether the system shall boot up in volatile mode. Takes a boolean argument, or + the special value state. If false (the default), normal boot mode is selected, the root + directory and /var are mounted as specified on the kernel command line or + /etc/fstab, or otherwise configured. If true, full state-less boot mode is selected. In + this case the root directory is mounted as volatile memory file system (tmpfs), and only + /usr is mounted from the file system configured as root device, in read-only mode. This + enables fully state-less boots were the vendor-supplied OS is used as shipped, with only default + configuration and no stored state in effect, as /etc and /var (as + well as all other resources shipped in the root file system) are reset at boot and lost on shutdown. If this + setting is set to state the root file system is mounted as usual, however + /var is mounted as a volatile memory file system (tmpfs), so that the + system boots up with the normal configuration applied, but all state reset at boot and lost at shutdown. For details, + see + systemd-volatile-root.service8 + and + systemd-fstab-generator8. + + + quiet @@ -382,6 +404,7 @@ systemd-cryptsetup-generator8, systemd-fstab-generator8, systemd-gpt-auto-generator8, + systemd-volatile-root.service8, systemd-modules-load.service8, systemd-backlight@.service8, systemd-rfkill.service8, diff --git a/man/systemd-fstab-generator.xml b/man/systemd-fstab-generator.xml index a971cb3675..5f37e9193e 100644 --- a/man/systemd-fstab-generator.xml +++ b/man/systemd-fstab-generator.xml @@ -89,12 +89,13 @@ Takes a boolean argument. Defaults to yes. If no, causes the - generator to ignore any mounts or swaps configured in + generator to ignore any mounts or swap devices configured in /etc/fstab. rd.fstab= - is honored only by initial RAM disk (initrd) while + is honored only by the initial RAM disk (initrd) while fstab= is honored by both the main system and the initrd. + root= @@ -102,6 +103,7 @@ initrd. root= is honored by the initrd. + rootfstype= @@ -109,6 +111,7 @@ passed to the mount command. rootfstype= is honored by the initrd. + rootflags= @@ -116,6 +119,7 @@ use. rootflags= is honored by the initrd. + mount.usr= @@ -133,6 +137,7 @@ mount.usr= is honored by the initrd. + mount.usrfstype= @@ -150,6 +155,7 @@ mount.usrfstype= is honored by the initrd. + mount.usrflags= @@ -166,6 +172,39 @@ mount.usrflags= is honored by the initrd. + + + systemd.volatile= + + Controls whether the system shall boot up in volatile mode. Takes a boolean argument or the + special value . + + If false (the default), this generator makes no changes to the mount tree and the system is booted up in + normal mode. + + If true the generator ensures + systemd-volatile-root.service8 + is run as part of the initial RAM disk ("initrd"). This service changes the mount table before transitioning to + the host system, so that a volatile memory file system (tmpfs) is used as root directory, + with only /usr mounted into it from the configured root file system, in read-only + mode. This way the system operates in fully stateless mode, with all configuration and state reset at boot and + lost at shutdown, as /etc and /var will be served from the (initially + unpopulated) volatile memory file system. + + If set to the generator will leave the root + directory mount point unaltered, however will mount a tmpfs file system to + /var. In this mode the normal system configuration (i.e the contents of + /etc) is in effect (and may be modified during system runtime), however the system state + (i.e. the contents of /var) is reset at boot and lost at shutdown. + + Note that in none of these modes the root directory, /etc, /var + or any other resources stored in the root file system are physically removed. It's thus safe to boot a system + that is normally operated in non-volatile mode temporarily into volatile mode, without losing data. + + Note that enabling this setting will only work correctly on operating systems that can boot up with only + /usr mounted, and are able to automatically populate /etc, and also + /var in case of systemd.volatile=yes. + @@ -176,7 +215,8 @@ fstab5, systemd.mount5, systemd.swap5, - systemd-cryptsetup-generator8 + systemd-cryptsetup-generator8, + kernel-command-line7 diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 2bc81ea1aa..f6b3f57fc7 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -939,12 +939,15 @@ (the default), the whole OS tree is made available writable. - Note that setting this to or - will only work correctly with - operating systems in the container that can boot up with only - /usr mounted, and are able to populate - /var automatically, as - needed. + This option provides similar functionality for containers as the systemd.volatile= + kernel command line switch provides for host systems. See + kernel-command-line7 for + details. + + Note that enabling this setting will only work correctly with operating systems in the container that can + boot up with only /usr mounted, and are able to automatically populate + /var, and also /etc in case of + --volatile=yes. diff --git a/man/systemd-volatile-root.service.xml b/man/systemd-volatile-root.service.xml new file mode 100644 index 0000000000..b90a3261fa --- /dev/null +++ b/man/systemd-volatile-root.service.xml @@ -0,0 +1,79 @@ + + + + + + + + systemd-volatile-root.service + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-volatile-root.service + 8 + + + + systemd-volatile-root.service + systemd-volatile-root + Make the root file system volatile + + + + systemd-volatile-root.service + /usr/lib/systemd/systemd-volatile-root + + + + Description + + systemd-volatile-root.service is a service that replaces the root directory with a + volatile memory file system (tmpfs), mounting the original (non-volatile) + /usr inside it read-only. This way, vendor data from /usr is available as + usual, but all configuration data in /etc, all state data in /var and all + other resources stored directly under the root directory are reset on boot and lost at shutdown, enabling fully + stateless systems. + + This service is only enabled if full volatile mode is selected, for example by specifying + systemd.volatile=yes on the kernel command line. This service runs only in the initial RAM disk + ("initrd"), before the system transitions to the host's root directory. Note that this service is not used if + systemd.volatile=state is used, as in that mode the root directory is non-volatile. + + + + See Also + + systemd1, + systemd-fstab-generator8, + kernel-command-line7 + + + + diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index 84163abbc5..f58aa27df2 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -42,8 +42,10 @@ #include "unit-name.h" #include "util.h" #include "virt.h" +#include "volatile-util.h" static const char *arg_dest = "/tmp"; +static const char *arg_dest_late = "/tmp"; static bool arg_fstab_enabled = true; static char *arg_root_what = NULL; static char *arg_root_fstype = NULL; @@ -52,6 +54,7 @@ static int arg_root_rw = -1; static char *arg_usr_what = NULL; static char *arg_usr_fstype = NULL; static char *arg_usr_options = NULL; +static VolatileMode arg_volatile_mode = _VOLATILE_MODE_INVALID; static int add_swap( const char *what, @@ -235,6 +238,7 @@ static int write_requires_mounts_for(FILE *f, const char *opts) { } static int add_mount( + const char *dest, const char *what, const char *where, const char *fstype, @@ -286,7 +290,7 @@ static int add_mount( if (r < 0) return log_error_errno(r, "Failed to generate unit name: %m"); - unit = strjoin(arg_dest, "/", name); + unit = strjoin(dest, "/", name); if (!unit) return log_oom(); @@ -318,7 +322,7 @@ static int add_mount( } if (passno != 0) { - r = generator_write_fsck_deps(f, arg_dest, what, where, fstype); + r = generator_write_fsck_deps(f, dest, what, where, fstype); if (r < 0) return r; } @@ -334,7 +338,7 @@ static int add_mount( if (!isempty(fstype) && !streq(fstype, "auto")) fprintf(f, "Type=%s\n", fstype); - r = generator_write_timeouts(arg_dest, what, where, opts, &filtered); + r = generator_write_timeouts(dest, what, where, opts, &filtered); if (r < 0) return r; @@ -350,7 +354,7 @@ static int add_mount( return log_error_errno(r, "Failed to write unit file %s: %m", unit); if (!noauto && !automount) { - lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", name); + lnk = strjoin(dest, "/", post, nofail ? ".wants/" : ".requires/", name); if (!lnk) return log_oom(); @@ -364,7 +368,7 @@ static int add_mount( if (r < 0) return log_error_errno(r, "Failed to generate unit name: %m"); - automount_unit = strjoin(arg_dest, "/", automount_name); + automount_unit = strjoin(dest, "/", automount_name); if (!automount_unit) return log_oom(); @@ -406,7 +410,7 @@ static int add_mount( return log_error_errno(r, "Failed to write unit file %s: %m", automount_unit); free(lnk); - lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", automount_name); + lnk = strjoin(dest, "/", post, nofail ? ".wants/" : ".requires/", automount_name); if (!lnk) return log_oom(); @@ -479,7 +483,8 @@ static int parse_fstab(bool initrd) { else post = SPECIAL_LOCAL_FS_TARGET; - k = add_mount(what, + k = add_mount(arg_dest, + what, where, me->mnt_type, me->mnt_opts, @@ -540,7 +545,8 @@ static int add_sysroot_mount(void) { return r; } - return add_mount(what, + return add_mount(arg_dest, + what, "/sysroot", arg_root_fstype, opts, @@ -593,7 +599,8 @@ static int add_sysroot_usr_mount(void) { opts = arg_usr_options; log_debug("Found entry what=%s where=/sysroot/usr type=%s", what, strna(arg_usr_fstype)); - return add_mount(what, + return add_mount(arg_dest, + what, "/sysroot/usr", arg_usr_fstype, opts, @@ -605,6 +612,46 @@ static int add_sysroot_usr_mount(void) { "/proc/cmdline"); } +static int add_volatile_root(void) { + const char *from, *to; + + if (arg_volatile_mode != VOLATILE_YES) + return 0; + + /* Let's add in systemd-remount-volatile.service which will remount the root device to tmpfs if this is + * requested, leaving only /usr from the root mount inside. */ + + from = strjoina(SYSTEM_DATA_UNIT_PATH "/systemd-volatile-root.service"); + to = strjoina(arg_dest, "/" SPECIAL_INITRD_ROOT_FS_TARGET, ".requires/systemd-volatile-root.service"); + + (void) mkdir_parents(to, 0755); + + if (symlink(from, to) < 0) + return log_error_errno(errno, "Failed to hook in volatile remount service: %m"); + + return 0; +} + +static int add_volatile_var(void) { + + if (arg_volatile_mode != VOLATILE_STATE) + return 0; + + /* If requested, mount /var as tmpfs, but do so only if there's nothing else defined for this. */ + + return add_mount(arg_dest_late, + "tmpfs", + "/var", + "tmpfs", + "mode=0755", + 0, + false, + false, + false, + SPECIAL_LOCAL_FS_TARGET, + "/proc/cmdline"); +} + static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; @@ -686,6 +733,18 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat arg_root_rw = true; else if (streq(key, "ro") && !value) arg_root_rw = false; + else if (streq(key, "systemd.volatile")) { + VolatileMode m; + + if (value) { + m = volatile_mode_from_string(value); + if (m < 0) + log_warning("Failed to parse systemd.volatile= argument: %s", value); + else + arg_volatile_mode = m; + } else + arg_volatile_mode = VOLATILE_YES; + } return 0; } @@ -700,6 +759,8 @@ int main(int argc, char *argv[]) { if (argc > 1) arg_dest = argv[1]; + if (argc > 3) + arg_dest_late = argv[3]; log_set_target(LOG_TARGET_SAFE); log_parse_environment(); @@ -720,8 +781,12 @@ int main(int argc, char *argv[]) { k = add_sysroot_usr_mount(); if (k < 0) r = k; + + k = add_volatile_root(); + if (k < 0) + r = k; } else - r = 0; + r = add_volatile_var(); /* Honour /etc/fstab only when that's enabled */ if (arg_fstab_enabled) { diff --git a/src/shared/volatile-util.c b/src/shared/volatile-util.c index 1329b51f4e..e7e9721411 100644 --- a/src/shared/volatile-util.c +++ b/src/shared/volatile-util.c @@ -17,8 +17,10 @@ along with systemd; If not, see . ***/ +#include "alloc-util.h" #include "macro.h" #include "parse-util.h" +#include "proc-cmdline.h" #include "string-util.h" #include "volatile-util.h" @@ -39,3 +41,28 @@ VolatileMode volatile_mode_from_string(const char *s) { return _VOLATILE_MODE_INVALID; } + +int query_volatile_mode(VolatileMode *ret) { + _cleanup_free_ char *mode = NULL; + VolatileMode m = VOLATILE_NO; + int r; + + r = proc_cmdline_get_key("systemd.volatile", PROC_CMDLINE_VALUE_OPTIONAL, &mode); + if (r < 0) + return r; + if (r == 0) + goto finish; + + if (mode) { + m = volatile_mode_from_string(mode); + if (m < 0) + return -EINVAL; + } else + m = VOLATILE_YES; + + r = 1; + +finish: + *ret = m; + return r; +} diff --git a/src/shared/volatile-util.h b/src/shared/volatile-util.h index d012940c76..17930ba6ae 100644 --- a/src/shared/volatile-util.h +++ b/src/shared/volatile-util.h @@ -28,3 +28,5 @@ typedef enum VolatileMode { } VolatileMode; VolatileMode volatile_mode_from_string(const char *s); + +int query_volatile_mode(VolatileMode *ret); diff --git a/src/volatile-root/Makefile b/src/volatile-root/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/volatile-root/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/volatile-root/volatile-root.c b/src/volatile-root/volatile-root.c new file mode 100644 index 0000000000..3c0b6fa1de --- /dev/null +++ b/src/volatile-root/volatile-root.c @@ -0,0 +1,157 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include "alloc-util.h" +#include "fs-util.h" +#include "mkdir.h" +#include "mount-util.h" +#include "stat-util.h" +#include "volatile-util.h" +#include "string-util.h" +#include "path-util.h" + +static int make_volatile(const char *path) { + _cleanup_free_ char *old_usr = NULL; + int r; + + r = path_is_mount_point(path, NULL, AT_SYMLINK_FOLLOW); + if (r < 0) + return log_error_errno(r, "Couldn't determine whether %s is a mount point: %m", path); + if (r == 0) { + log_error("%s is not a mount point.", path); + return -EINVAL; + } + + r = path_is_temporary_fs(path); + if (r < 0) + return log_error_errno(r, "Couldn't determine whether %s is a temporary file system: %m", path); + if (r > 0) { + log_info("%s already is a temporary file system.", path); + return 0; + } + + r = chase_symlinks("/usr", path, CHASE_PREFIX_ROOT, &old_usr); + if (r < 0) + return log_error_errno(r, "/usr not available in old root: %m"); + + r = mkdir_p("/run/systemd/volatile-sysroot", 0700); + if (r < 0) + return log_error_errno(r, "Couldn't generate volatile sysroot directory: %m"); + + r = mount_verbose(LOG_ERR, "tmpfs", "/run/systemd/volatile-sysroot", "tmpfs", MS_STRICTATIME, "mode=755"); + if (r < 0) + goto finish_rmdir; + + if (mkdir("/run/systemd/volatile-sysroot/usr", 0755) < 0) { + r = -errno; + goto finish_umount; + } + + r = mount_verbose(LOG_ERR, old_usr, "/run/systemd/volatile-sysroot/usr", NULL, MS_BIND|MS_REC, NULL); + if (r < 0) + goto finish_umount; + + r = bind_remount_recursive("/run/systemd/volatile-sysroot/usr", true, NULL); + if (r < 0) + goto finish_umount; + + r = umount_recursive(path, 0); + if (r < 0) { + log_error_errno(r, "Failed to unmount %s: %m", path); + goto finish_umount; + } + + if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) + log_warning_errno(errno, "Failed to remount %s MS_SLAVE|MS_REC: %m", path); + + r = mount_verbose(LOG_ERR, "/run/systemd/volatile-sysroot", path, NULL, MS_MOVE, NULL); + +finish_umount: + (void) umount_recursive("/run/systemd/volatile-sysroot", 0); + +finish_rmdir: + (void) rmdir("/run/systemd/volatile-sysroot"); + + return r; +} + +int main(int argc, char *argv[]) { + VolatileMode m = _VOLATILE_MODE_INVALID; + const char *path; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + if (argc > 3) { + log_error("Too many arguments. Expected directory and mode."); + r = -EINVAL; + goto finish; + } + + r = query_volatile_mode(&m); + if (r < 0) { + log_error_errno(r, "Failed to determine volatile mode from kernel command line."); + goto finish; + } + if (r == 0 && argc >= 2) { + /* The kernel command line always wins. However if nothing was set there, the argument passed here wins instead. */ + m = volatile_mode_from_string(argv[1]); + if (m < 0) { + log_error("Couldn't parse volatile mode: %s", argv[1]); + r = -EINVAL; + goto finish; + } + } + + if (argc < 3) + path = "/sysroot"; + else { + path = argv[2]; + + if (isempty(path)) { + log_error("Directory name cannot be empty."); + r = -EINVAL; + goto finish; + } + if (!path_is_absolute(path)) { + log_error("Directory must be specified as absolute path."); + r = -EINVAL; + goto finish; + } + if (path_equal(path, "/")) { + log_error("Directory cannot be the root directory."); + r = -EINVAL; + goto finish; + } + } + + if (m != VOLATILE_YES) { + r = 0; + goto finish; + } + + r = make_volatile(path); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/units/.gitignore b/units/.gitignore index 8fdb6e9ab5..4398a59f91 100644 --- a/units/.gitignore +++ b/units/.gitignore @@ -75,5 +75,6 @@ /systemd-update-utmp.service /systemd-user-sessions.service /systemd-vconsole-setup.service +/systemd-volatile-root.service /tmp.mount /user@.service diff --git a/units/systemd-volatile-root.service.in b/units/systemd-volatile-root.service.in new file mode 100644 index 0000000000..cc4e604e4c --- /dev/null +++ b/units/systemd-volatile-root.service.in @@ -0,0 +1,21 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Enforce Volatile Root File Systems +Documentation=man:systemd-volatile-root.service(8) +DefaultDependencies=no +Conflicts=shutdown.target +After=sysroot.mount +Before=initrd-root-fs.target shutdown.target +Conflicts=shutdown.target +AssertPathExists=/etc/initrd-release + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=@rootlibexecdir@/systemd-volatile-root yes /sysroot -- cgit v1.2.3-54-g00ecf From 2f3dfc6fb43e13f3999d10c509105d46f3cf5b93 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Dec 2016 12:57:44 +0100 Subject: verity: add support for setting up verity-protected root disks in the initrd This adds a generator and a small service that will look for "roothash=" on the kernel command line and use it for setting up a very partition for the root device. This provides similar functionality to nspawn's existing --roothash= switch. --- .gitignore | 2 + Makefile-man.am | 13 +- Makefile.am | 23 ++- man/kernel-command-line.xml | 14 ++ man/systemd-veritysetup-generator.xml | 122 ++++++++++++++ man/systemd-veritysetup@.service.xml | 75 +++++++++ src/fstab-generator/fstab-generator.c | 29 ++++ src/gpt-auto-generator/gpt-auto-generator.c | 9 + src/veritysetup/Makefile | 1 + src/veritysetup/veritysetup-generator.c | 253 ++++++++++++++++++++++++++++ src/veritysetup/veritysetup.c | 154 +++++++++++++++++ 11 files changed, 691 insertions(+), 4 deletions(-) create mode 100644 man/systemd-veritysetup-generator.xml create mode 100644 man/systemd-veritysetup@.service.xml create mode 120000 src/veritysetup/Makefile create mode 100644 src/veritysetup/veritysetup-generator.c create mode 100644 src/veritysetup/veritysetup.c (limited to 'man/kernel-command-line.xml') diff --git a/.gitignore b/.gitignore index f246d3e6d5..fe7859c265 100644 --- a/.gitignore +++ b/.gitignore @@ -126,6 +126,8 @@ /systemd-update-utmp /systemd-user-sessions /systemd-vconsole-setup +/systemd-veritysetup +/systemd-veritysetup-generator /systemd-volatile-root /tags /test-acd diff --git a/Makefile-man.am b/Makefile-man.am index 27660ef1c2..c47bedd0df 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -2276,13 +2276,20 @@ if HAVE_LIBCRYPTSETUP MANPAGES += \ man/crypttab.5 \ man/systemd-cryptsetup-generator.8 \ - man/systemd-cryptsetup@.service.8 + man/systemd-cryptsetup@.service.8 \ + man/systemd-veritysetup-generator.8 \ + man/systemd-veritysetup@.service.8 MANPAGES_ALIAS += \ - man/systemd-cryptsetup.8 + man/systemd-cryptsetup.8 \ + man/systemd-veritysetup.8 man/systemd-cryptsetup.8: man/systemd-cryptsetup@.service.8 +man/systemd-veritysetup.8: man/systemd-veritysetup@.service.8 man/systemd-cryptsetup.html: man/systemd-cryptsetup@.service.html $(html-alias) +man/systemd-veritysetup.html: man/systemd-veritysetup@.service.html + $(html-alias) + endif if HAVE_MICROHTTPD @@ -2810,6 +2817,8 @@ EXTRA_DIST += \ man/systemd-update-utmp.service.xml \ man/systemd-user-sessions.service.xml \ man/systemd-vconsole-setup.service.xml \ + man/systemd-veritysetup-generator.xml \ + man/systemd-veritysetup@.service.xml \ man/systemd-volatile-root.service.xml \ man/systemd.automount.xml \ man/systemd.device.xml \ diff --git a/Makefile.am b/Makefile.am index 92a3680461..c87c547e05 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4833,10 +4833,12 @@ EXTRA_DIST += \ # ------------------------------------------------------------------------------ if HAVE_LIBCRYPTSETUP rootlibexec_PROGRAMS += \ - systemd-cryptsetup + systemd-cryptsetup \ + systemd-veritysetup systemgenerator_PROGRAMS += \ - systemd-cryptsetup-generator + systemd-cryptsetup-generator \ + systemd-veritysetup-generator dist_systemunit_DATA += \ units/cryptsetup.target \ @@ -4859,6 +4861,23 @@ systemd_cryptsetup_generator_SOURCES = \ systemd_cryptsetup_generator_LDADD = \ libsystemd-shared.la +systemd_veritysetup_SOURCES = \ + src/veritysetup/veritysetup.c + +systemd_veritysetup_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBCRYPTSETUP_CFLAGS) + +systemd_veritysetup_LDADD = \ + libsystemd-shared.la \ + $(LIBCRYPTSETUP_LIBS) + +systemd_veritysetup_generator_SOURCES = \ + src/veritysetup/veritysetup-generator.c + +systemd_veritysetup_generator_LDADD = \ + libsystemd-shared.la + SYSINIT_TARGET_WANTS += \ cryptsetup.target diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 7e1d408ded..415b8d3cf9 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -333,6 +333,19 @@ + + roothash= + systemd.verity= + rd.systemd.verity= + systemd.verity_root_data= + systemd.verity_root_hash= + + Configures the integrity protection root hash for the root file system, and other related + parameters. For details, see + systemd-veritysetup-generator8. + + + systemd.gpt_auto= rd.systemd.gpt_auto= @@ -402,6 +415,7 @@ systemd-udevd.service8, plymouth8, systemd-cryptsetup-generator8, + systemd-veritysetup-generator8, systemd-fstab-generator8, systemd-gpt-auto-generator8, systemd-volatile-root.service8, diff --git a/man/systemd-veritysetup-generator.xml b/man/systemd-veritysetup-generator.xml new file mode 100644 index 0000000000..87d66e9ee5 --- /dev/null +++ b/man/systemd-veritysetup-generator.xml @@ -0,0 +1,122 @@ + + + + + + + + systemd-veritysetup-generator + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-veritysetup-generator + 8 + + + + systemd-veritysetup-generator + Unit generator for integrity protected block devices + + + + /usr/lib/systemd/system-generators/systemd-veritysetup-generator + + + + Description + + systemd-veritysetup-generator is a generator that translates kernel command line options + configuring integrity protected block devices (verity) into native systemd units early at boot and when + configuration of the system manager is reloaded. This will create + systemd-veritysetup@.service8 + units as necessary. + + Currently, only a single verity device may be se up with this generator, backing the root file system of the + OS. + + systemd-veritysetup-generator implements + systemd.generator7. + + + + Kernel Command Line + + systemd-veritysetup-generator + understands the following kernel command line parameters: + + + + systemd.verity= + rd.systemd.verity= + + Takes a boolean argument. Defaults to yes. If no, + disables the generator entirely. rd.systemd.verity= is honored only by the initial RAM disk + (initrd) while systemd.verity= is honored by both the host system and the + initrd. + + + + roothash= + + Takes a root hash value for the root file system. Expects a hash value formatted in hexadecimal + characters, of the appropriate length (i.e. most likely 256 bit/64 characters, or longer). If not specified via + systemd.verity_root_data= and systemd.verity_root_hash=, the hash and + data devices to use are automatically derived from the specified hash value. Specifically, the data partition + device is looked for under a GPT partition UUID derived from the first 128bit of the root hash, the hash + partition device is looked for under a GPT partition UUID derived from the last 128bit of the root hash. Hence + it is usually sufficient to specify the root hash to boot from an integrity protected root file system, as + device paths are automatically determined from it — as long as the partition table is properly set up. + + + + + systemd.verity_root_data= + systemd.verity_root_hash= + + These two settings take block device paths as arguments, and may be use to explicitly configure + the data partition and hash partition to use for setting up the integrity protection for the root file + system. If not specified, these paths are automatically derived from the roothash= argument + (see above). + + + + + + + See Also + + systemd1, + systemd-veritysetup@.service8, + veritysetup8, + systemd-fstab-generator8 + + + + diff --git a/man/systemd-veritysetup@.service.xml b/man/systemd-veritysetup@.service.xml new file mode 100644 index 0000000000..173e5358e0 --- /dev/null +++ b/man/systemd-veritysetup@.service.xml @@ -0,0 +1,75 @@ + + + + + + + + systemd-veritysetup@.service + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-veritysetup@.service + 8 + + + + systemd-veritysetup@.service + systemd-veritysetup + Disk integrity protection logic + + + + systemd-veritysetup@.service + /usr/lib/systemd/systemd-veritysetup + + + + Description + + systemd-veritysetup@.service is a service responsible for setting up integrity + protection (verity) block devices. It should be instantiated for each device that requires integrity + protection. + + At early boot and when the system manager configuration is reloaded kernel command line configuration for + integrity protected block devices is translated into systemd-veritysetup@.service units by + systemd-veritysetup-generator8. + + + + See Also + + systemd1, + systemd-veritysetup-generator8, + veritysetup8 + + + + diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index f58aa27df2..3c601a63e2 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -50,6 +50,7 @@ static bool arg_fstab_enabled = true; static char *arg_root_what = NULL; static char *arg_root_fstype = NULL; static char *arg_root_options = NULL; +static char *arg_root_hash = NULL; static int arg_root_rw = -1; static char *arg_usr_what = NULL; static char *arg_usr_fstype = NULL; @@ -697,6 +698,13 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat free(arg_root_options); arg_root_options = o; + } else if (streq(key, "roothash")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + if (free_and_strdup(&arg_root_hash, value) < 0) + return log_oom(); } else if (streq(key, "mount.usr")) { @@ -749,6 +757,24 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; } +static int determine_root(void) { + /* If we have a root hash but no root device then Verity is used, and we use the "root" DM device as root. */ + + if (arg_root_what) + return 0; + + if (!arg_root_hash) + return 0; + + arg_root_what = strdup("/dev/mapper/root"); + if (!arg_root_what) + return log_oom(); + + log_info("Using verity root device %s.", arg_root_what); + + return 1; +} + int main(int argc, char *argv[]) { int r = 0; @@ -772,6 +798,8 @@ int main(int argc, char *argv[]) { if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + (void) determine_root(); + /* Always honour root= and usr= in the kernel command line if we are in an initrd */ if (in_initrd()) { int k; @@ -812,6 +840,7 @@ int main(int argc, char *argv[]) { free(arg_root_what); free(arg_root_fstype); free(arg_root_options); + free(arg_root_hash); free(arg_usr_what); free(arg_usr_fstype); diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 3126469f60..612a3fe777 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -718,6 +718,15 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat arg_root_enabled = streq(value, "gpt-auto"); + } else if (streq(key, "roothash")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + /* Disable root disk logic if there's roothash= defined (i.e. verity enabled) */ + + arg_root_enabled = false; + } else if (streq(key, "rw") && !value) arg_root_rw = true; else if (streq(key, "ro") && !value) diff --git a/src/veritysetup/Makefile b/src/veritysetup/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/veritysetup/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/veritysetup/veritysetup-generator.c b/src/veritysetup/veritysetup-generator.c new file mode 100644 index 0000000000..519ac050f9 --- /dev/null +++ b/src/veritysetup/veritysetup-generator.c @@ -0,0 +1,253 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fstab-util.h" +#include "hexdecoct.h" +#include "id128-util.h" +#include "mkdir.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "string-util.h" +#include "unit-name.h" + +static char *arg_dest = NULL; +static bool arg_enabled = true; +static char *arg_root_hash = NULL; +static char *arg_data_what = NULL; +static char *arg_hash_what = NULL; + +static int create_device(void) { + _cleanup_free_ char *u = NULL, *v = NULL, *d = NULL, *e = NULL; + _cleanup_fclose_ FILE *f = NULL; + const char *p, *to; + int r; + + /* If all three pieces of information are missing, then verity is turned off */ + if (!arg_root_hash && !arg_data_what && !arg_hash_what) + return 0; + + /* if one of them is missing however, the data is simply incomplete and this is an error */ + if (!arg_root_hash) + log_error("Verity information incomplete, root hash unspecified."); + if (!arg_data_what) + log_error("Verity information incomplete, root data device unspecified."); + if (!arg_hash_what) + log_error("Verity information incomplete, root hash device unspecified."); + + if (!arg_root_hash || !arg_data_what || !arg_hash_what) + return -EINVAL; + + log_debug("Using root verity data device %s,\n" + " hash device %s,\n" + " and root hash %s.", arg_data_what, arg_hash_what, arg_root_hash); + + p = strjoina(arg_dest, "/systemd-veritysetup@root.service"); + + u = fstab_node_to_udev_node(arg_data_what); + if (!u) + return log_oom(); + v = fstab_node_to_udev_node(arg_hash_what); + if (!v) + return log_oom(); + + r = unit_name_from_path(u, ".device", &d); + if (r < 0) + return log_error_errno(r, "Failed to to generate unit name: %m"); + r = unit_name_from_path(v, ".device", &e); + if (r < 0) + return log_error_errno(r, "Failed to to generate unit name: %m"); + + f = fopen(p, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", p); + + fprintf(f, + "# Automatically generated by systemd-veritysetup-generator\n\n" + "[Unit]\n" + "Description=Integrity Protection Setup for %%I\n" + "Documentation=man:systemd-veritysetup-generator(8) man:systemd-veritysetup@.service(8)\n" + "SourcePath=/proc/cmdline\n" + "DefaultDependencies=no\n" + "Conflicts=umount.target\n" + "BindsTo=%s %s\n" + "IgnoreOnIsolate=true\n" + "After=cryptsetup-pre.target %s %s\n" + "Before=cryptsetup.target umount.target\n" + "\n[Service]\n" + "Type=oneshot\n" + "RemainAfterExit=yes\n" + "ExecStart=" ROOTLIBEXECDIR "/systemd-veritysetup attach root '%s' '%s' '%s'\n" + "ExecStop=" ROOTLIBEXECDIR "/systemd-veritysetup detach root\n", + d, e, + d, e, + u, v, arg_root_hash); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write file %s: %m", p); + + to = strjoina(arg_dest, "/cryptsetup.target.requires/systemd-veritysetup@root.service"); + + (void) mkdir_parents(to, 0755); + if (symlink("../systemd-veritysetup@root.service", to) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + + return 0; +} + +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r; + + if (streq(key, "systemd.verity")) { + + r = value ? parse_boolean(value) : 1; + if (r < 0) + log_warning("Failed to parse verity= kernel command line switch %s. Ignoring.", value); + else + arg_enabled = r; + + } else if (streq(key, "roothash")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_root_hash, value); + if (r < 0) + return log_oom(); + + } else if (streq(key, "systemd.verity_root_data")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_data_what, value); + if (r < 0) + return log_oom(); + + } else if (streq(key, "systemd.verity_root_hash")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_hash_what, value); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int determine_devices(void) { + _cleanup_free_ void *m = NULL; + sd_id128_t root_uuid, verity_uuid; + char ids[37]; + size_t l; + int r; + + /* Try to automatically derive the root data and hash device paths from the root hash */ + + if (!arg_root_hash) + return 0; + + if (arg_data_what && arg_hash_what) + return 0; + + r = unhexmem(arg_root_hash, strlen(arg_root_hash), &m, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash: %s", arg_root_hash); + if (l < sizeof(sd_id128_t)) { + log_debug("Root hash is shorter than 128 bits (32 characters), ignoring for discovering verity partition."); + return 0; + } + + if (!arg_data_what) { + memcpy(&root_uuid, m, sizeof(root_uuid)); + + arg_data_what = strjoin("/dev/disk/by-partuuid/", id128_to_uuid_string(root_uuid, ids)); + if (!arg_data_what) + return log_oom(); + } + + if (!arg_hash_what) { + memcpy(&verity_uuid, (uint8_t*) m + l - sizeof(verity_uuid), sizeof(verity_uuid)); + + arg_hash_what = strjoin("/dev/disk/by-partuuid/", id128_to_uuid_string(verity_uuid, ids)); + if (!arg_hash_what) + return log_oom(); + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[1]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) { + log_warning_errno(r, "Failed to parse kernel command line: %m"); + goto finish; + } + + /* For now we only support the root device on verity. Later on we might want to add support for /etc/veritytab + * or similar to define additional mappings */ + + if (!arg_enabled) { + r = 0; + goto finish; + } + + r = determine_devices(); + if (r < 0) + goto finish; + + r = create_device(); + if (r < 0) + goto finish; + + r = 0; + +finish: + free(arg_root_hash); + free(arg_data_what); + free(arg_hash_what); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c new file mode 100644 index 0000000000..f809d51638 --- /dev/null +++ b/src/veritysetup/veritysetup.c @@ -0,0 +1,154 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "log.h" +#include "hexdecoct.h" +#include "string-util.h" +#include "alloc-util.h" + +static char *arg_root_hash = NULL; +static char *arg_data_what = NULL; +static char *arg_hash_what = NULL; + +static int help(void) { + printf("%s attach VOLUME DATADEVICE HASHDEVICE ROOTHASH\n" + "%s detach VOLUME\n\n" + "Attaches or detaches an integrity protected block device.\n", + program_invocation_short_name, + program_invocation_short_name); + + return 0; +} + +static void log_glue(int level, const char *msg, void *usrptr) { + log_debug("%s", msg); +} + +int main(int argc, char *argv[]) { + struct crypt_device *cd = NULL; + int r; + + if (argc <= 1) { + r = help(); + goto finish; + } + + if (argc < 3) { + log_error("This program requires at least two arguments."); + r = -EINVAL; + goto finish; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (streq(argv[1], "attach")) { + _cleanup_free_ void *m = NULL; + crypt_status_info status; + size_t l; + + if (argc < 6) { + log_error("attach requires at least two arguments."); + r = -EINVAL; + goto finish; + } + + r = unhexmem(argv[5], strlen(argv[5]), &m, &l); + if (r < 0) { + log_error("Failed to parse root hash."); + goto finish; + } + + r = crypt_init(&cd, argv[4]); + if (r < 0) { + log_error_errno(r, "Failed to open verity device %s: %m", argv[4]); + goto finish; + } + + crypt_set_log_callback(cd, log_glue, NULL); + + status = crypt_status(cd, argv[2]); + if (status == CRYPT_ACTIVE || status == CRYPT_BUSY) { + log_info("Volume %s already active.", argv[2]); + r = 0; + goto finish; + } + + r = crypt_load(cd, CRYPT_VERITY, NULL); + if (r < 0) { + log_error_errno(r, "Failed to load verity superblock: %m"); + goto finish; + } + + r = crypt_set_data_device(cd, argv[3]); + if (r < 0) { + log_error_errno(r, "Failed to configure data device: %m"); + goto finish; + } + + r = crypt_activate_by_volume_key(cd, argv[2], m, l, CRYPT_ACTIVATE_READONLY); + if (r < 0) { + log_error_errno(r, "Failed to set up verity device: %m"); + goto finish; + } + + } else if (streq(argv[1], "detach")) { + + r = crypt_init_by_name(&cd, argv[2]); + if (r == -ENODEV) { + log_info("Volume %s already inactive.", argv[2]); + goto finish; + } else if (r < 0) { + log_error_errno(r, "crypt_init_by_name() failed: %m"); + goto finish; + } + + crypt_set_log_callback(cd, log_glue, NULL); + + r = crypt_deactivate(cd, argv[2]); + if (r < 0) { + log_error_errno(r, "Failed to deactivate: %m"); + goto finish; + } + + } else { + log_error("Unknown verb %s.", argv[1]); + r = -EINVAL; + goto finish; + } + + r = 0; + +finish: + if (cd) + crypt_free(cd); + + free(arg_root_hash); + free(arg_data_what); + free(arg_hash_what); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} -- cgit v1.2.3-54-g00ecf