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>
+ <para>The name may use the <literal>?</literal> and <literal>$</literal> wildcard patterns documented
+ in <citerefentry><refentrytitle>hostname</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+ which are expanded deterministically from the machine ID each time the name is applied. The expanded
+ name is never persisted, hence it may change if the word lists change. Note that in the initrd the
+ word lists (and possibly the machine ID) are typically not available; the option is then ignored and
+ the hostname is instead determined from the other sources, i.e.
+ <filename>/etc/hostname</filename> or the <varname>system.hostname</varname> credential, falling back
+ to the default hostname if none of those apply.</para>
+
<xi:include href="version-info.xml" xpointer="v246"/></listitem>
</varlistentry>
return 1;
}
+static int validate_and_substitute_hostname_from_source(const char *raw, const char *source, char **ret) {
+ int r;
+
+ assert(raw);
+ assert(source);
+ assert(ret);
+
+ /* Validate a raw hostname value that may carry '?'/'$' wildcards, expand the wildcards, then validate the
+ * concrete result. Shared by the credential and kernel command line paths. */
+ if (!hostname_is_valid(raw, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK|VALID_HOSTNAME_WORD_TOKEN))
+ return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Hostname specified %s is invalid, ignoring: %s", source, raw);
+
+ _cleanup_free_ char *substituted = NULL;
+ r = hostname_substitute_wildcards(raw, &substituted);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to substitute wildcards in hostname specified %s, ignoring: %m", source);
+
+ if (!hostname_is_valid(substituted, VALID_HOSTNAME_TRAILING_DOT)) /* check that the expanded hostname is valid */
+ return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Hostname specified %s is invalid after expansion, ignoring: %s", source, substituted);
+
+ *ret = TAKE_PTR(substituted);
+ return 0;
+}
+
static int acquire_hostname_from_credential(char **ret) {
_cleanup_free_ char *cred = NULL;
int r;
if (r == 0) /* not found */
return -ENXIO;
- if (!hostname_is_valid(cred, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK|VALID_HOSTNAME_WORD_TOKEN))
- return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Hostname specified in system.hostname credential is invalid, ignoring: %s", cred);
-
- _cleanup_free_ char *substituted = NULL;
- r = hostname_substitute_wildcards(cred, &substituted);
+ r = validate_and_substitute_hostname_from_source(cred, "in the system.hostname credential", ret);
if (r < 0)
- return log_warning_errno(r, "Failed to substitute wildcards in system.hostname credential, ignoring: %m");
-
- if (!hostname_is_valid(substituted, VALID_HOSTNAME_TRAILING_DOT)) /* check that the expanded hostname is valid */
- return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Hostname from system.hostname credential is invalid after expansion, ignoring: %s", substituted);
+ return r;
log_info("Initializing hostname from credential.");
- *ret = TAKE_PTR(substituted);
return 0;
}
+static int acquire_hostname_from_cmdline(char **ret) {
+ _cleanup_free_ char *hn = NULL;
+ int r;
+
+ assert(ret);
+
+ r = proc_cmdline_get_key("systemd.hostname", 0, &hn);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to retrieve system hostname from kernel command line, ignoring: %m");
+ if (r == 0) /* not specified */
+ return -ENXIO;
+
+ /* The name may contain '?'/'$' wildcards (see hostname(5)). In the initrd the word lists (and
+ * possibly the machine ID) are typically not available yet so returning here means the default
+ * hostname will be used. Once the host file system is up the expansion succeeds and the intended
+ * name is applied. */
+ return validate_and_substitute_hostname_from_source(hn, "on the kernel command line", ret);
+}
+
int read_etc_hostname_stream(FILE *f, bool substitute_wildcards, char **ret) {
int r;
bool enoent = false;
int r;
- r = proc_cmdline_get_key("systemd.hostname", 0, &hn);
- 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(hn, VALID_HOSTNAME_TRAILING_DOT))
- source = HOSTNAME_TRANSIENT;
- else {
- log_warning("Hostname specified on kernel command line is invalid, ignoring: %s", hn);
- hn = mfree(hn);
- }
- }
+ r = acquire_hostname_from_cmdline(&hn);
+ if (r >= 0)
+ source = HOSTNAME_TRANSIENT;
if (!hn) {
r = read_etc_hostname(/* path= */ NULL, /* substitute_wildcards= */ true, &hn);
ASSERT_OK(unsetenv("SYSTEMD_HOSTNAME_WORDLIST_PATH"));
}
+TEST(hostname_setup_cmdline_wildcards) {
+ _cleanup_(rm_rf_physical_and_freep) char *d = NULL;
+ int r;
+
+ r = sd_id128_get_machine(NULL);
+ if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r))
+ return (void) log_tests_skipped_errno(r, "skipping cmdline wildcard hostname tests, no machine ID defined");
+
+ ASSERT_OK(mkdtemp_malloc("/tmp/hostname-wordlist.XXXXXX", &d));
+ ASSERT_OK(setenv("SYSTEMD_PROC_CMDLINE", "systemd.hostname=$-????", /* overwrite= */ true));
+ ASSERT_OK(setenv("SYSTEMD_HOSTNAME_WORDLIST_PATH", d, /* overwrite= */ true));
+
+ /* Word list missing (as e.g. in the initrd): the kernel command line hostname is ignored and the
+ * usual fallback logic applies, hostname_setup() must still succeed. */
+ ASSERT_OK(hostname_setup(/* really= */ false));
+
+ /* Word list present: hostname_setup() now exercises the cmdline expansion path and must still
+ * succeed. With really=false nothing is applied and the resolved name is not surfaced, so the
+ * concrete expansion is asserted separately in the hostname_substitute_wildcards test above. */
+ _cleanup_free_ char *one_list = ASSERT_PTR(path_join(d, "1"));
+ ASSERT_OK(write_string_file(one_list, "happy\nsad\n", WRITE_STRING_FILE_CREATE));
+ ASSERT_OK(hostname_setup(/* really= */ false));
+
+ ASSERT_OK(unsetenv("SYSTEMD_PROC_CMDLINE"));
+ ASSERT_OK(unsetenv("SYSTEMD_HOSTNAME_WORDLIST_PATH"));
+}
+
TEST(hostname_setup) {
hostname_setup(false);
}