diff options
author | Yu Watanabe <watanabe.yu+github@gmail.com> | 2020-12-16 16:55:49 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-16 16:55:49 +0100 |
commit | ecfbbf098c6639ecab5758e34ee91655ec2ccb9a (patch) | |
tree | a4042a8de0f1bcaed0cc1ad0368771c6a2f0af30 /src | |
parent | sd-netlink: routing policy rule port to fib_rule_hdr (diff) | |
parent | hostnamed,shared/hostname-setup: expose the origin of the current hostname (diff) | |
download | systemd-ecfbbf098c6639ecab5758e34ee91655ec2ccb9a.tar.xz systemd-ecfbbf098c6639ecab5758e34ee91655ec2ccb9a.zip |
Merge pull request #17859 from keszybz/hostnamed-export-hostname-origin-and-simplify-logic
Export hostname origin and simplify logic in hostamed
Diffstat (limited to 'src')
-rw-r--r-- | src/basic/hostname-util.c | 120 | ||||
-rw-r--r-- | src/basic/hostname-util.h | 9 | ||||
-rw-r--r-- | src/basic/string-table.h | 1 | ||||
-rw-r--r-- | src/core/hostname-setup.c | 62 | ||||
-rw-r--r-- | src/core/hostname-setup.h | 4 | ||||
-rw-r--r-- | src/core/main.c | 2 | ||||
-rw-r--r-- | src/core/meson.build | 2 | ||||
-rw-r--r-- | src/fuzz/fuzz-hostname-setup.c (renamed from src/fuzz/fuzz-hostname-util.c) | 2 | ||||
-rw-r--r-- | src/fuzz/meson.build | 2 | ||||
-rw-r--r-- | src/hostname/hostnamed.c | 169 | ||||
-rw-r--r-- | src/network/networkd-dhcp4.c | 1 | ||||
-rw-r--r-- | src/network/test-network.c | 2 | ||||
-rw-r--r-- | src/nspawn/nspawn.c | 1 | ||||
-rw-r--r-- | src/shared/dissect-image.c | 2 | ||||
-rw-r--r-- | src/shared/hostname-setup.c | 236 | ||||
-rw-r--r-- | src/shared/hostname-setup.h | 24 | ||||
-rw-r--r-- | src/shared/machine-image.c | 2 | ||||
-rw-r--r-- | src/shared/meson.build | 2 | ||||
-rw-r--r-- | src/test/meson.build | 15 | ||||
-rw-r--r-- | src/test/test-hostname-setup.c | 72 | ||||
-rw-r--r-- | src/test/test-hostname-util.c | 55 | ||||
-rw-r--r-- | src/test/test-hostname.c | 14 |
22 files changed, 456 insertions, 343 deletions
diff --git a/src/basic/hostname-util.c b/src/basic/hostname-util.c index e1e3d1b061..d7aba2c263 100644 --- a/src/basic/hostname-util.c +++ b/src/basic/hostname-util.c @@ -7,28 +7,10 @@ #include <unistd.h> #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 fe417297ee..6cff9c1d4c 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/basic/string-table.h b/src/basic/string-table.h index 4911a455c5..ddc9b9a559 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/core/hostname-setup.c b/src/core/hostname-setup.c deleted file mode 100644 index 6ccaa479de..0000000000 --- a/src/core/hostname-setup.c +++ /dev/null @@ -1,62 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> - -#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 7fd0a02747..0000000000 --- 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/main.c b/src/core/main.c index ef4d03750f..eaa56aca2a 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/core/meson.build b/src/core/meson.build index 77767eb603..662e6376f1 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 index 0a81e74424..b8d36da54a 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 80fa0f6fcc..83527a68fb 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 a0dc25c834..f0bf29028f 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" @@ -16,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" @@ -29,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" @@ -58,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; @@ -66,11 +71,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; @@ -312,63 +315,63 @@ 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; - struct utsname u; + const char *hn; + 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; - } - - 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]; + hns = HOSTNAME_STATIC; /* ... the transient hostname, (ie: DHCP) comes next ... */ - else if (!isempty(transient_hn)) + } else if (transient_hn) { hn = transient_hn; - - /* ... fallback to static "localhost.*" ignored above ... */ - else if (!isempty(static_hn)) - hn = static_hn; + hns = HOSTNAME_TRANSIENT; /* ... and the ultimate fallback */ - else + } else { hn = FALLBACK_HOSTNAME; + hns = HOSTNAME_FALLBACK; + } - if (sethostname_idempotent(hn) < 0) - return -errno; + r = sethostname_idempotent(hn); + if (r < 0) + 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")); - return 0; + 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 */ } 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]); } @@ -383,7 +386,7 @@ static int context_write_data_machine_info(Context *c) { }; _cleanup_strv_free_ char **l = NULL; - int r, p; + int r; assert(c); @@ -391,7 +394,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; @@ -461,6 +464,52 @@ 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_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, @@ -579,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); @@ -588,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); - - if (isempty(name)) - name = c->data[PROP_STATIC_HOSTNAME]; + name = empty_to_null(name); - 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, @@ -618,14 +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"); - } - - 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); + 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); } @@ -650,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( @@ -671,21 +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"); } - log_info("Changed static hostname to '%s'", strna(c->data[PROP_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); + (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), + "/org/freedesktop/hostname1", "org.freedesktop.hostname1", + "StaticHostname", "Hostname", "HostnameSource", NULL); return sd_bus_reply_method_return(m, NULL); } @@ -850,6 +891,8 @@ 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("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/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 14e7a28774..080dcedab5 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 03c94409fa..45cd3fa973 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 cfbc8f11bf..0f6411d193 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 113538bb97..b1d7d689ea 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 0000000000..c0465d3dcd --- /dev/null +++ b/src/shared/hostname-setup.c @@ -0,0 +1,236 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/utsname.h> +#include <unistd.h> + +#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" + +static int sethostname_idempotent_full(const char *s, bool really) { + char buf[HOST_NAME_MAX + 1] = {}; + + assert(s); + + if (gethostname(buf, sizeof(buf) - 1) < 0) + return -errno; + + if (streq(buf, s)) + return 0; + + if (really && + sethostname(s, strlen(s)) < 0) + return -errno; + + return 1; +} + +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; + + /* 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); +} + +void hostname_update_source_hint(const char *hostname, HostnameSource source) { + int r; + + /* 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 (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; + + 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; + source = HOSTNAME_TRANSIENT; + } 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; + source = HOSTNAME_STATIC; + } + } + + if (isempty(hn)) { + /* Don't override the hostname if it is already set and not explicitly configured */ + + 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, using fallback hostname."); + + hn = FALLBACK_HOSTNAME; + source = HOSTNAME_FALLBACK; + + } + + 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); + + 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 new file mode 100644 index 0000000000..022f0eb835 --- /dev/null +++ b/src/shared/hostname-setup.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <stdbool.h> +#include <stdio.h> + +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); + +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/src/shared/machine-image.c b/src/shared/machine-image.c index 671a56b9e9..0b148c067a 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 b43fe9c6d9..b4c77513e2 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 0cfc709f44..9254f18a23 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,10 @@ tests += [ [], []], + [['src/test/test-hostname-setup.c'], + [], + []], + [['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 0000000000..55996500f3 --- /dev/null +++ b/src/test/test-hostname-setup.c @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#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) { + hostname_setup(false); +} + +int main(int argc, char *argv[]) { + test_setup_logging(LOG_DEBUG); + + 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 c7a63bd047..24c8ed9e3b 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 1a925f253c..0000000000 --- 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; -} |