diff --git a/TODO b/TODO index 792f2d4be1a..42f2fcfe149 100644 --- a/TODO +++ b/TODO @@ -252,8 +252,7 @@ Features: that are linked to these places instead of copied. After all they are constant vendor data. -* maybe add kernel cmdline params: 1) to force first-boot mode + 2) to force - random seed crediting +* maybe add kernel cmdline params: to force random seed crediting * nspawn: on cgroupsv1 issue cgroup empty handler process based on host events, so that we make cgroup agent logic safe diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 4aac14ea180..0f2972a0b68 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -433,8 +433,47 @@ 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. + 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. Not to be confused with + systemd.condition-first-boot= (see below), which overrides the result of the + ConditionFirstBoot= unit file condition, and thus controls more than just + systemd-firstboot.service behaviour. + + + + systemd.condition-needs-update= + + Takes a boolean argument. If specified, overrides the result of + ConditionNeedsUpdate= unit condition checks. See + systemd.unit5 for + details. + + + + systemd.condition-first-boot= + + Takes a boolean argument. If specified, overrides the result of + ConditionFirstBoot= unit condition checks. See + systemd.unit5 for + details. Not to be confused with systemd.firstboot= which only controls behaviour + of the systemd-firstboot.service system service but has no effect on the + condition check (see above). + + + + systemd.clock-usec= + + Takes a decimal, numeric timestamp in µs since January 1st 1970, 00:00am, to set the + system clock to. The system time is set to the specified timestamp early during + boot. It is not propagated to the hardware clock (RTC). + + + + systemd.hostname= + + Accepts a hostname to set during early boot. If specified takes precedence over what + is set in /etc/hostname. Note that this does not bar later runtime changes to + the hostname, it simply controls the initial hostname set during early boot. diff --git a/man/systemd-update-done.service.xml b/man/systemd-update-done.service.xml index ad412691a94..91196dff301 100644 --- a/man/systemd-update-done.service.xml +++ b/man/systemd-update-done.service.xml @@ -58,6 +58,10 @@ touch1 on it. + Note that if the systemd.condition-needs-update= kernel command line option is + used it overrides the ConditionNeedsUpdate= unit condition checks. In that case + systemd-update-done.service will not reset the condition state until a follow-up + reboot where the kernel switch is not specified anymore. diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index b91c1ad0ecc..fa8ed1b47bb 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -1294,6 +1294,13 @@ systemd-update-done.service8, to make sure they run before the stamp file's modification time gets reset indicating a completed update. + + If the systemd.condition-needs-update= option is specified on the kernel + command line (taking a boolean), it will override the result of this condition check, taking + precedence over any file modification time checks. If it is used + systemd-update-done.service will not have immediate effect on any following + ConditionNeedsUpdate= checks, until the system is rebooted where the kernel + command line option is not specified anymore. @@ -1305,6 +1312,10 @@ (specifically: an /etc with no /etc/machine-id). This may be used to populate /etc on the first boot after factory reset, or when a new system instance boots up for the first time. + + If the systemd.condition-first-boot= option is specified on the kernel + command line (taking a boolean), it will override the result of this condition check, taking + precedence over /etc/machine-id existence checks. diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index d3d99d9a7f9..d8d30b0f1df 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -268,17 +268,17 @@ int proc_cmdline_get_bool(const char *key, bool *ret) { r = proc_cmdline_get_key(key, PROC_CMDLINE_VALUE_OPTIONAL, &v); if (r < 0) return r; - if (r == 0) { + if (r == 0) { /* key not specified at all */ *ret = false; return 0; } - if (v) { /* parameter passed */ + if (v) { /* key with parameter passed */ r = parse_boolean(v); if (r < 0) return r; *ret = r; - } else /* no parameter passed */ + } else /* key without parameter passed */ *ret = true; return 1; diff --git a/src/basic/proc-cmdline.h b/src/basic/proc-cmdline.h index 4115fdbc99f..275f46c89ec 100644 --- a/src/basic/proc-cmdline.h +++ b/src/basic/proc-cmdline.h @@ -6,9 +6,9 @@ #include "log.h" typedef enum ProcCmdlineFlags { - PROC_CMDLINE_STRIP_RD_PREFIX = 1 << 0, - PROC_CMDLINE_VALUE_OPTIONAL = 1 << 1, - PROC_CMDLINE_RD_STRICT = 1 << 2, + PROC_CMDLINE_STRIP_RD_PREFIX = 1 << 0, /* automatically strip "rd." prefix if it is set (and we are in the initrd, since otherwise we'd not consider it anyway) */ + PROC_CMDLINE_VALUE_OPTIONAL = 1 << 1, /* the value is optional (for boolean switches that can omit the value) */ + PROC_CMDLINE_RD_STRICT = 1 << 2, /* ignore this in the initrd */ } ProcCmdlineFlags; typedef int (*proc_cmdline_parse_t)(const char *key, const char *value, void *data); diff --git a/src/core/hostname-setup.c b/src/core/hostname-setup.c index 83cce88131e..6d047db8388 100644 --- a/src/core/hostname-setup.c +++ b/src/core/hostname-setup.c @@ -10,29 +10,41 @@ #include "hostname-util.h" #include "log.h" #include "macro.h" +#include "proc-cmdline.h" #include "string-util.h" #include "util.h" int hostname_setup(void) { _cleanup_free_ char *b = NULL; + const char *hn = NULL; bool enoent = false; - const char *hn; int r; - r = read_etc_hostname(NULL, &b); - if (r < 0) { - if (r == -ENOENT) - enoent = true; - else - log_warning_errno(r, "Failed to read configured hostname: %m"); + r = proc_cmdline_get_key("systemd.hostname", 0, &b); + if (r < 0) + log_warning_errno(r, "Failed to retrieve system hostname from kernel command line, ignoring: %m"); + else if (r > 0) { + if (hostname_is_valid(b, true)) + hn = b; + else { + log_warning("Hostname specified on kernel command line is invalid, ignoring: %s", b); + b = mfree(b); + } + } - hn = NULL; - } else - hn = b; + if (!hn) { + r = read_etc_hostname(NULL, &b); + if (r < 0) { + if (r == -ENOENT) + enoent = true; + else + log_warning_errno(r, "Failed to read configured hostname: %m"); + } else + hn = b; + } if (isempty(hn)) { - /* Don't override the hostname if it is already set - * and not explicitly configured */ + /* Don't override the hostname if it is already set and not explicitly configured */ if (hostname_is_set()) return 0; diff --git a/src/core/main.c b/src/core/main.c index c9eaf70bd6c..3ab4aa4e198 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -146,6 +146,7 @@ static EmergencyAction arg_cad_burst_action; static OOMPolicy arg_default_oom_policy; static CPUSet arg_cpu_affinity; static NUMAPolicy arg_numa_policy; +static usec_t arg_clock_usec; /* A copy of the original environment block */ static char **saved_env = NULL; @@ -491,6 +492,15 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat (void) parse_path_argument_and_warn(value, false, &arg_watchdog_device); + } else if (proc_cmdline_key_streq(key, "systemd.clock_usec")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = safe_atou64(value, &arg_clock_usec); + if (r < 0) + log_warning_errno(r, "Failed to parse systemd.clock_usec= argument, ignoring: %s", value); + } else if (streq(key, "quiet") && !value) { if (arg_show_status == _SHOW_STATUS_INVALID) @@ -1504,6 +1514,9 @@ static int become_shutdown( static void initialize_clock(void) { int r; + /* This is called very early on, before we parse the kernel command line or otherwise figure out why + * we are running, but only once. */ + if (clock_is_localtime(NULL) > 0) { int min; @@ -1542,6 +1555,25 @@ static void initialize_clock(void) { log_info("System time before build time, advancing clock."); } +static void apply_clock_update(void) { + struct timespec ts; + + /* This is called later than initialize_clock(), i.e. after we parsed configuration files/kernel + * command line and such. */ + + if (arg_clock_usec == 0) + return; + + if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, arg_clock_usec)) < 0) + log_error_errno(errno, "Failed to set system clock to time specified on kernel command line: %m"); + else { + char buf[FORMAT_TIMESTAMP_MAX]; + + log_info("Set system clock to %s, as specified on the kernel command line.", + format_timestamp(buf, sizeof(buf), arg_clock_usec)); + } +} + static void initialize_coredump(bool skip_setup) { #if ENABLE_COREDUMP if (getpid_cached() != 1) @@ -2658,6 +2690,8 @@ int main(int argc, char *argv[]) { assert_se(chdir("/") == 0); if (arg_action == ACTION_RUN) { + /* Apply the systemd.clock_usec= kernel command line switch */ + apply_clock_update(); /* A core pattern might have been specified via the cmdline. */ initialize_core_pattern(skip_setup); diff --git a/src/shared/condition.c b/src/shared/condition.c index c83a4ba0eb8..bf3b5fa1622 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -546,30 +546,47 @@ static int condition_test_capability(Condition *c, char **env) { } static int condition_test_needs_update(Condition *c, char **env) { - const char *p; struct stat usr, other; + const char *p; + bool b; + int r; assert(c); assert(c->parameter); assert(c->type == CONDITION_NEEDS_UPDATE); + r = proc_cmdline_get_bool("systemd.condition-needs-update", &b); + if (r < 0) + log_debug_errno(r, "Failed to parse systemd.condition-needs-update= kernel command line argument, ignoring: %m"); + if (r > 0) + return b; + + if (!path_is_absolute(c->parameter)) { + log_debug("Specified condition parameter '%s' is not absolute, assuming an update is needed.", c->parameter); + return true; + } + /* If the file system is read-only we shouldn't suggest an update */ - if (path_is_read_only_fs(c->parameter) > 0) + r = path_is_read_only_fs(c->parameter); + if (r < 0) + log_debug_errno(r, "Failed to determine if '%s' is read-only, ignoring: %m", c->parameter); + if (r > 0) return false; - /* Any other failure means we should allow the condition to be true, - * so that we rather invoke too many update tools than too - * few. */ - - if (!path_is_absolute(c->parameter)) - return true; + /* Any other failure means we should allow the condition to be true, so that we rather invoke too + * many update tools than too few. */ p = strjoina(c->parameter, "/.updated"); - if (lstat(p, &other) < 0) + if (lstat(p, &other) < 0) { + if (errno != ENOENT) + log_debug_errno(errno, "Failed to stat() '%s', assuming an update is needed: %m", p); return true; + } - if (lstat("/usr/", &usr) < 0) + if (lstat("/usr/", &usr) < 0) { + log_debug_errno(errno, "Failed to stat() /usr/, assuming an update is needed: %m"); return true; + } /* * First, compare seconds as they are always accurate... @@ -585,44 +602,52 @@ static int condition_test_needs_update(Condition *c, char **env) { * AND the target file's nanoseconds == 0 * (otherwise the filesystem supports nsec timestamps, see stat(2)). */ - if (usr.st_mtim.tv_nsec > 0 && other.st_mtim.tv_nsec == 0) { - _cleanup_free_ char *timestamp_str = NULL; - uint64_t timestamp; - int r; + if (usr.st_mtim.tv_nsec == 0 || other.st_mtim.tv_nsec > 0) + return usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec; - r = parse_env_file(NULL, p, "TIMESTAMP_NSEC", ×tamp_str); - if (r < 0) { - log_error_errno(r, "Failed to parse timestamp file '%s', using mtime: %m", p); - return true; - } else if (r == 0) { - log_debug("No data in timestamp file '%s', using mtime", p); - return true; - } - - r = safe_atou64(timestamp_str, ×tamp); - if (r < 0) { - log_error_errno(r, "Failed to parse timestamp value '%s' in file '%s', using mtime: %m", timestamp_str, p); - return true; - } - - timespec_store(&other.st_mtim, timestamp); + _cleanup_free_ char *timestamp_str = NULL; + r = parse_env_file(NULL, p, "TIMESTAMP_NSEC", ×tamp_str); + if (r < 0) { + log_debug_errno(r, "Failed to parse timestamp file '%s', using mtime: %m", p); + return true; + } else if (r == 0) { + log_debug("No data in timestamp file '%s', using mtime.", p); + return true; } - return usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec; + uint64_t timestamp; + r = safe_atou64(timestamp_str, ×tamp); + if (r < 0) { + log_debug_errno(r, "Failed to parse timestamp value '%s' in file '%s', using mtime: %m", timestamp_str, p); + return true; + } + + return timespec_load_nsec(&usr.st_mtim) > timestamp; } static int condition_test_first_boot(Condition *c, char **env) { - int r; + int r, q; + bool b; assert(c); assert(c->parameter); assert(c->type == CONDITION_FIRST_BOOT); + r = proc_cmdline_get_bool("systemd.condition-first-boot", &b); + if (r < 0) + log_debug_errno(r, "Failed to parse systemd.condition-first-boot= kernel command line argument, ignoring: %m"); + if (r > 0) + return b == !!r; + r = parse_boolean(c->parameter); if (r < 0) return r; - return (access("/run/systemd/first-boot", F_OK) >= 0) == !!r; + q = access("/run/systemd/first-boot", F_OK); + if (q < 0 && errno != ENOENT) + log_debug_errno(errno, "Failed to check if /run/systemd/first-boot exists, ignoring: %m"); + + return (q >= 0) == !!r; } static int condition_test_environment(Condition *c, char **env) {