From: Michael Vogt Date: Fri, 12 Jun 2026 14:27:31 +0000 (+0200) Subject: hostname: add ? and $ in systemd.hostname= kernel cmdline X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c3c735bd09cd3f894703a7b07327551b96933607;p=thirdparty%2Fsystemd.git hostname: add ? and $ in systemd.hostname= kernel cmdline Similar to the support for ?/$ in /etc/hostname and the credentials we now add this to the kernel commandline systemd.hostname= option. If the expansion fails, e.g. in the initrd where the word lists (and possibly the machine ID) are not available yet, the option is ignored and the usual default hostname logic applies. Once the host system is up the expansion succeeds and the intended name is applied. --- diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 03765010f07..8253f0fafd4 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -736,6 +736,15 @@ 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. + The name may use the ? and $ wildcard patterns documented + in hostname5, + 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. + /etc/hostname or the system.hostname credential, falling back + to the default hostname if none of those apply. + diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c index 63464f26cfc..aa04a1ceab3 100644 --- a/src/shared/hostname-setup.c +++ b/src/shared/hostname-setup.c @@ -84,6 +84,30 @@ int shorten_overlong(const char *s, char **ret) { 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; @@ -96,22 +120,33 @@ static int acquire_hostname_from_credential(char **ret) { 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; @@ -194,17 +229,9 @@ int hostname_setup(bool really) { 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); diff --git a/src/test/test-hostname-setup.c b/src/test/test-hostname-setup.c index 0ac0acd4751..e72bf1f16d7 100644 --- a/src/test/test-hostname-setup.c +++ b/src/test/test-hostname-setup.c @@ -136,6 +136,33 @@ TEST(hostname_substitute_wildcards_words) { 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); }