mirror of
https://github.com/systemd/systemd.git
synced 2024-11-30 22:03:41 +08:00
Merge pull request #15810 from poettering/override-first-boot
core: allow overriding needs-update/first-boot/system clock via kernel cmdline
This commit is contained in:
commit
619720ba0a
3
TODO
3
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
|
||||
|
@ -433,8 +433,47 @@
|
||||
|
||||
<listitem><para>Takes a boolean argument, defaults to on. If off,
|
||||
<citerefentry><refentrytitle>systemd-firstboot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
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.</para></listitem>
|
||||
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
|
||||
<varname>systemd.condition-first-boot=</varname> (see below), which overrides the result of the
|
||||
<varname>ConditionFirstBoot=</varname> unit file condition, and thus controls more than just
|
||||
<filename>systemd-firstboot.service</filename> behaviour.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>systemd.condition-needs-update=</varname></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument. If specified, overrides the result of
|
||||
<varname>ConditionNeedsUpdate=</varname> unit condition checks. See
|
||||
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
details.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>systemd.condition-first-boot=</varname></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument. If specified, overrides the result of
|
||||
<varname>ConditionFirstBoot=</varname> unit condition checks. See
|
||||
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
details. Not to be confused with <varname>systemd.firstboot=</varname> which only controls behaviour
|
||||
of the <filename>systemd-firstboot.service</filename> system service but has no effect on the
|
||||
condition check (see above).</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>systemd.clock-usec=</varname></term>
|
||||
|
||||
<listitem><para>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).</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>systemd.hostname=</varname></term>
|
||||
|
||||
<listitem><para>Accepts a hostname to set during early boot. If specified takes precedence over what
|
||||
is set in <filename>/etc/hostname</filename>. Note that this does not bar later runtime changes to
|
||||
the hostname, it simply controls the initial hostname set during early boot.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
|
@ -58,6 +58,10 @@
|
||||
<citerefentry project='man-pages'><refentrytitle>touch</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
on it.</para>
|
||||
|
||||
<para>Note that if the <varname>systemd.condition-needs-update=</varname> kernel command line option is
|
||||
used it overrides the <varname>ConditionNeedsUpdate=</varname> unit condition checks. In that case
|
||||
<filename>systemd-update-done.service</filename> will not reset the condition state until a follow-up
|
||||
reboot where the kernel switch is not specified anymore.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
|
@ -1294,6 +1294,13 @@
|
||||
<citerefentry><refentrytitle>systemd-update-done.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
to make sure they run before the stamp file's modification time gets reset indicating a completed
|
||||
update.</para>
|
||||
|
||||
<para>If the <varname>systemd.condition-needs-update=</varname> 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
|
||||
<filename>systemd-update-done.service</filename> will not have immediate effect on any following
|
||||
<varname>ConditionNeedsUpdate=</varname> checks, until the system is rebooted where the kernel
|
||||
command line option is not specified anymore.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
@ -1305,6 +1312,10 @@
|
||||
(specifically: an <filename>/etc</filename> with no <filename>/etc/machine-id</filename>). This may
|
||||
be used to populate <filename>/etc</filename> on the first boot after factory reset, or when a new
|
||||
system instance boots up for the first time.</para>
|
||||
|
||||
<para>If the <varname>systemd.condition-first-boot=</varname> option is specified on the kernel
|
||||
command line (taking a boolean), it will override the result of this condition check, taking
|
||||
precedence over <filename>/etc/machine-id</filename> existence checks.</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user