hostnamed: add AddAndRemoveTags() D-Bus method
Add a new D-Bus method on org.freedesktop.hostname1 that incrementally updates the machine tags instead of replacing the whole list: it takes two string arrays, one of tags to add and one of tags to remove, and stores (current ∪ add) \ remove. This avoids the read-modify-write race that clients would otherwise hit when several of them each manage a subset of the host's tags. Factor the tag persistence and the write-error-to-D-Bus-error mapping out of method_set_tags() into helpers, and add a machine_tags_add_remove() helper for the set arithmetic, all to be shared with further callers.
This commit is contained in:
@@ -57,6 +57,8 @@ node /org/freedesktop/hostname1 {
|
|||||||
SetLocation(in s location,
|
SetLocation(in s location,
|
||||||
in b interactive);
|
in b interactive);
|
||||||
SetTags(in as tags);
|
SetTags(in as tags);
|
||||||
|
AddAndRemoveTags(in as add,
|
||||||
|
in as remove);
|
||||||
GetProductUUID(in b interactive,
|
GetProductUUID(in b interactive,
|
||||||
out ay uuid);
|
out ay uuid);
|
||||||
GetHardwareSerial(out s serial);
|
GetHardwareSerial(out s serial);
|
||||||
@@ -146,6 +148,8 @@ node /org/freedesktop/hostname1 {
|
|||||||
|
|
||||||
<variablelist class="dbus-method" generated="True" extra-ref="SetTags()"/>
|
<variablelist class="dbus-method" generated="True" extra-ref="SetTags()"/>
|
||||||
|
|
||||||
|
<variablelist class="dbus-method" generated="True" extra-ref="AddAndRemoveTags()"/>
|
||||||
|
|
||||||
<variablelist class="dbus-method" generated="True" extra-ref="GetProductUUID()"/>
|
<variablelist class="dbus-method" generated="True" extra-ref="GetProductUUID()"/>
|
||||||
|
|
||||||
<variablelist class="dbus-method" generated="True" extra-ref="GetHardwareSerial()"/>
|
<variablelist class="dbus-method" generated="True" extra-ref="GetHardwareSerial()"/>
|
||||||
@@ -385,6 +389,13 @@ node /org/freedesktop/hostname1 {
|
|||||||
alphanumeric characters, <literal>-</literal> and <literal>.</literal>). Passing an empty list removes
|
alphanumeric characters, <literal>-</literal> and <literal>.</literal>). Passing an empty list removes
|
||||||
the <varname>TAGS=</varname> field from <filename>/etc/machine-info</filename>.</para>
|
the <varname>TAGS=</varname> field from <filename>/etc/machine-info</filename>.</para>
|
||||||
|
|
||||||
|
<para><function>AddAndRemoveTags()</function> incrementally updates the <varname>Tags</varname>
|
||||||
|
property instead of replacing it. It takes two lists of machine tags: the tags in <varname>add</varname>
|
||||||
|
are added to the current list, and the tags in <varname>remove</varname> are dropped from it (the
|
||||||
|
resulting list is <varname>(current ∪ add) \ remove</varname>, sorted and deduplicated). This is useful
|
||||||
|
when several independent agents each manage a subset of the tags without racing on a read-modify-write
|
||||||
|
of the full list. Each tag must be a valid machine tag, as for <function>SetTags()</function>.</para>
|
||||||
|
|
||||||
<para><varname>PrettyHostname</varname>, <varname>IconName</varname>, <varname>Chassis</varname>,
|
<para><varname>PrettyHostname</varname>, <varname>IconName</varname>, <varname>Chassis</varname>,
|
||||||
<varname>Deployment</varname>, <varname>Location</varname>, and <varname>Tags</varname> are stored in
|
<varname>Deployment</varname>, <varname>Location</varname>, and <varname>Tags</varname> are stored in
|
||||||
<filename>/etc/machine-info</filename>. See
|
<filename>/etc/machine-info</filename>. See
|
||||||
@@ -421,7 +432,8 @@ node /org/freedesktop/hostname1 {
|
|||||||
<function>SetStaticHostname()</function> and <function>SetPrettyHostname()</function> it is
|
<function>SetStaticHostname()</function> and <function>SetPrettyHostname()</function> it is
|
||||||
<interfacename>org.freedesktop.hostname1.set-static-hostname</interfacename>. For
|
<interfacename>org.freedesktop.hostname1.set-static-hostname</interfacename>. For
|
||||||
<function>SetIconName()</function>, <function>SetChassis()</function>, <function>SetDeployment()</function>,
|
<function>SetIconName()</function>, <function>SetChassis()</function>, <function>SetDeployment()</function>,
|
||||||
<function>SetLocation()</function> and <function>SetTags()</function> it is
|
<function>SetLocation()</function>, <function>SetTags()</function> and
|
||||||
|
<function>AddAndRemoveTags()</function> it is
|
||||||
<interfacename>org.freedesktop.hostname1.set-machine-info</interfacename>.</para>
|
<interfacename>org.freedesktop.hostname1.set-machine-info</interfacename>.</para>
|
||||||
</refsect2>
|
</refsect2>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
@@ -527,6 +539,7 @@ node /org/freedesktop/hostname1 {
|
|||||||
<para><varname>OperatingSystemFancyName</varname> was added in version 260.</para>
|
<para><varname>OperatingSystemFancyName</varname> was added in version 260.</para>
|
||||||
<para><function>GetMachineInfo()</function>, <varname>Tags</varname> and
|
<para><function>GetMachineInfo()</function>, <varname>Tags</varname> and
|
||||||
<function>SetTags()</function> were added in version 261.</para>
|
<function>SetTags()</function> were added in version 261.</para>
|
||||||
|
<para><function>AddAndRemoveTags()</function> was added in version 262.</para>
|
||||||
</refsect2>
|
</refsect2>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
||||||
|
|||||||
+145
-20
@@ -1585,6 +1585,52 @@ static int method_set_location(sd_bus_message *m, void *userdata, sd_bus_error *
|
|||||||
return set_machine_info(userdata, m, PROP_LOCATION, method_set_location, error);
|
return set_machine_info(userdata, m, PROP_LOCATION, method_set_location, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int context_store_tags(Context *c, char **tags) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(c);
|
||||||
|
|
||||||
|
/* Persists the given machine tags (which must already be validated, sorted and deduplicated) to
|
||||||
|
* /etc/machine-info and emits a PropertiesChanged signal on the Tags property. */
|
||||||
|
|
||||||
|
if (strv_isempty(tags))
|
||||||
|
c->data[PROP_TAGS] = mfree(c->data[PROP_TAGS]);
|
||||||
|
else {
|
||||||
|
_cleanup_free_ char *j = strv_join(tags, ":");
|
||||||
|
if (!j)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
free_and_replace(c->data[PROP_TAGS], j);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = context_write_data_machine_info(c);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
log_info("Changed tags to '%s'", strempty(c->data[PROP_TAGS]));
|
||||||
|
|
||||||
|
(void) sd_bus_emit_properties_changed(
|
||||||
|
c->bus,
|
||||||
|
"/org/freedesktop/hostname1",
|
||||||
|
"org.freedesktop.hostname1",
|
||||||
|
"Tags",
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int bus_error_from_tags_write(sd_bus_error *error, int r) {
|
||||||
|
assert(error);
|
||||||
|
assert(r < 0);
|
||||||
|
|
||||||
|
log_error_errno(r, "Failed to write machine info: %m");
|
||||||
|
if (ERRNO_IS_PRIVILEGE(r))
|
||||||
|
return sd_bus_error_set(error, BUS_ERROR_FILE_IS_PROTECTED, "Not allowed to update /etc/machine-info.");
|
||||||
|
if (r == -EROFS)
|
||||||
|
return sd_bus_error_set(error, BUS_ERROR_READ_ONLY_FILESYSTEM, "/etc/machine-info is in a read-only filesystem.");
|
||||||
|
return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %m");
|
||||||
|
}
|
||||||
|
|
||||||
static int method_set_tags(sd_bus_message *m, void *userdata, sd_bus_error *error) {
|
static int method_set_tags(sd_bus_message *m, void *userdata, sd_bus_error *error) {
|
||||||
Context *c = ASSERT_PTR(userdata);
|
Context *c = ASSERT_PTR(userdata);
|
||||||
int r;
|
int r;
|
||||||
@@ -1610,7 +1656,12 @@ static int method_set_tags(sd_bus_message *m, void *userdata, sd_bus_error *erro
|
|||||||
|
|
||||||
context_read_machine_info(c);
|
context_read_machine_info(c);
|
||||||
|
|
||||||
if (streq_ptr(empty_to_null(j), empty_to_null(c->data[PROP_TAGS])))
|
_cleanup_strv_free_ char **current = NULL;
|
||||||
|
r = machine_tags_from_string(c->data[PROP_TAGS], /* graceful= */ true, ¤t);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse current machine tags: %m");
|
||||||
|
|
||||||
|
if (strv_equal(current, tags))
|
||||||
return sd_bus_reply_method_return(m, NULL);
|
return sd_bus_reply_method_return(m, NULL);
|
||||||
|
|
||||||
r = bus_verify_polkit_async_full(
|
r = bus_verify_polkit_async_full(
|
||||||
@@ -1626,29 +1677,98 @@ static int method_set_tags(sd_bus_message *m, void *userdata, sd_bus_error *erro
|
|||||||
if (r == 0)
|
if (r == 0)
|
||||||
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
|
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
|
||||||
|
|
||||||
if (strv_isempty(tags))
|
r = context_store_tags(c, tags);
|
||||||
c->data[PROP_TAGS] = mfree(c->data[PROP_TAGS]);
|
if (r < 0)
|
||||||
else
|
return bus_error_from_tags_write(error, r);
|
||||||
free_and_replace(c->data[PROP_TAGS], j);
|
|
||||||
|
|
||||||
r = context_write_data_machine_info(c);
|
return sd_bus_reply_method_return(m, NULL);
|
||||||
if (r < 0) {
|
}
|
||||||
log_error_errno(r, "Failed to write machine info: %m");
|
|
||||||
if (ERRNO_IS_PRIVILEGE(r))
|
static int machine_tags_add_remove(char * const *base, char * const *add, char * const *remove, char ***ret) {
|
||||||
return sd_bus_error_set(error, BUS_ERROR_FILE_IS_PROTECTED, "Not allowed to update /etc/machine-info.");
|
int r;
|
||||||
if (r == -EROFS)
|
|
||||||
return sd_bus_error_set(error, BUS_ERROR_READ_ONLY_FILESYSTEM, "/etc/machine-info is in a read-only filesystem.");
|
assert(ret);
|
||||||
return sd_bus_error_set_errnof(error, r, "Failed to write machine info: %m");
|
|
||||||
|
/* Computes the resulting machine tag list when adding 'add' to and removing 'remove' from the 'base'
|
||||||
|
* list, i.e. (base ∪ add) \ remove, sorted and deduplicated. Shared by the D-Bus AddAndRemoveTags()
|
||||||
|
* and Varlink SetTags() implementations. */
|
||||||
|
|
||||||
|
_cleanup_strv_free_ char **tags = strv_copy(base);
|
||||||
|
if (!tags)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
r = strv_extend_strv(&tags, add, /* filter_duplicates= */ true);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
strv_remove_strv(tags, remove);
|
||||||
|
|
||||||
|
strv_sort_uniq(tags);
|
||||||
|
|
||||||
|
if (strv_length(tags) > MACHINE_TAGS_MAX)
|
||||||
|
return -E2BIG;
|
||||||
|
|
||||||
|
*ret = TAKE_PTR(tags);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int method_add_and_remove_tags(sd_bus_message *m, void *userdata, sd_bus_error *error) {
|
||||||
|
Context *c = ASSERT_PTR(userdata);
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(m);
|
||||||
|
|
||||||
|
_cleanup_strv_free_ char **add = NULL, **remove = NULL;
|
||||||
|
r = sd_bus_message_read_strv(m, &add);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
r = sd_bus_message_read_strv(m, &remove);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (!machine_tag_list_is_valid(add)) {
|
||||||
|
_cleanup_free_ char *j = strv_join(add, ":");
|
||||||
|
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid tags to add '%s'", strna(j));
|
||||||
|
}
|
||||||
|
if (!machine_tag_list_is_valid(remove)) {
|
||||||
|
_cleanup_free_ char *j = strv_join(remove, ":");
|
||||||
|
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid tags to remove '%s'", strna(j));
|
||||||
}
|
}
|
||||||
|
|
||||||
log_info("Changed tags to '%s'", strempty(c->data[PROP_TAGS]));
|
context_read_machine_info(c);
|
||||||
|
|
||||||
(void) sd_bus_emit_properties_changed(
|
/* Start from the current tags, add the requested ones, then drop the ones to be removed. */
|
||||||
sd_bus_message_get_bus(m),
|
_cleanup_strv_free_ char **current = NULL;
|
||||||
"/org/freedesktop/hostname1",
|
r = machine_tags_from_string(c->data[PROP_TAGS], /* graceful= */ true, ¤t);
|
||||||
"org.freedesktop.hostname1",
|
if (r < 0)
|
||||||
"Tags",
|
return log_error_errno(r, "Failed to parse current machine tags: %m");
|
||||||
NULL);
|
|
||||||
|
_cleanup_strv_free_ char **tags = NULL;
|
||||||
|
r = machine_tags_add_remove(current, add, remove, &tags);
|
||||||
|
if (r == -E2BIG)
|
||||||
|
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Too many machine tags specified.");
|
||||||
|
if (r < 0)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
if (strv_equal(current, tags))
|
||||||
|
return sd_bus_reply_method_return(m, NULL);
|
||||||
|
|
||||||
|
r = bus_verify_polkit_async_full(
|
||||||
|
m,
|
||||||
|
"org.freedesktop.hostname1.set-machine-info",
|
||||||
|
/* details= */ NULL,
|
||||||
|
/* good_user= */ UID_INVALID,
|
||||||
|
/* flags= */ 0,
|
||||||
|
&c->polkit_registry,
|
||||||
|
error);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r == 0)
|
||||||
|
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
|
||||||
|
|
||||||
|
r = context_store_tags(c, tags);
|
||||||
|
if (r < 0)
|
||||||
|
return bus_error_from_tags_write(error, r);
|
||||||
|
|
||||||
return sd_bus_reply_method_return(m, NULL);
|
return sd_bus_reply_method_return(m, NULL);
|
||||||
}
|
}
|
||||||
@@ -2009,6 +2129,11 @@ static const sd_bus_vtable hostname_vtable[] = {
|
|||||||
SD_BUS_NO_RESULT,
|
SD_BUS_NO_RESULT,
|
||||||
method_set_tags,
|
method_set_tags,
|
||||||
SD_BUS_VTABLE_UNPRIVILEGED),
|
SD_BUS_VTABLE_UNPRIVILEGED),
|
||||||
|
SD_BUS_METHOD_WITH_ARGS("AddAndRemoveTags",
|
||||||
|
SD_BUS_ARGS("as", add, "as", remove),
|
||||||
|
SD_BUS_NO_RESULT,
|
||||||
|
method_add_and_remove_tags,
|
||||||
|
SD_BUS_VTABLE_UNPRIVILEGED),
|
||||||
SD_BUS_METHOD_WITH_ARGS("GetProductUUID",
|
SD_BUS_METHOD_WITH_ARGS("GetProductUUID",
|
||||||
SD_BUS_ARGS("b", interactive),
|
SD_BUS_ARGS("b", interactive),
|
||||||
SD_BUS_RESULT("ay", uuid),
|
SD_BUS_RESULT("ay", uuid),
|
||||||
|
|||||||
Reference in New Issue
Block a user