From: Lennart Poettering Date: Fri, 29 May 2026 20:05:03 +0000 (+0200) Subject: hostnamed: add Varlink equivalents for the hostname/machine-info setters X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e3f453b3e95a16e544543a395ddda75bf303e166;p=thirdparty%2Fsystemd.git hostnamed: add Varlink equivalents for the hostname/machine-info setters Add SetHostname(), SetStaticHostname(), SetPrettyHostname(), SetIconName(), SetChassis(), SetDeployment() and SetLocation() to the io.systemd.Hostname Varlink interface, mirroring the existing D-Bus methods of the same names. Each takes a single nullable string "newValue" (null clears/resets the value; the method name itself makes clear what the value is) plus the usual allowInteractiveAuthentication polkit input, and applies the same validation, polkit actions and /etc/hostname resp. /etc/machine-info persistence as its D-Bus counterpart. The five machine-info setters share a common helper. The hostname validator is reused from the D-Bus path, translating its INVALID_ARGS error into a Varlink invalid-parameter error. Being reachable over the socket-activated Varlink socket, these work in early boot before the system bus is up. --- diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 8849843e13e..42fc050f405 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -1347,7 +1347,7 @@ static int property_get_vsock_cid( return sd_bus_message_append(reply, "u", (uint32_t) local_cid); } -static int validate_and_substitute_hostname(const char *name, char **ret_substituted, sd_bus_error *error) { +static int validate_and_substitute_hostname(const char *name, char **ret_substituted) { int r; assert(ret_substituted); @@ -1366,12 +1366,24 @@ static int validate_and_substitute_hostname(const char *name, char **ret_substit return log_error_errno(r, "Failed to substitute wildcards in hostname: %m"); if (!hostname_is_valid(substituted, 0)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name); + return -EUCLEAN; *ret_substituted = TAKE_PTR(substituted); return 1; } +static int bus_validate_and_substitute_hostname(const char *name, char **ret_substituted, sd_bus_error *error) { + int r; + + assert(ret_substituted); + + r = validate_and_substitute_hostname(name, ret_substituted); + if (r == -EUCLEAN) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", name); + + return r; +} + static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) { Context *c = ASSERT_PTR(userdata); const char *name; @@ -1389,7 +1401,7 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error * * we might want to adjust hostname source information even if the actual hostname is unchanged. */ _cleanup_free_ char *substituted = NULL; - r = validate_and_substitute_hostname(name, &substituted, error); + r = bus_validate_and_substitute_hostname(name, &substituted, error); if (r < 0) return r; @@ -1440,7 +1452,7 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_ return sd_bus_reply_method_return(m, NULL); _cleanup_free_ char *substituted = NULL; - r = validate_and_substitute_hostname(name, &substituted, error); + r = bus_validate_and_substitute_hostname(name, &substituted, error); if (r < 0) return r; @@ -2229,6 +2241,252 @@ static int vl_method_describe(sd_varlink *link, sd_json_variant *parameters, sd_ return sd_varlink_reply(link, v); } +static int vl_validate_and_substitute_hostname(sd_varlink *link, const char *name, char **ret_substituted) { + int r; + + assert(link); + assert(ret_substituted); + + r = validate_and_substitute_hostname(name, ret_substituted); + if (r == -EUCLEAN) + return sd_varlink_error_invalid_parameter_name(link, "newValue"); + + return r; +} + +static int vl_method_set_hostname(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "newValue", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0, SD_JSON_NULLABLE }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + Context *c = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + _cleanup_free_ char *value = NULL; + r = sd_varlink_dispatch(link, parameters, dispatch_table, &value); + if (r != 0) + return r; + + const char *name = empty_to_null(value); + + /* 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. */ + + _cleanup_free_ char *substituted = NULL; + r = vl_validate_and_substitute_hostname(link, name, &substituted); + if (r < 0) + return r; + + name = substituted; + + r = varlink_verify_polkit_async( + link, + c->bus, + "org.freedesktop.hostname1.set-hostname", + /* details= */ NULL, + &c->polkit_registry); + if (r <= 0) + return r; + + context_read_etc_hostname(c); + + r = context_update_kernel_hostname(c, name); + if (r < 0) + return r; + if (r > 0) + (void) sd_bus_emit_properties_changed(c->bus, + "/org/freedesktop/hostname1", "org.freedesktop.hostname1", + "Hostname", "HostnameSource", NULL); + + return sd_varlink_reply(link, NULL); +} + +static int vl_method_set_static_hostname(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "newValue", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0, SD_JSON_NULLABLE }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + Context *c = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + _cleanup_free_ char *value = NULL; + r = sd_varlink_dispatch(link, parameters, dispatch_table, &value); + if (r != 0) + return r; + + const char *name = empty_to_null(value); + + context_read_etc_hostname(c); + + if (streq_ptr(name, c->data[PROP_STATIC_HOSTNAME])) + return sd_varlink_reply(link, NULL); + + _cleanup_free_ char *substituted = NULL; + r = vl_validate_and_substitute_hostname(link, name, &substituted); + if (r < 0) + return r; + + r = varlink_verify_polkit_async( + link, + c->bus, + "org.freedesktop.hostname1.set-static-hostname", + /* details= */ NULL, + &c->polkit_registry); + if (r <= 0) + return r; + + r = free_and_strdup_warn(&c->data[PROP_STATIC_HOSTNAME], name); + if (r < 0) + return r; + + free_and_replace(c->data[PROP_STATIC_HOSTNAME_SUBSTITUTED_WILDCARDS], substituted); + + r = context_write_data_static_hostname(c); + if (r < 0) + return r; + + r = context_update_kernel_hostname(c, /* transient_hostname= */ NULL); + if (r < 0) + return r; + + (void) sd_bus_emit_properties_changed(c->bus, + "/org/freedesktop/hostname1", "org.freedesktop.hostname1", + "StaticHostname", "Hostname", "HostnameSource", NULL); + + return sd_varlink_reply(link, NULL); +} + +static int vl_set_machine_info(sd_varlink *link, sd_json_variant *parameters, void *userdata, HostProperty prop) { + static const sd_json_dispatch_field dispatch_table[] = { + { "newValue", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, 0, SD_JSON_NULLABLE }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + Context *c = ASSERT_PTR(userdata); + int r; + + assert(link); + assert(parameters); + + _cleanup_free_ char *value = NULL; + r = sd_varlink_dispatch(link, parameters, dispatch_table, &value); + if (r != 0) + return r; + + const char *name = empty_to_null(value), + *polkit_action = "org.freedesktop.hostname1.set-machine-info", + *bus_property, *human_name; + + switch (prop) { + + case PROP_PRETTY_HOSTNAME: + if (name && string_has_cc(name, NULL)) + return sd_varlink_error_invalid_parameter_name(link, "newValue"); + bus_property = "PrettyHostname"; + human_name = "pretty hostname"; + /* Since the pretty hostname should always be changed at the same time as the static one, use + * the same policy action for both... */ + polkit_action = "org.freedesktop.hostname1.set-static-hostname"; + break; + + case PROP_ICON_NAME: + /* The icon name might ultimately be used as file name, so better be safe than sorry. */ + if (name && !filename_is_valid(name)) + return sd_varlink_error_invalid_parameter_name(link, "newValue"); + bus_property = "IconName"; + human_name = "icon name"; + break; + + case PROP_CHASSIS: + if (name && !valid_chassis(name)) + return sd_varlink_error_invalid_parameter_name(link, "newValue"); + bus_property = "Chassis"; + human_name = "chassis"; + break; + + case PROP_DEPLOYMENT: + if (name && !valid_deployment(name)) + return sd_varlink_error_invalid_parameter_name(link, "newValue"); + bus_property = "Deployment"; + human_name = "deployment"; + break; + + case PROP_LOCATION: + if (name && string_has_cc(name, NULL)) + return sd_varlink_error_invalid_parameter_name(link, "newValue"); + bus_property = "Location"; + human_name = "location"; + break; + + default: + assert_not_reached(); + } + + context_read_machine_info(c); + + if (streq_ptr(name, c->data[prop])) + return sd_varlink_reply(link, NULL); + + r = varlink_verify_polkit_async( + link, + c->bus, + polkit_action, + /* details= */ NULL, + &c->polkit_registry); + if (r <= 0) + return r; + + r = free_and_strdup_warn(&c->data[prop], name); + if (r < 0) + return r; + + r = context_write_data_machine_info(c); + if (r < 0) + return r; + + log_info("Changed %s to '%s'", human_name, strna(c->data[prop])); + + (void) sd_bus_emit_properties_changed( + c->bus, + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1", + bus_property, + NULL); + + return sd_varlink_reply(link, NULL); +} + +static int vl_method_set_pretty_hostname(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_set_machine_info(link, parameters, userdata, PROP_PRETTY_HOSTNAME); +} + +static int vl_method_set_icon_name(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_set_machine_info(link, parameters, userdata, PROP_ICON_NAME); +} + +static int vl_method_set_chassis(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_set_machine_info(link, parameters, userdata, PROP_CHASSIS); +} + +static int vl_method_set_deployment(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_set_machine_info(link, parameters, userdata, PROP_DEPLOYMENT); +} + +static int vl_method_set_location(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + return vl_set_machine_info(link, parameters, userdata, PROP_LOCATION); +} + typedef struct SetTagsParameters { char **set; char **add; @@ -2333,11 +2591,18 @@ static int connect_varlink(Context *c) { r = sd_varlink_server_bind_method_many( c->varlink_server, - "io.systemd.Hostname.Describe", vl_method_describe, - "io.systemd.Hostname.SetTags", vl_method_set_tags, - "io.systemd.service.Ping", varlink_method_ping, - "io.systemd.service.SetLogLevel", varlink_method_set_log_level, - "io.systemd.service.GetEnvironment", varlink_method_get_environment); + "io.systemd.Hostname.Describe", vl_method_describe, + "io.systemd.Hostname.SetHostname", vl_method_set_hostname, + "io.systemd.Hostname.SetStaticHostname", vl_method_set_static_hostname, + "io.systemd.Hostname.SetPrettyHostname", vl_method_set_pretty_hostname, + "io.systemd.Hostname.SetIconName", vl_method_set_icon_name, + "io.systemd.Hostname.SetChassis", vl_method_set_chassis, + "io.systemd.Hostname.SetDeployment", vl_method_set_deployment, + "io.systemd.Hostname.SetLocation", vl_method_set_location, + "io.systemd.Hostname.SetTags", vl_method_set_tags, + "io.systemd.service.Ping", varlink_method_ping, + "io.systemd.service.SetLogLevel", varlink_method_set_log_level, + "io.systemd.service.GetEnvironment", varlink_method_get_environment); if (r < 0) return log_error_errno(r, "Failed to bind Varlink method calls: %m"); diff --git a/src/shared/varlink-io.systemd.Hostname.c b/src/shared/varlink-io.systemd.Hostname.c index e7e03e127ec..ce02f5ecbec 100644 --- a/src/shared/varlink-io.systemd.Hostname.c +++ b/src/shared/varlink-io.systemd.Hostname.c @@ -44,6 +44,48 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_DEFINE_OUTPUT(ProductUUID, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), SD_VARLINK_DEFINE_OUTPUT(VSockCID, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_METHOD( + SetHostname, + SD_VARLINK_FIELD_COMMENT("The transient (runtime) hostname to set. If null the transient hostname is reset, so that the static hostname is used again."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + SetStaticHostname, + SD_VARLINK_FIELD_COMMENT("The static hostname to set, stored in /etc/hostname. If null the static hostname is removed."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + SetPrettyHostname, + SD_VARLINK_FIELD_COMMENT("The pretty (free-form, UTF-8) hostname to set. If null the pretty hostname is removed."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + SetIconName, + SD_VARLINK_FIELD_COMMENT("The icon name to set. If null the icon name is removed."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + SetChassis, + SD_VARLINK_FIELD_COMMENT("The chassis type to set. If null the chassis type is removed."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + SetDeployment, + SD_VARLINK_FIELD_COMMENT("The deployment environment to set. If null the deployment is removed."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + +static SD_VARLINK_DEFINE_METHOD( + SetLocation, + SD_VARLINK_FIELD_COMMENT("The physical location to set. If null the location is removed."), + SD_VARLINK_DEFINE_INPUT(newValue, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT); + static SD_VARLINK_DEFINE_METHOD( SetTags, SD_VARLINK_FIELD_COMMENT("If specified, the machine tag list is first reset to exactly these tags, before the 'add' and 'remove' fields are applied. If null, the current tag list is used as basis instead."), @@ -58,5 +100,19 @@ SD_VARLINK_DEFINE_INTERFACE( io_systemd_Hostname, "io.systemd.Hostname", &vl_method_Describe, + SD_VARLINK_SYMBOL_COMMENT("Sets the transient (runtime) hostname."), + &vl_method_SetHostname, + SD_VARLINK_SYMBOL_COMMENT("Sets the static hostname, stored in /etc/hostname."), + &vl_method_SetStaticHostname, + SD_VARLINK_SYMBOL_COMMENT("Sets the pretty (free-form, UTF-8) hostname."), + &vl_method_SetPrettyHostname, + SD_VARLINK_SYMBOL_COMMENT("Sets the icon name representing this system."), + &vl_method_SetIconName, + SD_VARLINK_SYMBOL_COMMENT("Sets the chassis type of this system."), + &vl_method_SetChassis, + SD_VARLINK_SYMBOL_COMMENT("Sets the deployment environment of this system."), + &vl_method_SetDeployment, + SD_VARLINK_SYMBOL_COMMENT("Sets the physical location of this system."), + &vl_method_SetLocation, SD_VARLINK_SYMBOL_COMMENT("Edits the machine tag list: optionally resets it to 'set', then adds 'add' and removes 'remove'."), &vl_method_SetTags);