diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 36229f3c9bb..7f6607a5270 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -28,6 +28,7 @@ #include "selinux-util.h" #include "service-util.h" #include "signal-util.h" +#include "stat-util.h" #include "strv.h" #include "user-util.h" #include "util.h" @@ -36,54 +37,89 @@ #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:") enum { - PROP_HOSTNAME, + /* Read from /etc/hostname */ PROP_STATIC_HOSTNAME, + + /* Read from /etc/machine-info */ PROP_PRETTY_HOSTNAME, PROP_ICON_NAME, PROP_CHASSIS, PROP_DEPLOYMENT, PROP_LOCATION, + + /* Read from /etc/os-release (or /usr/lib/os-release) */ PROP_OS_PRETTY_NAME, PROP_OS_CPE_NAME, - PROP_HOME_URL, - _PROP_MAX + PROP_OS_HOME_URL, + _PROP_MAX, + _PROP_INVALID = -1, }; typedef struct Context { char *data[_PROP_MAX]; + + struct stat etc_hostname_stat; + struct stat etc_os_release_stat; + struct stat etc_machine_info_stat; + Hashmap *polkit_registry; } Context; -static void context_reset(Context *c) { +static void context_reset(Context *c, uint64_t mask) { int p; assert(c); - for (p = 0; p < _PROP_MAX; p++) + for (p = 0; p < _PROP_MAX; p++) { + if (!FLAGS_SET(mask, UINT64_C(1) << p)) + continue; + c->data[p] = mfree(c->data[p]); + } } static void context_destroy(Context *c) { assert(c); - context_reset(c); + context_reset(c, UINT64_MAX); bus_verify_polkit_async_registry_free(c->polkit_registry); } -static int context_read_data(Context *c) { +static void context_read_etc_hostname(Context *c) { + struct stat current_stat = {}; int r; assert(c); - context_reset(c); + if (stat("/etc/hostname", ¤t_stat) >= 0 && + stat_inode_unmodified(&c->etc_hostname_stat, ¤t_stat)) + return; - c->data[PROP_HOSTNAME] = gethostname_malloc(); - if (!c->data[PROP_HOSTNAME]) - return -ENOMEM; + context_reset(c, UINT64_C(1) << PROP_STATIC_HOSTNAME); r = read_etc_hostname(NULL, &c->data[PROP_STATIC_HOSTNAME]); if (r < 0 && r != -ENOENT) - return r; + log_warning_errno(r, "Failed to read /etc/hostname, ignoring: %m"); + + c->etc_hostname_stat = current_stat; +} + +static void context_read_machine_info(Context *c) { + struct stat current_stat = {}; + int r; + + assert(c); + + if (stat("/etc/machine-info", ¤t_stat) >= 0 && + stat_inode_unmodified(&c->etc_machine_info_stat, ¤t_stat)) + return; + + context_reset(c, + (UINT64_C(1) << PROP_PRETTY_HOSTNAME) | + (UINT64_C(1) << PROP_ICON_NAME) | + (UINT64_C(1) << PROP_CHASSIS) | + (UINT64_C(1) << PROP_DEPLOYMENT) | + (UINT64_C(1) << PROP_LOCATION)); r = parse_env_file(NULL, "/etc/machine-info", "PRETTY_HOSTNAME", &c->data[PROP_PRETTY_HOSTNAME], @@ -92,17 +128,36 @@ static int context_read_data(Context *c) { "DEPLOYMENT", &c->data[PROP_DEPLOYMENT], "LOCATION", &c->data[PROP_LOCATION]); if (r < 0 && r != -ENOENT) - return r; + log_warning_errno(r, "Failed to read /etc/machine-info, ignoring: %m"); + + c->etc_machine_info_stat = current_stat; +} + +static void context_read_os_release(Context *c) { + struct stat current_stat = {}; + int r; + + assert(c); + + if ((stat("/etc/os-release", ¤t_stat) >= 0 || + stat("/usr/lib/os-release", ¤t_stat) >= 0) && + stat_inode_unmodified(&c->etc_os_release_stat, ¤t_stat)) + return; + + context_reset(c, + (UINT64_C(1) << PROP_OS_PRETTY_NAME) | + (UINT64_C(1) << PROP_OS_CPE_NAME) | + (UINT64_C(1) << PROP_OS_HOME_URL)); r = parse_os_release(NULL, "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME], "CPE_NAME", &c->data[PROP_OS_CPE_NAME], - "HOME_URL", &c->data[PROP_HOME_URL], + "HOME_URL", &c->data[PROP_OS_HOME_URL], NULL); if (r < 0 && r != -ENOENT) - return r; + log_warning_errno(r, "Failed to read os-release file, ignoring: %m"); - return 0; + c->etc_os_release_stat = current_stat; } static bool valid_chassis(const char *chassis) { @@ -244,12 +299,22 @@ static bool hostname_is_useful(const char *hn) { return !isempty(hn) && !is_localhost(hn); } -static int context_update_kernel_hostname(Context *c) { - const char *static_hn; - const char *hn; +static int context_update_kernel_hostname( + Context *c, + const char *transient_hn) { + + const char *static_hn, *hn; + struct utsname u; 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" @@ -258,8 +323,8 @@ static int context_update_kernel_hostname(Context *c) { hn = static_hn; /* ... the transient hostname, (ie: DHCP) comes next ... */ - else if (!isempty(c->data[PROP_HOSTNAME])) - hn = c->data[PROP_HOSTNAME]; + else if (!isempty(transient_hn)) + hn = transient_hn; /* ... fallback to static "localhost.*" ignored above ... */ else if (!isempty(static_hn)) @@ -278,7 +343,6 @@ static int context_update_kernel_hostname(Context *c) { } static int context_write_data_static_hostname(Context *c) { - assert(c); if (isempty(c->data[PROP_STATIC_HOSTNAME])) { @@ -342,6 +406,94 @@ static int context_write_data_machine_info(Context *c) { return write_env_file_label("/etc/machine-info", l); } +static int property_get_hostname( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_free_ char *current = NULL; + int r; + + r = gethostname_strict(¤t); + if (r == -ENXIO) + return sd_bus_message_append(reply, "s", FALLBACK_HOSTNAME); + if (r < 0) + return r; + + return sd_bus_message_append(reply, "s", current); +} + +static int property_get_static_hostname( + 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; + assert(c); + + context_read_etc_hostname(c); + + return sd_bus_message_append(reply, "s", c->data[PROP_STATIC_HOSTNAME]); +} + +static int property_get_machine_info_field( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + sd_bus_slot *slot; + Context *c; + + /* Acquire the context object without this property's userdata offset added. Explanation: we want + * access to two pointers here: a) the main context object we cache all properties in, and b) the + * pointer to the property field inside the context object that we are supposed to update and + * use. The latter (b) we get in the 'userdata' function parameter, and sd-bus calculates that for us + * from the 'userdata' pointer we supplied when the vtable was registered, with the offset we + * specified in the vtable added on top. To get the former (a) we need the 'userdata' pointer from + * the vtable registration directly, without the offset added. Hence we ask sd-bus what the slot + * object is (which encapsulates the vtable registration), and then query the 'userdata' field + * directly off it. */ + assert_se(slot = sd_bus_get_current_slot(bus)); + assert_se(c = sd_bus_slot_get_userdata(slot)); + + context_read_machine_info(c); + + return sd_bus_message_append(reply, "s", *(char**) userdata); +} + +static int property_get_os_release_field( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + sd_bus_slot *slot; + Context *c; + + /* As above, acquire the current context without this property's userdata offset added. */ + assert_se(slot = sd_bus_get_current_slot(bus)); + assert_se(c = sd_bus_slot_get_userdata(slot)); + + context_read_os_release(c); + + return sd_bus_message_append(reply, "s", *(char**) userdata); +} + static int property_get_icon_name( sd_bus *bus, const char *path, @@ -355,6 +507,8 @@ static int property_get_icon_name( Context *c = userdata; const char *name; + context_read_machine_info(c); + if (isempty(c->data[PROP_ICON_NAME])) name = n = context_fallback_icon_name(c); else @@ -378,6 +532,8 @@ static int property_get_chassis( Context *c = userdata; const char *name; + context_read_machine_info(c); + if (isempty(c->data[PROP_CHASSIS])) name = fallback_chassis(); else @@ -405,8 +561,8 @@ static int property_get_uname_field( static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) { Context *c = userdata; const char *name; - int interactive; - int r; + int interactive, r; + struct utsname u; assert(m); assert(c); @@ -415,6 +571,8 @@ 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]; @@ -424,7 +582,8 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error * if (!hostname_is_valid(name, false)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name); - if (streq_ptr(name, c->data[PROP_HOSTNAME])) + assert_se(uname(&u) >= 0); + if (streq_ptr(name, u.nodename)) return sd_bus_reply_method_return(m, NULL); r = bus_verify_polkit_async( @@ -441,17 +600,13 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error * if (r == 0) return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - r = free_and_strdup(&c->data[PROP_HOSTNAME], name); - if (r < 0) - return r; - - r = context_update_kernel_hostname(c); + r = context_update_kernel_hostname(c, name); 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"); } - log_info("Changed hostname to '%s'", strna(c->data[PROP_HOSTNAME])); + 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); @@ -473,6 +628,8 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_ name = empty_to_null(name); + context_read_etc_hostname(c); + if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME])) return sd_bus_reply_method_return(m, NULL); @@ -497,7 +654,7 @@ 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); + 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"); @@ -530,6 +687,8 @@ static int set_machine_info(Context *c, sd_bus_message *m, int prop, sd_bus_mess name = empty_to_null(name); + context_read_machine_info(c); + if (streq_ptr(name, c->data[prop])) return sd_bus_reply_method_return(m, NULL); @@ -671,19 +830,19 @@ static int method_get_product_uuid(sd_bus_message *m, void *userdata, sd_bus_err static const sd_bus_vtable hostname_vtable[] = { SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Hostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("StaticHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_STATIC_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("PrettyHostname", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_PRETTY_HOSTNAME, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + 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("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", NULL, offsetof(Context, data) + sizeof(char*) * PROP_DEPLOYMENT, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Location", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_LOCATION, 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), + SD_BUS_PROPERTY("Location", "s", property_get_machine_info_field, offsetof(Context, data) + sizeof(char*) * PROP_LOCATION, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("KernelName", "s", property_get_uname_field, offsetof(struct utsname, sysname), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("KernelRelease", "s", property_get_uname_field, offsetof(struct utsname, release), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("KernelVersion", "s", property_get_uname_field, offsetof(struct utsname, version), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("OperatingSystemCPEName", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("HomeURL", "s", NULL, offsetof(Context, data) + sizeof(char*) * PROP_HOME_URL, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", property_get_os_release_field, offsetof(Context, data) + sizeof(char*) * PROP_OS_PRETTY_NAME, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OperatingSystemCPEName", "s", property_get_os_release_field, offsetof(Context, data) + sizeof(char*) * PROP_OS_CPE_NAME, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("HomeURL", "s", property_get_os_release_field, offsetof(Context, data) + sizeof(char*) * PROP_OS_HOME_URL, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_METHOD_WITH_NAMES("SetHostname", "sb", @@ -826,10 +985,6 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - r = context_read_data(&context); - if (r < 0) - return log_error_errno(r, "Failed to read hostname and machine information: %m"); - r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to run event loop: %m");