From de31bbc6b1689322c982908ec90adf8c7778b05f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 4 Dec 2020 15:16:24 +0100 Subject: [PATCH 01/11] man/hostnamectl,hostaned,hostname1: adjust the docs to match reality The semantics were significantly changed in c779a44222161155c039a7fd2fd304c006590ac7 ("hostnamed: Fix the way that static and transient host names interact", Feb. 2014), but when the dbus api documentation was imported much later, it wasn't properly adjusted to describe those new semantics. 34293dfafd2a81d80727938199769906dab321bd which added systemd.hostname= also added new behaviour. Let's ove various bits and pieces around so that they are in more appropriate places. Drop recommendations to set the hostname for DHCP or mDNS purposes. Nowadays we expect tools that want to expose some different hostname to the outside to manage that internally without affecting visible state. Also drop mentions of DHCP or mDNS directly setting the hostname, since nowadays network management software is expected to (and does) go through hostnamed. Also, add a high-level description of semantics. It glosses over the details of handling of localhost-style names. Later commits will remove this special handling anyway. --- man/hostname.xml | 77 ++++++++++++++++++++------ man/hostnamectl.xml | 30 ++++------- man/org.freedesktop.hostname1.xml | 90 +++++++++++++++---------------- man/systemd-hostnamed.service.xml | 10 +++- 4 files changed, 122 insertions(+), 85 deletions(-) diff --git a/man/hostname.xml b/man/hostname.xml index edbeef8f4af..d0507037929 100644 --- a/man/hostname.xml +++ b/man/hostname.xml @@ -1,6 +1,9 @@ + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [ + +%entities; +]> @@ -26,23 +29,65 @@ Description - The /etc/hostname file configures the - name of the local system that is set during boot using the - sethostname2 - system call. It should contain a single newline-terminated - hostname string. Comments (lines starting with a `#') are ignored. - The hostname may be a free-form string up to 64 characters in length; - however, it is recommended that it consists only of 7-bit ASCII lower-case - characters and no spaces or dots, and limits itself to the format allowed - for DNS domain name labels, even though this is not a strict - requirement. + The /etc/hostname file configures the name of the local system. Unless + overridden as described in the next section, + systemd1 will set this + hostname during boot using the + sethostname2 system + call. + + The file should contain a single newline-terminated hostname string. Comments (lines starting with + a #) are ignored. The hostname should be composed of up to 64 7-bit ASCII lower-case + alphanumeric characters or hyphens forming a valid DNS domain name. It is recommended that this name + contains only a single label, i.e. without any dots. Invalid characters will be filtered out in an + attempt to make the name valid, but obviously it is recommended to use a valid name and not rely on this + filtering. You may use - hostnamectl1 - to change the value of this file during runtime from the command - line. Use - systemd-firstboot1 - to initialize it on mounted (but not booted) system images. + hostnamectl1 to change + the value of this file during runtime from the command line. Use + systemd-firstboot1 to + initialize it on mounted (but not booted) system images. + + + + Hostname semantics + + systemd1 and the + associated tools will obtain the hostname in the following ways: + + If the kernel commandline parameter systemd.hostname= specifies a + valid hostname, + systemd1 will use it + to set the hostname during early boot, see + kernel-command-line7, + + + Otherwise, the "static" hostname specified by /etc/hostname as + described above will be used. + + Otherwise, a transient hostname may be set during runtime, for example based on + information in a DHCP lease, see + systemd-hostnamed.service8. + Both NetworkManager and + systemd-networkd.service8 + allow this. Note that + systemd-hostnamed.service8 + gives higher priority to the static hostname, so the transient hostname will only be used if the static + hostname is not configured. + + Otherwise, a fallback hostname configured at compilation time will be used + (&FALLBACK_HOSTNAME;). + + + + + Effectively, the static hostname has higher priority than a transient hostname, which has higher + priority than the fallback hostname. Transient hostnames are equivalent, so setting a new transient + hostname causes the previous transient hostname to be forgotten. The hostname specified on the kernel + command line is like a transient hostname, with the exception that it has higher priority when the + machine boots. Also note that those are the semantics implemented by systemd tools, but other programs + may also set the hostname. diff --git a/man/hostnamectl.xml b/man/hostnamectl.xml index 8c00867e739..f50cefa217e 100644 --- a/man/hostnamectl.xml +++ b/man/hostnamectl.xml @@ -32,33 +32,23 @@ Description - hostnamectl may be used to query and - change the system hostname and related settings. + hostnamectl may be used to query and change the system hostname and related + settings. - This tool distinguishes three different hostnames: the - high-level "pretty" hostname which might include all kinds of - special characters (e.g. "Lennart's Laptop"), the static hostname - which is used to initialize the kernel hostname at boot (e.g. - "lennarts-laptop"), and the transient hostname which is a fallback - value received from network configuration. If a static hostname is - set, and is valid (something other than localhost), then the - transient hostname is not used. + systemd-hostnamed.service8 + and this tool distinguish three different hostnames: the high-level "pretty" hostname which might include + all kinds of special characters (e.g. "Lennart's Laptop"), the "static" hostname which is the + user-configured hostname (e.g. "lennarts-laptop"), and the transient hostname which is a fallback value + received from network configuration (e.g. "node12345678"). If a static hostname is set, and is valid + (something other than localhost), then the transient hostname is not used. Note that the pretty hostname has little restrictions on the characters and length used, while the static and transient hostnames are limited to the usually accepted characters of Internet domain names, and 64 characters at maximum (the latter being a Linux limitation). - The static hostname is stored in - /etc/hostname, see - hostname5 - for more information. The pretty hostname, chassis type, and icon - name are stored in /etc/machine-info, see - machine-info5. - Use - systemd-firstboot1 - to initialize the system hostname for mounted (but not booted) - system images. + systemd-firstboot1 to + initialize the system hostname for mounted (but not booted) system images. diff --git a/man/org.freedesktop.hostname1.xml b/man/org.freedesktop.hostname1.xml index f8e199ceaaf..c715bad593d 100644 --- a/man/org.freedesktop.hostname1.xml +++ b/man/org.freedesktop.hostname1.xml @@ -144,55 +144,53 @@ node /org/freedesktop/hostname1 { Semantics - The static (configured) hostname is the one configured in - /etc/hostname. It is chosen by the local user. It is not always in sync with the - current hostname as returned by the + The StaticHostname property exposes the "static" hostname configured in + /etc/hostname. It is not always in sync with the current hostname as returned by the gethostname3 - system call. If no hostname is configured this property will be the empty string. Setting this property - to the empty string will remove /etc/hostname. This property should be an - internet-style hostname, 7-bit lowercase ASCII, no special chars/spaces. + system call. If no static hostname is configured this property will be the empty string. - The transient (dynamic) hostname is the one configured via the kernel's + When systemd1 or + systemd-hostnamed.service8 + set the hostname, this static hostname has the highest priority. + + The Hostname property exposes the actual hostname configured in the kernel via sethostname3. - It can be different from the static hostname if DHCP or mDNS have been configured to change the name - based on network information. - This property is never empty. If no hostname is set this will default to - &FALLBACK_HOSTNAME; (configurable at compilation time). Setting this property to the - empty string will reset the dynamic hostname to the static hostname. If no static hostname is - configured the dynamic hostname will be reset to &FALLBACK_HOSTNAME;. This property - should be an internet-style hostname, 7-bit lowercase ASCII, no special chars/spaces. + It can be different from the static hostname. This property is never empty. - The pretty hostname is a free-form UTF-8 hostname for presentation to the - user. User interfaces should ensure that the pretty hostname and the static hostname stay in sync. - I.e. when the former is Lennart’s Computer the latter should be - lennarts-computer. If no pretty hostname is set this setting will be the empty - string. Applications should then find a suitable fallback, such as the dynamic hostname. + The PrettyHostname property exposes the pretty hostname + which is a free-form UTF-8 hostname for presentation to the user. User interfaces should ensure that the + pretty hostname and the static hostname stay in sync. E.g. when the former is Lennart’s + Computer the latter should be lennarts-computer. If no pretty hostname is + set this setting will be the empty string. Applications should then find a suitable fallback, such as the + dynamic hostname. - The icon name is a name following the XDG icon naming spec. If not set, - information such as the chassis type (see below) is used to find a suitable fallback icon name - (i.e. computer-laptop vs. computer-desktop is picked based on the - chassis information). If no such data is available, the empty string is returned. In that case an application - should fall back to a replacement icon, for example computer. If this property is set - to the empty string, the automatic fallback name selection is enabled again. + The IconName property exposes the icon name following the + XDG icon naming spec. If not set, information such as the chassis type (see below) is used to find a + suitable fallback icon name (i.e. computer-laptop + vs. computer-desktop is picked based on the chassis information). If no such data is + available, the empty string is returned. In that case an application should fall back to a replacement + icon, for example computer. If this property is set to the empty string, the automatic + fallback name selection is enabled again. - The chassis type should be one of the currently defined chassis types: - desktop, laptop, server, - tablet, handset, as well as the special chassis types - vm and container for virtualized systems. Note that in most cases - the chassis type will be determined automatically from DMI/SMBIOS/ACPI firmware information. Writing to - this setting is hence useful only to override misdetected chassis types, or to configure the chassis type if - it could not be auto-detected. Set this property to the empty string to reenable the automatic detection of - the chassis type from firmware information. + The Chassis property exposes a chassis type, one of the + currently defined chassis types: desktop, laptop, + server, tablet, handset, as well as the special + chassis types vm and container for virtualized systems. Note that + in most cases the chassis type will be determined automatically from DMI/SMBIOS/ACPI firmware + information. Writing to this setting is hence useful only to override misdetected chassis types, or to + configure the chassis type if it could not be auto-detected. Set this property to the empty string to + reenable the automatic detection of the chassis type from firmware information. Note that systemd-hostnamed starts only on request and terminates after a short idle period. This effectively means that PropertyChanged messages are not sent out for changes made directly on the files (as in: administrator edits the files with vi). This is the intended behavior: manual configuration changes should require manual reloading. - The transient (dynamic) hostname maps directly to the kernel hostname. This hostname should be - assumed to be highly dynamic, and hence should be watched directly, without depending on - PropertyChanged messages from systemd-hostnamed. To accomplish - this, open /proc/sys/kernel/hostname and + The transient (dynamic) hostname exposed by the Hostname property maps directly + to the kernel hostname. This hostname should be assumed to be highly dynamic, and hence should be watched + directly, without depending on PropertyChanged messages from + systemd-hostnamed. To accomplish this, open + /proc/sys/kernel/hostname and poll3 for SIGHUP which is triggered by the kernel every time the hostname changes. Again: this is special for the transient (dynamic) hostname, and does not apply to the configured (fixed) @@ -206,15 +204,17 @@ node /org/freedesktop/hostname1 { for that. For more information on these files and syscalls see the respective man pages. - Methods and Properties + Methods - SetHostname() sets the transient (dynamic) hostname which is exposed by the - Hostname property. If empty, the transient hostname is set to the static hostname. - + SetHostname() sets the transient (dynamic) hostname, which is used if no + static hostname is set. This value must be an internet-style hostname, 7-bit lowercase ASCII, no + special chars/spaces. An empty string will unset the transient hostname. SetStaticHostname() sets the static hostname which is exposed by the - StaticHostname property. If empty, the built-in default of - &FALLBACK_HOSTNAME; is used. + StaticHostname property. When called with an empty argument, the static + configuration in /etc/hostname is removed. Since the static hostname has the + highest priority, calling this function usually affects also the Hostname property + and the effective hostname configured in the kernel. SetPrettyHostname() sets the pretty hostname which is exposed by the PrettyHostname property. @@ -287,10 +287,6 @@ node /org/freedesktop/hostname1 { with nss-myhostname3. - A client that wants to change the local hostname for DHCP/mDNS should invoke - SetHostname("newname", false) as soon as the name is available and afterwards reset it via - SetHostname(""). - Here are some recommendations to follow when generating a static (internet) hostname from a pretty name: diff --git a/man/systemd-hostnamed.service.xml b/man/systemd-hostnamed.service.xml index c0c46b66094..0e42f671c23 100644 --- a/man/systemd-hostnamed.service.xml +++ b/man/systemd-hostnamed.service.xml @@ -51,9 +51,15 @@ + The static hostname is stored in /etc/hostname, see + hostname5 for more + information. The pretty hostname, chassis type, and icon name are stored in + /etc/machine-info, see + machine-info5. + The tool - hostnamectl1 - is a command line client to this service. + hostnamectl1 is a + command line client to this service. See org.freedesktop.hostname15 From ce6b138c7576df55ede15f5d8b02a1766449bd1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 4 Dec 2020 15:16:54 +0100 Subject: [PATCH 02/11] hostnamed: expose the fallback-hostname setting as a const dbus property Various users want to know what the fallback hostname is. Since it was made configurable in 8146c32b9264a6915d467a5cab1a24311fbede7e, we didn't expose this nicely. --- man/org.freedesktop.hostname1.xml | 7 +++++++ src/hostname/hostnamed.c | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/man/org.freedesktop.hostname1.xml b/man/org.freedesktop.hostname1.xml index c715bad593d..8e5c37345d6 100644 --- a/man/org.freedesktop.hostname1.xml +++ b/man/org.freedesktop.hostname1.xml @@ -62,6 +62,8 @@ node /org/freedesktop/hostname1 { readonly s Hostname = '...'; readonly s StaticHostname = '...'; readonly s PrettyHostname = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s FallbackHostname = '...'; readonly s IconName = '...'; readonly s Chassis = '...'; readonly s Deployment = '...'; @@ -113,6 +115,8 @@ node /org/freedesktop/hostname1 { + + @@ -164,6 +168,9 @@ node /org/freedesktop/hostname1 { set this setting will be the empty string. Applications should then find a suitable fallback, such as the dynamic hostname. + The FallbackHostname property exposes the fallback hostname (configured at + compilation time). + The IconName property exposes the icon name following the XDG icon naming spec. If not set, information such as the chassis type (see below) is used to find a suitable fallback icon name (i.e. computer-laptop diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index a0dc25c8347..bec1fde5a55 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -8,6 +8,7 @@ #include "alloc-util.h" #include "bus-common-errors.h" +#include "bus-get-properties.h" #include "bus-log-control-api.h" #include "bus-polkit.h" #include "def.h" @@ -461,6 +462,8 @@ static int property_get_static_hostname( return sd_bus_message_append(reply, "s", c->data[PROP_STATIC_HOSTNAME]); } +static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_fallback_hostname, "s", FALLBACK_HOSTNAME); + static int property_get_machine_info_field( sd_bus *bus, const char *path, @@ -850,6 +853,7 @@ static const sd_bus_vtable hostname_vtable[] = { SD_BUS_PROPERTY("Hostname", "s", property_get_hostname, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("StaticHostname", "s", property_get_static_hostname, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("PrettyHostname", "s", property_get_machine_info_field, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("FallbackHostname", "s", property_get_fallback_hostname, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Deployment", "s", property_get_machine_info_field, offsetof(Context, data) + sizeof(char*) * PROP_DEPLOYMENT, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), From 7d9ec609903a0d4ae121bb2cc39aa74773eee984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 4 Dec 2020 17:35:22 +0100 Subject: [PATCH 03/11] hostnamed: fix return value --- src/hostname/hostnamed.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index bec1fde5a55..e624ace7545 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -323,6 +323,7 @@ static int context_update_kernel_hostname( const char *static_hn, *hn; struct utsname u; + int r; assert(c); @@ -352,8 +353,9 @@ static int context_update_kernel_hostname( else hn = FALLBACK_HOSTNAME; - if (sethostname_idempotent(hn) < 0) - return -errno; + r = sethostname_idempotent(hn); + if (r < 0) + return r; (void) nscd_flush_cache(STRV_MAKE("hosts")); From e2054217d506c73232d00c850bb46ea9caf26cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 4 Dec 2020 18:39:23 +0100 Subject: [PATCH 04/11] Move hostname setup logic to new shared/hostname-setup.[ch] No functional change, just moving a bunch of things around. Before we needed a rather complicated setup to test hostname_setup(), because the code was in src/core/. When things are moved to src/shared/ we can just test it as any function. The test is still "unsafe" because hostname_setup() may modify the hostname. --- src/basic/hostname-util.c | 120 ------------ src/basic/hostname-util.h | 9 - src/core/hostname-setup.c | 62 ------ src/core/hostname-setup.h | 4 - src/core/meson.build | 2 - ...-hostname-util.c => fuzz-hostname-setup.c} | 2 +- src/fuzz/meson.build | 2 +- src/hostname/hostnamed.c | 1 + src/network/networkd-dhcp4.c | 1 + src/network/test-network.c | 2 +- src/nspawn/nspawn.c | 1 + src/shared/dissect-image.c | 2 +- src/shared/hostname-setup.c | 183 ++++++++++++++++++ src/shared/hostname-setup.h | 13 ++ src/shared/machine-image.c | 2 +- src/shared/meson.build | 2 + src/test/meson.build | 16 +- src/test/test-hostname-setup.c | 76 ++++++++ src/test/test-hostname-util.c | 55 +----- src/test/test-hostname.c | 14 -- 20 files changed, 289 insertions(+), 280 deletions(-) delete mode 100644 src/core/hostname-setup.c delete mode 100644 src/core/hostname-setup.h rename src/fuzz/{fuzz-hostname-util.c => fuzz-hostname-setup.c} (96%) create mode 100644 src/shared/hostname-setup.c create mode 100644 src/shared/hostname-setup.h create mode 100644 src/test/test-hostname-setup.c delete mode 100644 src/test/test-hostname.c diff --git a/src/basic/hostname-util.c b/src/basic/hostname-util.c index e1e3d1b0613..d7aba2c2630 100644 --- a/src/basic/hostname-util.c +++ b/src/basic/hostname-util.c @@ -7,28 +7,10 @@ #include #include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" #include "hostname-util.h" -#include "macro.h" #include "string-util.h" #include "strv.h" -bool hostname_is_set(void) { - struct utsname u; - - assert_se(uname(&u) >= 0); - - if (isempty(u.nodename)) - return false; - - /* This is the built-in kernel default hostname */ - if (streq(u.nodename, "(none)")) - return false; - - return true; -} - char* gethostname_malloc(void) { struct utsname u; const char *s; @@ -208,105 +190,3 @@ bool is_localhost(const char *hostname) { endswith_no_case(hostname, ".localhost.localdomain") || endswith_no_case(hostname, ".localhost.localdomain."); } - -int sethostname_idempotent(const char *s) { - char buf[HOST_NAME_MAX + 1] = {}; - - assert(s); - - if (gethostname(buf, sizeof(buf)) < 0) - return -errno; - - if (streq(buf, s)) - return 0; - - if (sethostname(s, strlen(s)) < 0) - return -errno; - - return 1; -} - -int shorten_overlong(const char *s, char **ret) { - char *h, *p; - - /* Shorten an overlong name to HOST_NAME_MAX or to the first dot, - * whatever comes earlier. */ - - assert(s); - - h = strdup(s); - if (!h) - return -ENOMEM; - - if (hostname_is_valid(h, 0)) { - *ret = h; - return 0; - } - - p = strchr(h, '.'); - if (p) - *p = 0; - - strshorten(h, HOST_NAME_MAX); - - if (!hostname_is_valid(h, 0)) { - free(h); - return -EDOM; - } - - *ret = h; - return 1; -} - -int read_etc_hostname_stream(FILE *f, char **ret) { - int r; - - assert(f); - assert(ret); - - for (;;) { - _cleanup_free_ char *line = NULL; - char *p; - - r = read_line(f, LONG_LINE_MAX, &line); - if (r < 0) - return r; - if (r == 0) /* EOF without any hostname? the file is empty, let's treat that exactly like no file at all: ENOENT */ - return -ENOENT; - - p = strstrip(line); - - /* File may have empty lines or comments, ignore them */ - if (!IN_SET(*p, '\0', '#')) { - char *copy; - - hostname_cleanup(p); /* normalize the hostname */ - - if (!hostname_is_valid(p, VALID_HOSTNAME_TRAILING_DOT)) /* check that the hostname we return is valid */ - return -EBADMSG; - - copy = strdup(p); - if (!copy) - return -ENOMEM; - - *ret = copy; - return 0; - } - } -} - -int read_etc_hostname(const char *path, char **ret) { - _cleanup_fclose_ FILE *f = NULL; - - assert(ret); - - if (!path) - path = "/etc/hostname"; - - f = fopen(path, "re"); - if (!f) - return -errno; - - return read_etc_hostname_stream(f, ret); - -} diff --git a/src/basic/hostname-util.h b/src/basic/hostname-util.h index fe417297eea..6cff9c1d4cc 100644 --- a/src/basic/hostname-util.h +++ b/src/basic/hostname-util.h @@ -7,8 +7,6 @@ #include "macro.h" #include "strv.h" -bool hostname_is_set(void); - char* gethostname_malloc(void); char* gethostname_short_malloc(void); int gethostname_strict(char **ret); @@ -29,10 +27,3 @@ static inline bool is_gateway_hostname(const char *hostname) { /* This tries to identify the valid syntaxes for the our synthetic "gateway" host. */ return STRCASE_IN_SET(hostname, "_gateway", "_gateway."); } - -int sethostname_idempotent(const char *s); - -int shorten_overlong(const char *s, char **ret); - -int read_etc_hostname_stream(FILE *f, char **ret); -int read_etc_hostname(const char *path, char **ret); diff --git a/src/core/hostname-setup.c b/src/core/hostname-setup.c deleted file mode 100644 index 6ccaa479de2..00000000000 --- a/src/core/hostname-setup.c +++ /dev/null @@ -1,62 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include - -#include "alloc-util.h" -#include "fileio.h" -#include "hostname-setup.h" -#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; - int r; - - 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, VALID_HOSTNAME_TRAILING_DOT)) - hn = b; - else { - log_warning("Hostname specified on kernel command line is invalid, ignoring: %s", b); - b = mfree(b); - } - } - - if (!hn) { - r = read_etc_hostname(NULL, &b); - if (r == -ENOENT) - enoent = true; - else if (r < 0) - log_warning_errno(r, "Failed to read configured hostname, ignoring: %m"); - else - hn = b; - } - - if (isempty(hn)) { - /* Don't override the hostname if it is already set and not explicitly configured */ - if (hostname_is_set()) - return 0; - - if (enoent) - log_info("No hostname configured."); - - hn = FALLBACK_HOSTNAME; - } - - r = sethostname_idempotent(hn); - if (r < 0) - return log_warning_errno(r, "Failed to set hostname to <%s>: %m", hn); - - log_info("Set hostname to <%s>.", hn); - return 0; -} diff --git a/src/core/hostname-setup.h b/src/core/hostname-setup.h deleted file mode 100644 index 7fd0a027475..00000000000 --- a/src/core/hostname-setup.h +++ /dev/null @@ -1,4 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -int hostname_setup(void); diff --git a/src/core/meson.build b/src/core/meson.build index 77767eb6033..662e6376f19 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -76,8 +76,6 @@ libcore_sources = ''' execute.h generator-setup.c generator-setup.h - hostname-setup.c - hostname-setup.h ima-setup.c ima-setup.h ip-address-access.c diff --git a/src/fuzz/fuzz-hostname-util.c b/src/fuzz/fuzz-hostname-setup.c similarity index 96% rename from src/fuzz/fuzz-hostname-util.c rename to src/fuzz/fuzz-hostname-setup.c index 0a81e74424f..b8d36da54a6 100644 --- a/src/fuzz/fuzz-hostname-util.c +++ b/src/fuzz/fuzz-hostname-setup.c @@ -4,7 +4,7 @@ #include "fd-util.h" #include "fileio.h" #include "fuzz.h" -#include "hostname-util.h" +#include "hostname-setup.h" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { _cleanup_fclose_ FILE *f = NULL; diff --git a/src/fuzz/meson.build b/src/fuzz/meson.build index 80fa0f6fccf..83527a68fbb 100644 --- a/src/fuzz/meson.build +++ b/src/fuzz/meson.build @@ -123,7 +123,7 @@ fuzzers += [ [libshared], []], - [['src/fuzz/fuzz-hostname-util.c'], + [['src/fuzz/fuzz-hostname-setup.c'], [libshared], []], diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index e624ace7545..10a5d31ccdc 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -17,6 +17,7 @@ #include "env-util.h" #include "fileio-label.h" #include "fileio.h" +#include "hostname-setup.h" #include "hostname-util.h" #include "id128-util.h" #include "main-func.h" diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 14e7a287746..080dcedab54 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -8,6 +8,7 @@ #include "escape.h" #include "alloc-util.h" #include "dhcp-client-internal.h" +#include "hostname-setup.h" #include "hostname-util.h" #include "parse-util.h" #include "network-internal.h" diff --git a/src/network/test-network.c b/src/network/test-network.c index 03c94409fa0..45cd3fa973c 100644 --- a/src/network/test-network.c +++ b/src/network/test-network.c @@ -8,7 +8,7 @@ #include "alloc-util.h" #include "dhcp-lease-internal.h" #include "ether-addr-util.h" -#include "hostname-util.h" +#include "hostname-setup.h" #include "network-internal.h" #include "networkd-manager.h" #include "string-util.h" diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index cfbc8f11bf1..0f6411d1936 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -46,6 +46,7 @@ #include "fs-util.h" #include "gpt.h" #include "hexdecoct.h" +#include "hostname-setup.h" #include "hostname-util.h" #include "id128-util.h" #include "io-util.h" diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 113538bb97a..b1d7d689ea8 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -32,7 +32,7 @@ #include "fsck-util.h" #include "gpt.h" #include "hexdecoct.h" -#include "hostname-util.h" +#include "hostname-setup.h" #include "id128-util.h" #include "mkdir.h" #include "mount-util.h" diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c new file mode 100644 index 00000000000..cd5ad133053 --- /dev/null +++ b/src/shared/hostname-setup.c @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hostname-setup.h" +#include "hostname-util.h" +#include "log.h" +#include "macro.h" +#include "proc-cmdline.h" +#include "string-util.h" +#include "util.h" + +int sethostname_idempotent(const char *s) { + char buf[HOST_NAME_MAX + 1] = {}; + + assert(s); + + if (gethostname(buf, sizeof(buf)) < 0) + return -errno; + + if (streq(buf, s)) + return 0; + + if (sethostname(s, strlen(s)) < 0) + return -errno; + + return 1; +} + +int shorten_overlong(const char *s, char **ret) { + char *h, *p; + + /* Shorten an overlong name to HOST_NAME_MAX or to the first dot, + * whatever comes earlier. */ + + assert(s); + + h = strdup(s); + if (!h) + return -ENOMEM; + + if (hostname_is_valid(h, 0)) { + *ret = h; + return 0; + } + + p = strchr(h, '.'); + if (p) + *p = 0; + + strshorten(h, HOST_NAME_MAX); + + if (!hostname_is_valid(h, 0)) { + free(h); + return -EDOM; + } + + *ret = h; + return 1; +} + +int read_etc_hostname_stream(FILE *f, char **ret) { + int r; + + assert(f); + assert(ret); + + for (;;) { + _cleanup_free_ char *line = NULL; + char *p; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return r; + if (r == 0) /* EOF without any hostname? the file is empty, let's treat that exactly like no file at all: ENOENT */ + return -ENOENT; + + p = strstrip(line); + + /* File may have empty lines or comments, ignore them */ + if (!IN_SET(*p, '\0', '#')) { + char *copy; + + hostname_cleanup(p); /* normalize the hostname */ + + if (!hostname_is_valid(p, VALID_HOSTNAME_TRAILING_DOT)) /* check that the hostname we return is valid */ + return -EBADMSG; + + copy = strdup(p); + if (!copy) + return -ENOMEM; + + *ret = copy; + return 0; + } + } +} + +int read_etc_hostname(const char *path, char **ret) { + _cleanup_fclose_ FILE *f = NULL; + + assert(ret); + + if (!path) + path = "/etc/hostname"; + + f = fopen(path, "re"); + if (!f) + return -errno; + + return read_etc_hostname_stream(f, ret); + +} + +static bool hostname_is_set(void) { + struct utsname u; + + assert_se(uname(&u) >= 0); + + if (isempty(u.nodename)) + return false; + + /* This is the built-in kernel default hostname */ + if (streq(u.nodename, "(none)")) + return false; + + return true; +} + +int hostname_setup(void) { + _cleanup_free_ char *b = NULL; + const char *hn = NULL; + bool enoent = false; + int r; + + 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); + } + } + + 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 */ + if (hostname_is_set()) + return 0; + + if (enoent) + log_info("No hostname configured."); + + hn = FALLBACK_HOSTNAME; + } + + r = sethostname_idempotent(hn); + if (r < 0) + return log_warning_errno(r, "Failed to set hostname to <%s>: %m", hn); + + log_info("Set hostname to <%s>.", hn); + return 0; +} diff --git a/src/shared/hostname-setup.h b/src/shared/hostname-setup.h new file mode 100644 index 00000000000..032c7ac36b4 --- /dev/null +++ b/src/shared/hostname-setup.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +int sethostname_idempotent(const char *s); + +int shorten_overlong(const char *s, char **ret); + +int read_etc_hostname_stream(FILE *f, char **ret); +int read_etc_hostname(const char *path, char **ret); + +int hostname_setup(void); diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c index 671a56b9e99..0b148c067af 100644 --- a/src/shared/machine-image.c +++ b/src/shared/machine-image.c @@ -22,7 +22,7 @@ #include "fd-util.h" #include "fs-util.h" #include "hashmap.h" -#include "hostname-util.h" +#include "hostname-setup.h" #include "id128-util.h" #include "lockfile-util.h" #include "log.h" diff --git a/src/shared/meson.build b/src/shared/meson.build index b43fe9c6d98..b4c77513e22 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -115,6 +115,8 @@ shared_sources = files(''' gpt.h group-record.c group-record.h + hostname-setup.c + hostname-setup.h id128-print.c id128-print.h idn-util.c diff --git a/src/test/meson.build b/src/test/meson.build index 0cfc709f44d..3dab9ace6b0 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -105,17 +105,6 @@ tests += [ libmount, libblkid]], - [['src/test/test-hostname.c'], - [libcore, - libshared], - [threads, - librt, - libseccomp, - libselinux, - libmount, - libblkid], - '', 'unsafe'], - [['src/test/test-dns-domain.c'], [libcore, libshared], @@ -339,6 +328,11 @@ tests += [ [], []], + [['src/test/test-hostname-setup.c'], + [], + [], + '', 'unsafe'], + [['src/test/test-hostname-util.c'], [], []], diff --git a/src/test/test-hostname-setup.c b/src/test/test-hostname-setup.c new file mode 100644 index 00000000000..494ebb2ff5a --- /dev/null +++ b/src/test/test-hostname-setup.c @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "fileio.h" +#include "hostname-setup.h" +#include "string-util.h" +#include "tests.h" +#include "tmpfile-util.h" + +static void test_read_etc_hostname(void) { + char path[] = "/tmp/hostname.XXXXXX"; + char *hostname; + int fd; + + fd = mkostemp_safe(path); + assert(fd > 0); + close(fd); + + /* simple hostname */ + assert_se(write_string_file(path, "foo", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_etc_hostname(path, &hostname) == 0); + assert_se(streq(hostname, "foo")); + hostname = mfree(hostname); + + /* with comment */ + assert_se(write_string_file(path, "# comment\nfoo", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_etc_hostname(path, &hostname) == 0); + assert_se(hostname); + assert_se(streq(hostname, "foo")); + hostname = mfree(hostname); + + /* with comment and extra whitespace */ + assert_se(write_string_file(path, "# comment\n\n foo ", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_etc_hostname(path, &hostname) == 0); + assert_se(hostname); + assert_se(streq(hostname, "foo")); + hostname = mfree(hostname); + + /* cleans up name */ + assert_se(write_string_file(path, "!foo/bar.com", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_etc_hostname(path, &hostname) == 0); + assert_se(hostname); + assert_se(streq(hostname, "foobar.com")); + hostname = mfree(hostname); + + /* no value set */ + hostname = (char*) 0x1234; + assert_se(write_string_file(path, "# nothing here\n", WRITE_STRING_FILE_CREATE) == 0); + assert_se(read_etc_hostname(path, &hostname) == -ENOENT); + assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */ + + /* nonexisting file */ + assert_se(read_etc_hostname("/non/existing", &hostname) == -ENOENT); + assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */ + + unlink(path); +} + +static void test_hostname_setup(void) { + int r; + + r = hostname_setup(); + if (r < 0) + log_error_errno(r, "hostname: %m"); +} + +int main(int argc, char *argv[]) { + test_setup_logging(LOG_INFO); + + test_read_etc_hostname(); + test_hostname_setup(); + + return 0; +} diff --git a/src/test/test-hostname-util.c b/src/test/test-hostname-util.c index c7a63bd047d..24c8ed9e3b3 100644 --- a/src/test/test-hostname-util.c +++ b/src/test/test-hostname-util.c @@ -6,8 +6,8 @@ #include "fileio.h" #include "hostname-util.h" #include "string-util.h" +#include "tests.h" #include "tmpfile-util.h" -#include "util.h" static void test_hostname_is_valid(void) { assert_se(hostname_is_valid("foobar", 0)); @@ -91,55 +91,6 @@ static void test_hostname_cleanup(void) { assert_se(streq(hostname_cleanup(s), "xxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")); } -static void test_read_etc_hostname(void) { - char path[] = "/tmp/hostname.XXXXXX"; - char *hostname; - int fd; - - fd = mkostemp_safe(path); - assert(fd > 0); - close(fd); - - /* simple hostname */ - assert_se(write_string_file(path, "foo", WRITE_STRING_FILE_CREATE) == 0); - assert_se(read_etc_hostname(path, &hostname) == 0); - assert_se(streq(hostname, "foo")); - hostname = mfree(hostname); - - /* with comment */ - assert_se(write_string_file(path, "# comment\nfoo", WRITE_STRING_FILE_CREATE) == 0); - assert_se(read_etc_hostname(path, &hostname) == 0); - assert_se(hostname); - assert_se(streq(hostname, "foo")); - hostname = mfree(hostname); - - /* with comment and extra whitespace */ - assert_se(write_string_file(path, "# comment\n\n foo ", WRITE_STRING_FILE_CREATE) == 0); - assert_se(read_etc_hostname(path, &hostname) == 0); - assert_se(hostname); - assert_se(streq(hostname, "foo")); - hostname = mfree(hostname); - - /* cleans up name */ - assert_se(write_string_file(path, "!foo/bar.com", WRITE_STRING_FILE_CREATE) == 0); - assert_se(read_etc_hostname(path, &hostname) == 0); - assert_se(hostname); - assert_se(streq(hostname, "foobar.com")); - hostname = mfree(hostname); - - /* no value set */ - hostname = (char*) 0x1234; - assert_se(write_string_file(path, "# nothing here\n", WRITE_STRING_FILE_CREATE) == 0); - assert_se(read_etc_hostname(path, &hostname) == -ENOENT); - assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */ - - /* nonexisting file */ - assert_se(read_etc_hostname("/non/existing", &hostname) == -ENOENT); - assert_se(hostname == (char*) 0x1234); /* does not touch argument on error */ - - unlink(path); -} - static void test_hostname_malloc(void) { _cleanup_free_ char *h = NULL, *l = NULL; @@ -158,12 +109,10 @@ static void test_fallback_hostname(void) { } int main(int argc, char *argv[]) { - log_parse_environment(); - log_open(); + test_setup_logging(LOG_INFO); test_hostname_is_valid(); test_hostname_cleanup(); - test_read_etc_hostname(); test_hostname_malloc(); test_fallback_hostname(); diff --git a/src/test/test-hostname.c b/src/test/test-hostname.c deleted file mode 100644 index 1a925f253c1..00000000000 --- a/src/test/test-hostname.c +++ /dev/null @@ -1,14 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "hostname-setup.h" -#include "util.h" - -int main(int argc, char* argv[]) { - int r; - - r = hostname_setup(); - if (r < 0) - log_error_errno(r, "hostname: %m"); - - return 0; -} From b6fad30665c35da470b4389563004c6d72ff1a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 4 Dec 2020 18:45:23 +0100 Subject: [PATCH 05/11] shared/hostname-setup: add mode where we check what would be set, without doing This allows the 'unsafe' mark to be removed from the test. --- src/core/main.c | 2 +- src/shared/hostname-setup.c | 22 ++++++++++++++++------ src/shared/hostname-setup.h | 3 ++- src/test/meson.build | 3 +-- src/test/test-hostname-setup.c | 8 ++------ 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index ef4d03750f7..eaa56aca2a4 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -2064,7 +2064,7 @@ static int initialize_runtime( } status_welcome(); - hostname_setup(); + (void) hostname_setup(true); /* Force transient machine-id on first boot. */ machine_id_setup(NULL, first_boot, arg_machine_id, NULL); (void) loopback_setup(); diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c index cd5ad133053..42a8ada1447 100644 --- a/src/shared/hostname-setup.c +++ b/src/shared/hostname-setup.c @@ -17,7 +17,7 @@ #include "string-util.h" #include "util.h" -int sethostname_idempotent(const char *s) { +static int sethostname_idempotent_full(const char *s, bool really) { char buf[HOST_NAME_MAX + 1] = {}; assert(s); @@ -28,12 +28,17 @@ int sethostname_idempotent(const char *s) { if (streq(buf, s)) return 0; - if (sethostname(s, strlen(s)) < 0) + if (really && + sethostname(s, strlen(s)) < 0) return -errno; return 1; } +int sethostname_idempotent(const char *s) { + return sethostname_idempotent_full(s, true); +} + int shorten_overlong(const char *s, char **ret) { char *h, *p; @@ -134,7 +139,7 @@ static bool hostname_is_set(void) { return true; } -int hostname_setup(void) { +int hostname_setup(bool really) { _cleanup_free_ char *b = NULL; const char *hn = NULL; bool enoent = false; @@ -174,10 +179,15 @@ int hostname_setup(void) { hn = FALLBACK_HOSTNAME; } - r = sethostname_idempotent(hn); + r = sethostname_idempotent_full(hn, really); if (r < 0) return log_warning_errno(r, "Failed to set hostname to <%s>: %m", hn); + if (r == 0) + log_debug("Hostname was already set to <%s>.", hn); + else + log_info("Hostname %s to <%s>.", + really ? "set" : "would have been set", + hn); - log_info("Set hostname to <%s>.", hn); - return 0; + return r; } diff --git a/src/shared/hostname-setup.h b/src/shared/hostname-setup.h index 032c7ac36b4..90637c4b49c 100644 --- a/src/shared/hostname-setup.h +++ b/src/shared/hostname-setup.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include #include int sethostname_idempotent(const char *s); @@ -10,4 +11,4 @@ int shorten_overlong(const char *s, char **ret); int read_etc_hostname_stream(FILE *f, char **ret); int read_etc_hostname(const char *path, char **ret); -int hostname_setup(void); +int hostname_setup(bool really); diff --git a/src/test/meson.build b/src/test/meson.build index 3dab9ace6b0..9254f18a23e 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -330,8 +330,7 @@ tests += [ [['src/test/test-hostname-setup.c'], [], - [], - '', 'unsafe'], + []], [['src/test/test-hostname-util.c'], [], diff --git a/src/test/test-hostname-setup.c b/src/test/test-hostname-setup.c index 494ebb2ff5a..55996500f3b 100644 --- a/src/test/test-hostname-setup.c +++ b/src/test/test-hostname-setup.c @@ -59,15 +59,11 @@ static void test_read_etc_hostname(void) { } static void test_hostname_setup(void) { - int r; - - r = hostname_setup(); - if (r < 0) - log_error_errno(r, "hostname: %m"); + hostname_setup(false); } int main(int argc, char *argv[]) { - test_setup_logging(LOG_INFO); + test_setup_logging(LOG_DEBUG); test_read_etc_hostname(); test_hostname_setup(); From 39ede7cc377dcb057411c80509e5fde8901f20fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 11 Dec 2020 16:52:30 +0100 Subject: [PATCH 06/11] shared/hostname-setup: leave the terminator byte alone gethostname(3) says it's unspecified whether the string is properly terminated when the hostname is too long. We created a buffer with one extra byte, and it seems the intent was to let that byte serve as terminator even if we get an unterminated string from gethostname(). --- src/shared/hostname-setup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c index 42a8ada1447..19e528604bd 100644 --- a/src/shared/hostname-setup.c +++ b/src/shared/hostname-setup.c @@ -22,7 +22,7 @@ static int sethostname_idempotent_full(const char *s, bool really) { assert(s); - if (gethostname(buf, sizeof(buf)) < 0) + if (gethostname(buf, sizeof(buf) - 1) < 0) return -errno; if (streq(buf, s)) From efda832d4f0523cdb544a1a6baf862d0ec57da6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 4 Dec 2020 19:17:45 +0100 Subject: [PATCH 07/11] hostnamed: when hostname is set to existing value, suppress notifications When the hostname is set through network config or such, let's optimize things a bit by suppressing the logs and dbus notifications. --- src/hostname/hostnamed.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 10a5d31ccdc..5ada47459d0 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -359,8 +359,7 @@ static int context_update_kernel_hostname( return r; (void) nscd_flush_cache(STRV_MAKE("hosts")); - - return 0; + return r; /* 0 if no change, 1 if something was done */ } static int context_write_data_static_hostname(Context *c) { @@ -627,12 +626,16 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error * if (r < 0) { log_error_errno(r, "Failed to set hostname: %m"); return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %m"); + } else if (r == 0) + log_debug("Hostname was already set to <%s>.", name); + else { + log_info("Hostname set to <%s>", name); + + (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), + "/org/freedesktop/hostname1", "org.freedesktop.hostname1", + "Hostname", "HostnameSource", NULL); } - log_info("Changed hostname to '%s'", name); - - (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", NULL); - return sd_bus_reply_method_return(m, NULL); } @@ -691,7 +694,8 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_ log_info("Changed static hostname to '%s'", strna(c->data[PROP_STATIC_HOSTNAME])); - (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL); + (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), + "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL); return sd_bus_reply_method_return(m, NULL); } From 536970d4f9828f30d508ac9143f717ea921f99c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 4 Dec 2020 19:40:46 +0100 Subject: [PATCH 08/11] hostnamed: minor style cleanups --- src/hostname/hostnamed.c | 11 ++++------- src/shared/hostname-setup.c | 1 - 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 5ada47459d0..c41d5fd741f 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -68,11 +68,9 @@ typedef struct Context { } Context; static void context_reset(Context *c, uint64_t mask) { - int p; - assert(c); - for (p = 0; p < _PROP_MAX; p++) { + for (int p = 0; p < _PROP_MAX; p++) { if (!FLAGS_SET(mask, UINT64_C(1) << p)) continue; @@ -366,12 +364,11 @@ static int context_write_data_static_hostname(Context *c) { assert(c); if (isempty(c->data[PROP_STATIC_HOSTNAME])) { - if (unlink("/etc/hostname") < 0) return errno == ENOENT ? 0 : -errno; - return 0; } + return write_string_file_atomic_label("/etc/hostname", c->data[PROP_STATIC_HOSTNAME]); } @@ -386,7 +383,7 @@ static int context_write_data_machine_info(Context *c) { }; _cleanup_strv_free_ char **l = NULL; - int r, p; + int r; assert(c); @@ -394,7 +391,7 @@ static int context_write_data_machine_info(Context *c) { if (r < 0 && r != -ENOENT) return r; - for (p = PROP_PRETTY_HOSTNAME; p <= PROP_LOCATION; p++) { + for (int p = PROP_PRETTY_HOSTNAME; p <= PROP_LOCATION; p++) { _cleanup_free_ char *t = NULL; char **u; diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c index 19e528604bd..7eccc86c3bf 100644 --- a/src/shared/hostname-setup.c +++ b/src/shared/hostname-setup.c @@ -121,7 +121,6 @@ int read_etc_hostname(const char *path, char **ret) { return -errno; return read_etc_hostname_stream(f, ret); - } static bool hostname_is_set(void) { From 468695c8cdab09ab8235b43133960c4e1b2e9e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 12 Dec 2020 13:34:48 +0100 Subject: [PATCH 09/11] hostnamed: improve message about static hostname Changed static hostname to 'n/a' is not very nice. --- src/hostname/hostnamed.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index c41d5fd741f..43b5c116124 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -689,7 +689,10 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_ return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %m"); } - log_info("Changed static hostname to '%s'", strna(c->data[PROP_STATIC_HOSTNAME])); + if (c->data[PROP_STATIC_HOSTNAME]) + log_info("Changed static hostname to <%s>", c->data[PROP_STATIC_HOSTNAME]); + else + log_info("Unset static hostname."); (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL); From d39079fcaa05e23540d2b1f0270fa31c22a7e9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 4 Dec 2020 19:56:49 +0100 Subject: [PATCH 10/11] hostnamed: stop discriminating against "localhost" in /etc/hostname We would sometimes ignore localhost-style names in /etc/hostname. That is brittle. If the user configured some hostname, it's most likely because they want to use that as the hostname. If they don't want to use such a hostname, they should just not create the config. Everything becomes simples if we just use the configured hostname as-is. This behaviour seems to have been a workaround for Anaconda installer and other tools writing out /etc/hostname with the default of "localhost.localdomain". Anaconda PR to stop doing that: https://github.com/rhinstaller/anaconda/pull/3040. That might have been useful as a work-around for other programs misbehaving if /etc/hostname was not present, but nowadays it's not useful because systemd mostly controls the hostname and it is perfectly happy without that file. Apart from making things simpler, this allows users to set a hostname like "localhost" and have it honoured, if such a whim strikes them. --- man/hostnamectl.xml | 4 ++-- src/hostname/hostnamed.c | 19 ++++--------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/man/hostnamectl.xml b/man/hostnamectl.xml index f50cefa217e..224dab78a7c 100644 --- a/man/hostnamectl.xml +++ b/man/hostnamectl.xml @@ -39,8 +39,8 @@ and this tool distinguish three different hostnames: the high-level "pretty" hostname which might include all kinds of special characters (e.g. "Lennart's Laptop"), the "static" hostname which is the user-configured hostname (e.g. "lennarts-laptop"), and the transient hostname which is a fallback value - received from network configuration (e.g. "node12345678"). If a static hostname is set, and is valid - (something other than localhost), then the transient hostname is not used. + received from network configuration (e.g. "node12345678"). If a static hostname is set to a valid value, + then the transient hostname is not used. Note that the pretty hostname has little restrictions on the characters and length used, while the static and transient hostnames are limited to the usually accepted characters of Internet domain names, and 64 characters at diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 43b5c116124..9e8a4dd8a42 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -312,15 +312,11 @@ static char* context_fallback_icon_name(Context *c) { return strdup("computer"); } -static bool hostname_is_useful(const char *hn) { - return !isempty(hn) && !is_localhost(hn); -} - static int context_update_kernel_hostname( Context *c, const char *transient_hn) { - const char *static_hn, *hn; + const char *hn; struct utsname u; int r; @@ -333,21 +329,14 @@ static int context_update_kernel_hostname( isempty(u.nodename) || streq(u.nodename, "(none)") ? NULL : u.nodename; } - static_hn = c->data[PROP_STATIC_HOSTNAME]; - - /* /etc/hostname with something other than "localhost" - * has the highest preference ... */ - if (hostname_is_useful(static_hn)) - hn = static_hn; + /* /etc/hostname has the highest preference ... */ + if (c->data[PROP_STATIC_HOSTNAME]) + hn = c->data[PROP_STATIC_HOSTNAME]; /* ... the transient hostname, (ie: DHCP) comes next ... */ else if (!isempty(transient_hn)) hn = transient_hn; - /* ... fallback to static "localhost.*" ignored above ... */ - else if (!isempty(static_hn)) - hn = static_hn; - /* ... and the ultimate fallback */ else hn = FALLBACK_HOSTNAME; From 60e4fb4240b24bdd2d4299d8d844f48093df8807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 4 Dec 2020 19:40:34 +0100 Subject: [PATCH 11/11] hostnamed,shared/hostname-setup: expose the origin of the current hostname MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In hostnamed this is exposed as a dbus property, and in the logs in both places. This is of interest to network management software and such: if the fallback hostname is used, it's not as useful as the real configured thing. Right now various programs try to guess the source of hostname by looking at the string. E.g. "localhost" is assumed to be not the real hostname, but "fedora" is. Any such attempts are bound to fail, because we cannot distinguish "fedora" (a fallback value set by a distro), from "fedora" (received from reverse dns), from "fedora" read from /etc/hostname. /run/systemd/fallback-hostname is written with the fallback hostname when either pid1 or hostnamed sets the kernel hostname to the fallback value. Why remember the fallback value and not the transient hostname in /run/hostname instead? We have three hostname types: "static", "transient", fallback". – Distinguishing "static" is easy: the hostname that is set matches what is in /etc/hostname. – Distingiushing "transient" and "fallback" is not easy. And the "transient" hostname may be set outside of pid1+hostnamed. In particular, it may be set by container manager, some non-systemd tool in the initramfs, or even by a direct call. All those mechanisms count as "transient". Trying to get those cases to write /run/hostname is futile. It is much easier to isolate the "fallback" case which is mostly under our control. And since the file is only used as a flag to mark the hostname as fallback, it can be hidden inside of our /run/systemd directory. For https://bugzilla.redhat.com/show_bug.cgi?id=1892235. --- man/org.freedesktop.hostname1.xml | 8 ++ src/basic/string-table.h | 1 + src/hostname/hostnamed.c | 131 +++++++++++++++++++---------- src/shared/hostname-setup.c | 76 +++++++++++++---- src/shared/hostname-setup.h | 10 +++ units/systemd-hostnamed.service.in | 2 +- 6 files changed, 168 insertions(+), 60 deletions(-) diff --git a/man/org.freedesktop.hostname1.xml b/man/org.freedesktop.hostname1.xml index 8e5c37345d6..a17339ad07a 100644 --- a/man/org.freedesktop.hostname1.xml +++ b/man/org.freedesktop.hostname1.xml @@ -64,6 +64,7 @@ node /org/freedesktop/hostname1 { readonly s PrettyHostname = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s FallbackHostname = '...'; + readonly s HostnameSource = '...'; readonly s IconName = '...'; readonly s Chassis = '...'; readonly s Deployment = '...'; @@ -117,6 +118,8 @@ node /org/freedesktop/hostname1 { + + @@ -171,6 +174,11 @@ node /org/freedesktop/hostname1 { The FallbackHostname property exposes the fallback hostname (configured at compilation time). + The HostnameSource property exposes the origin of the currently configured + hostname. One of static (set from /etc/hostname), + transient (a non-permanent hostname from an external source), + fallback (the compiled-in fallback value). + The IconName property exposes the icon name following the XDG icon naming spec. If not set, information such as the chassis type (see below) is used to find a suitable fallback icon name (i.e. computer-laptop diff --git a/src/basic/string-table.h b/src/basic/string-table.h index 4911a455c58..ddc9b9a5596 100644 --- a/src/basic/string-table.h +++ b/src/basic/string-table.h @@ -78,6 +78,7 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) #define DEFINE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,) +#define DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,) #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,static) #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static) #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,static) diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 9e8a4dd8a42..f0bf29028fa 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -31,6 +31,7 @@ #include "service-util.h" #include "signal-util.h" #include "stat-util.h" +#include "string-table.h" #include "strv.h" #include "user-util.h" #include "util.h" @@ -60,6 +61,8 @@ enum { typedef struct Context { char *data[_PROP_MAX]; + HostnameSource hostname_source; + struct stat etc_hostname_stat; struct stat etc_os_release_stat; struct stat etc_machine_info_stat; @@ -317,35 +320,46 @@ static int context_update_kernel_hostname( const char *transient_hn) { const char *hn; - struct utsname u; + HostnameSource hns; int r; assert(c); - if (!transient_hn) { - /* If no transient hostname is passed in, then let's check what is currently set. */ - assert_se(uname(&u) >= 0); - transient_hn = - isempty(u.nodename) || streq(u.nodename, "(none)") ? NULL : u.nodename; - } - /* /etc/hostname has the highest preference ... */ - if (c->data[PROP_STATIC_HOSTNAME]) + if (c->data[PROP_STATIC_HOSTNAME]) { hn = c->data[PROP_STATIC_HOSTNAME]; + hns = HOSTNAME_STATIC; /* ... the transient hostname, (ie: DHCP) comes next ... */ - else if (!isempty(transient_hn)) + } else if (transient_hn) { hn = transient_hn; + hns = HOSTNAME_TRANSIENT; /* ... and the ultimate fallback */ - else + } else { hn = FALLBACK_HOSTNAME; + hns = HOSTNAME_FALLBACK; + } r = sethostname_idempotent(hn); if (r < 0) - return r; + return log_error_errno(r, "Failed to set hostname: %m"); + + if (c->hostname_source != hns) { + c->hostname_source = hns; + r = 1; + } (void) nscd_flush_cache(STRV_MAKE("hosts")); + + if (r == 0) + log_debug("Hostname was already set to <%s>.", hn); + else { + log_info("Hostname set to <%s> (%s)", hn, hostname_source_to_string(hns)); + + hostname_update_source_hint(hn, hns); + } + return r; /* 0 if no change, 1 if something was done */ } @@ -452,6 +466,50 @@ static int property_get_static_hostname( static BUS_DEFINE_PROPERTY_GET_GLOBAL(property_get_fallback_hostname, "s", FALLBACK_HOSTNAME); +static int property_get_hostname_source( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Context *c = userdata; + int r; + assert(c); + + context_read_etc_hostname(c); + + if (c->hostname_source < 0) { + char hostname[HOST_NAME_MAX + 1] = {}; + _cleanup_free_ char *fallback = NULL; + + (void) get_hostname_filtered(hostname); + + if (streq_ptr(hostname, c->data[PROP_STATIC_HOSTNAME])) + c->hostname_source = HOSTNAME_STATIC; + + else { + /* If the hostname was not set by us, try to figure out where it came from. If we set + * it to the fallback hostname, the file will tell us. We compare the string because + * it is possible that the hostname was set by an older version that had a different + * fallback, in the initramfs or before we reexecuted. */ + + r = read_one_line_file("/run/systemd/fallback-hostname", &fallback); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read /run/systemd/fallback-hostname, ignoring: %m"); + + if (streq_ptr(fallback, hostname)) + c->hostname_source = HOSTNAME_FALLBACK; + else + c->hostname_source = HOSTNAME_TRANSIENT; + } + } + + return sd_bus_message_append(reply, "s", hostname_source_to_string(c->hostname_source)); +} + static int property_get_machine_info_field( sd_bus *bus, const char *path, @@ -570,7 +628,6 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error * Context *c = userdata; const char *name; int interactive, r; - struct utsname u; assert(m); assert(c); @@ -579,20 +636,15 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error * if (r < 0) return r; - context_read_etc_hostname(c); + name = empty_to_null(name); - if (isempty(name)) - name = c->data[PROP_STATIC_HOSTNAME]; - - if (isempty(name)) - name = FALLBACK_HOSTNAME; + /* We always go through with the procedure below without comparing to the current hostname, because + * we might want to adjust hostname source information even if the actual hostname is unchanged. */ if (!hostname_is_valid(name, 0)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name); - assert_se(uname(&u) >= 0); - if (streq_ptr(name, u.nodename)) - return sd_bus_reply_method_return(m, NULL); + context_read_etc_hostname(c); r = bus_verify_polkit_async( m, @@ -609,18 +661,12 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error * return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ r = context_update_kernel_hostname(c, name); - if (r < 0) { - log_error_errno(r, "Failed to set hostname: %m"); + if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %m"); - } else if (r == 0) - log_debug("Hostname was already set to <%s>.", name); - else { - log_info("Hostname set to <%s>", name); - + else if (r > 0) (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "Hostname", "HostnameSource", NULL); - } return sd_bus_reply_method_return(m, NULL); } @@ -645,7 +691,7 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_ if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME])) return sd_bus_reply_method_return(m, NULL); - if (!isempty(name) && !hostname_is_valid(name, 0)) + if (name && !hostname_is_valid(name, 0)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name); r = bus_verify_polkit_async( @@ -666,25 +712,21 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_ if (r < 0) return r; - r = context_update_kernel_hostname(c, NULL); - if (r < 0) { - log_error_errno(r, "Failed to set hostname: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %m"); - } - r = context_write_data_static_hostname(c); if (r < 0) { log_error_errno(r, "Failed to write static hostname: %m"); return sd_bus_error_set_errnof(error, r, "Failed to set static hostname: %m"); } - if (c->data[PROP_STATIC_HOSTNAME]) - log_info("Changed static hostname to <%s>", c->data[PROP_STATIC_HOSTNAME]); - else - log_info("Unset static hostname."); + r = context_update_kernel_hostname(c, NULL); + if (r < 0) { + log_error_errno(r, "Failed to set hostname: %m"); + return sd_bus_error_set_errnof(error, r, "Failed to set hostname: %m"); + } (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), - "/org/freedesktop/hostname1", "org.freedesktop.hostname1", "StaticHostname", NULL); + "/org/freedesktop/hostname1", "org.freedesktop.hostname1", + "StaticHostname", "Hostname", "HostnameSource", NULL); return sd_bus_reply_method_return(m, NULL); } @@ -850,6 +892,7 @@ static const sd_bus_vtable hostname_vtable[] = { SD_BUS_PROPERTY("StaticHostname", "s", property_get_static_hostname, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("PrettyHostname", "s", property_get_machine_info_field, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("FallbackHostname", "s", property_get_fallback_hostname, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("HostnameSource", "s", property_get_hostname_source, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("IconName", "s", property_get_icon_name, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Chassis", "s", property_get_chassis, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Deployment", "s", property_get_machine_info_field, offsetof(Context, data) + sizeof(char*) * PROP_DEPLOYMENT, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), @@ -960,7 +1003,9 @@ static int connect_bus(Context *c, sd_event *event, sd_bus **ret) { } static int run(int argc, char *argv[]) { - _cleanup_(context_destroy) Context context = {}; + _cleanup_(context_destroy) Context context = { + .hostname_source = _HOSTNAME_INVALID, /* appropriate value will be set later */ + }; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c index 7eccc86c3bf..c0465d3dcd1 100644 --- a/src/shared/hostname-setup.c +++ b/src/shared/hostname-setup.c @@ -9,11 +9,13 @@ #include "alloc-util.h" #include "fd-util.h" #include "fileio.h" +#include "fs-util.h" #include "hostname-setup.h" #include "hostname-util.h" #include "log.h" #include "macro.h" #include "proc-cmdline.h" +#include "string-table.h" #include "string-util.h" #include "util.h" @@ -39,6 +41,26 @@ int sethostname_idempotent(const char *s) { return sethostname_idempotent_full(s, true); } +bool get_hostname_filtered(char ret[static HOST_NAME_MAX + 1]) { + char buf[HOST_NAME_MAX + 1] = {}; + + /* Returns true if we got a good hostname, false otherwise. */ + + if (gethostname(buf, sizeof(buf) - 1) < 0) + return false; /* This can realistically only fail with ENAMETOOLONG. + * Let's treat that case the same as an invalid hostname. */ + + if (isempty(buf)) + return false; + + /* This is the built-in kernel default hostname */ + if (streq(buf, "(none)")) + return false; + + memcpy(ret, buf, sizeof buf); + return true; +} + int shorten_overlong(const char *s, char **ret) { char *h, *p; @@ -123,24 +145,26 @@ int read_etc_hostname(const char *path, char **ret) { return read_etc_hostname_stream(f, ret); } -static bool hostname_is_set(void) { - struct utsname u; +void hostname_update_source_hint(const char *hostname, HostnameSource source) { + int r; - assert_se(uname(&u) >= 0); + /* Why save the value and not just create a flag file? This way we will + * notice if somebody sets the hostname directly (not going through hostnamed). + */ - if (isempty(u.nodename)) - return false; - - /* This is the built-in kernel default hostname */ - if (streq(u.nodename, "(none)")) - return false; - - return true; + if (source == HOSTNAME_FALLBACK) { + r = write_string_file("/run/systemd/fallback-hostname", hostname, + WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_ATOMIC); + if (r < 0) + log_warning_errno(r, "Failed to create \"/run/systemd/fallback-hostname\": %m"); + } else + unlink_or_warn("/run/systemd/fallback-hostname"); } int hostname_setup(bool really) { _cleanup_free_ char *b = NULL; const char *hn = NULL; + HostnameSource source; bool enoent = false; int r; @@ -148,9 +172,10 @@ int hostname_setup(bool really) { 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)) + if (hostname_is_valid(b, true)) { hn = b; - else { + source = HOSTNAME_TRANSIENT; + } else { log_warning("Hostname specified on kernel command line is invalid, ignoring: %s", b); b = mfree(b); } @@ -163,19 +188,27 @@ int hostname_setup(bool really) { enoent = true; else log_warning_errno(r, "Failed to read configured hostname: %m"); - } else + } else { hn = b; + source = HOSTNAME_STATIC; + } } if (isempty(hn)) { /* Don't override the hostname if it is already set and not explicitly configured */ - if (hostname_is_set()) + + char buf[HOST_NAME_MAX + 1] = {}; + if (get_hostname_filtered(buf)) { + log_debug("No hostname configured, leaving existing hostname <%s> in place.", buf); return 0; + } if (enoent) - log_info("No hostname configured."); + log_info("No hostname configured, using fallback hostname."); hn = FALLBACK_HOSTNAME; + source = HOSTNAME_FALLBACK; + } r = sethostname_idempotent_full(hn, really); @@ -188,5 +221,16 @@ int hostname_setup(bool really) { really ? "set" : "would have been set", hn); + if (really) + hostname_update_source_hint(hn, source); + return r; } + +static const char* const hostname_source_table[] = { + [HOSTNAME_STATIC] = "static", + [HOSTNAME_TRANSIENT] = "transient", + [HOSTNAME_FALLBACK] = "fallback", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(hostname_source, HostnameSource); diff --git a/src/shared/hostname-setup.h b/src/shared/hostname-setup.h index 90637c4b49c..022f0eb835d 100644 --- a/src/shared/hostname-setup.h +++ b/src/shared/hostname-setup.h @@ -4,6 +4,14 @@ #include #include +typedef enum HostnameSource { + HOSTNAME_STATIC, /* from /etc/hostname */ + HOSTNAME_TRANSIENT, /* a transient hostname set through systemd, hostnamed, the container manager, or otherwise */ + HOSTNAME_FALLBACK, /* the compiled-in fallback was used */ + _HOSTNAME_INVALID = -1, +} HostnameSource; + +const char* hostname_source_to_string(HostnameSource source); int sethostname_idempotent(const char *s); int shorten_overlong(const char *s, char **ret); @@ -11,4 +19,6 @@ int shorten_overlong(const char *s, char **ret); int read_etc_hostname_stream(FILE *f, char **ret); int read_etc_hostname(const char *path, char **ret); +bool get_hostname_filtered(char ret[static HOST_NAME_MAX + 1]); +void hostname_update_source_hint(const char *hostname, HostnameSource source); int hostname_setup(bool really); diff --git a/units/systemd-hostnamed.service.in b/units/systemd-hostnamed.service.in index d3d0efebd0a..222700564e8 100644 --- a/units/systemd-hostnamed.service.in +++ b/units/systemd-hostnamed.service.in @@ -32,7 +32,7 @@ ProtectKernelLogs=yes ProtectKernelModules=yes ProtectKernelTunables=yes ProtectSystem=strict -ReadWritePaths=/etc +ReadWritePaths=/etc /run/systemd RestrictAddressFamilies=AF_UNIX RestrictNamespaces=yes RestrictRealtime=yes