diff --git a/man/org.freedesktop.hostname1.xml b/man/org.freedesktop.hostname1.xml index 8e5c37345d..a17339ad07 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 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/hostname/hostnamed.c b/src/hostname/hostnamed.c index 9e8a4dd8a4..f0bf29028f 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 7eccc86c3b..c0465d3dcd 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 90637c4b49..022f0eb835 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 d3d0efebd0..222700564e 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