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) {