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.
This commit is contained in:
Lennart Poettering
2026-06-01 10:56:16 +02:00
parent 0f790ea87f
commit e3f453b3e9
2 changed files with 330 additions and 9 deletions
+274 -9
View File
@@ -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");
+56
View File
@@ -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);