hostnamed: allow setting machine tags via udev rules (#42390)

This gives hostnamed a lot of love, and makes it possibly to "auto-tag"
a machine via a udev rule.

It builds on the machine tag concept added in v261.
This commit is contained in:
Lennart Poettering
2026-06-19 20:38:42 +02:00
committed by GitHub
15 changed files with 978 additions and 73 deletions
+2 -6
View File
@@ -265,9 +265,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
- sysupdate: add concept for enabling/disabling specific components explicitly,
just like features.
- udev: add a MACHINE_TAGS field, that augments /etc/machine-info configured
tags.
- hostnamectl: management, collation of all tags. four sources: udev,
/etc/machine-info, credentials, and /etc/machine-tags.d/*.conf
@@ -780,7 +777,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
- clean up date formatting and parsing so that all absolute/relative timestamps we format can also be parsed
- **complete varlink introspection comments:**
- io.systemd.Hostname
- io.systemd.ManagedOOM
- io.systemd.Network
- io.systemd.PCRLock
@@ -2431,8 +2427,8 @@ SPDX-License-Identifier: LGPL-2.1-or-later
PR_SET_DUMPABLE so that it cannot be ptraced from the host. Should have
CAP_SYS_BPF as only service around.
- SIGRTMIN+18 and memory pressure handling should still be added to: hostnamed,
localed, oomd, timedated.
- SIGRTMIN+18 and memory pressure handling should still be added to: localed,
oomd, timedated.
- socket units: allow creating a udev monitor socket with ListenDevices= or so,
with matches, then activate app through that passing socket over
+8
View File
@@ -177,6 +177,14 @@
resulting list is sorted before being stored. To remove all tags, invoke the command with a single
empty string argument.</para>
<para>Alternatively, each argument may be prefixed with <literal>+</literal> or <literal>-</literal>
to incrementally update the configured tags rather than replacing them: an argument prefixed with
<literal>+</literal> adds all of its (colon-separated) tags to the list, one prefixed with
<literal>-</literal> removes them. For example, <command>hostnamectl tags +foo:bar -quux:ooo</command>
adds the tags <literal>foo</literal> and <literal>bar</literal> and removes <literal>quux</literal>
and <literal>ooo</literal>. The prefix applies to the whole argument, i.e. to every tag listed within
it. Prefixed and unprefixed arguments may not be combined in a single invocation.</para>
<para>Machine tags are short labels that may be used to classify and group machines for management
purposes, for example to identify the role a machine plays in a deployment, the fleet or
organizational unit it belongs to, or any other administrator-defined attribute. Each individual tag
+14 -1
View File
@@ -57,6 +57,8 @@ node /org/freedesktop/hostname1 {
SetLocation(in s location,
in b interactive);
SetTags(in as tags);
AddAndRemoveTags(in as add,
in as remove);
GetProductUUID(in b interactive,
out ay uuid);
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="AddAndRemoveTags()"/>
<variablelist class="dbus-method" generated="True" extra-ref="GetProductUUID()"/>
<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
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>,
<varname>Deployment</varname>, <varname>Location</varname>, and <varname>Tags</varname> are stored in
<filename>/etc/machine-info</filename>. See
@@ -421,7 +432,8 @@ node /org/freedesktop/hostname1 {
<function>SetStaticHostname()</function> and <function>SetPrettyHostname()</function> it is
<interfacename>org.freedesktop.hostname1.set-static-hostname</interfacename>. For
<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>
</refsect2>
</refsect1>
@@ -527,6 +539,7 @@ node /org/freedesktop/hostname1 {
<para><varname>OperatingSystemFancyName</varname> was added in version 260.</para>
<para><function>GetMachineInfo()</function>, <varname>Tags</varname> and
<function>SetTags()</function> were added in version 261.</para>
<para><function>AddAndRemoveTags()</function> was added in version 262.</para>
</refsect2>
</refsect1>
+1
View File
@@ -1107,6 +1107,7 @@ manpages = [
['systemd-loop@.service', '8', [], ''],
['systemd-machine-id-commit.service', '8', [], ''],
['systemd-machine-id-setup', '1', [], ''],
['systemd-machine-tag@.service', '8', [], 'ENABLE_HOSTNAMED'],
['systemd-machined.service', '8', ['systemd-machined'], 'ENABLE_MACHINED'],
['systemd-makefs@.service',
'8',
+65
View File
@@ -0,0 +1,65 @@
<?xml version="1.0"?>
<!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-machine-tag_.service" conditional='ENABLE_HOSTNAMED'
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-machine-tag@.service</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-machine-tag@.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-machine-tag@.service</refname>
<refpurpose>Add a machine tag to the host</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-machine-tag@.service</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para><filename>systemd-machine-tag@<replaceable>TAG</replaceable>.service</filename> is a service
template that adds the machine tag <replaceable>TAG</replaceable> (taken from the instance name) to the
host's list of machine tags. Whenever an instance of this template is pulled in, the corresponding tag is
added; the operation is idempotent, so pulling it in repeatedly has no further effect.</para>
<para>The primary use of this template is automatic, hardware-driven tagging: the
<varname>SYSTEMD_MACHINE_TAG</varname> udev property, when set on a device, causes the matching instance
of this template to be pulled in (via <varname>SYSTEMD_WANTS=</varname>), so that plugging in or
detecting a specific device automatically tags the host. See
<citerefentry><refentrytitle>systemd.device</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details on the <varname>SYSTEMD_WANTS=</varname> mechanism.</para>
<para>Machine tags may be inspected and edited with the <command>tags</command> verb of
<citerefentry><refentrytitle>hostnamectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, and are
stored in the <varname>TAGS=</varname> field of
<citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry>. Each
tag must be 1…255 characters long and consist only of ASCII alphanumeric characters,
<literal>-</literal> and <literal>.</literal>.</para>
<xi:include href="version-info.xml" xpointer="v262"/>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>hostnamectl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-hostnamed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>machine-info</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>
+3
View File
@@ -98,4 +98,7 @@ SUBSYSTEM=="tpm", KERNEL=="tpm[0-9]*", TAG+="systemd"
SUBSYSTEM=="block", ENV{ID_CDROM}=="1", ENV{ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP}=="1", \
ENV{SYSTEMD_WANTS}+="systemd-loop@.service"
# A device carrying the SYSTEMD_MACHINE_TAG property auto-tags the host with the specified machine tag.
ENV{SYSTEMD_MACHINE_TAG}=="?*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="systemd-machine-tag@$env{SYSTEMD_MACHINE_TAG}.service"
LABEL="systemd_end"
+13 -4
View File
@@ -812,16 +812,14 @@ bool strv_is_uniq(char * const *l) {
}
char** strv_remove(char **l, const char *s) {
char **f, **t;
if (!l)
return NULL;
assert(s);
/* Drops every occurrence of s in the string list, edits
* in-place. */
/* Drops every occurrence of s in the string list, edits in-place. */
char **f, **t;
for (f = t = l; *f; f++)
if (streq(*f, s))
free(*f);
@@ -832,6 +830,17 @@ char** strv_remove(char **l, const char *s) {
return l;
}
char** strv_remove_strv(char **l, char *const*ll) {
if (strv_isempty(l))
return l;
STRV_FOREACH(i, ll)
strv_remove(l, *i);
return l;
}
bool strv_overlap(char * const *a, char * const *b) {
POINTER_MAY_BE_NULL(a);
POINTER_MAY_BE_NULL(b);
+1
View File
@@ -85,6 +85,7 @@ int strv_consume_pair(char ***l, char *a, char *b);
int strv_consume_prepend(char ***l, char *value);
char** strv_remove(char **l, const char *s);
char** strv_remove_strv(char **l, char *const*ll);
char** strv_uniq(char **l);
bool strv_is_uniq(char * const *l) _pure_;
+55 -5
View File
@@ -772,25 +772,75 @@ static int verb_get_or_set_tags(int argc, char *argv[], uintptr_t _data, void *u
return 0;
}
_cleanup_strv_free_ char **l = NULL;
_cleanup_strv_free_ char **plain = NULL, **add = NULL, **remove = NULL;
bool prefixed = false, unprefixed = false;
for (int i = 1; i < argc; i++) {
r = strv_split_and_extend(&l, argv[i], ":", /* filter_duplicates= */ true);
if (r < 0)
const char *e = argv[i];
char ***dest;
/* The first character of each argument selects the mode for all (colon-separated) tags in
* that argument: a leading '+' adds them, a leading '-' removes them, and otherwise the
* argument lists tags to set verbatim. Setting and adding/removing may not be mixed. */
if (e[0] == '+') {
dest = &add;
e++;
prefixed = true;
} else if (e[0] == '-') {
dest = &remove;
e++;
prefixed = true;
} else {
dest = &plain;
unprefixed = true;
}
if (isempty(e)) {
if (dest == &plain && !*dest) {
*dest = new0(char*, 1);
if (!*dest)
return log_oom();
}
} else if (strv_split_and_extend(dest, e, ":", /* filter_duplicates= */ true) < 0)
return log_oom();
}
strv_sort(l);
if (prefixed && unprefixed)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Plain tags may not be combined with '+'/'-' prefixed tags.");
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
if (!prefixed) {
/* No prefixes used: replace the whole tag list with the given tags (possibly empty, which
* clears all tags). */
strv_sort(plain);
r = bus_message_new_method_call(bus, &m, bus_hostname, "SetTags");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, l);
r = sd_bus_message_append_strv(m, plain);
if (r < 0)
return bus_log_create_error(r);
} else {
/* Prefixes used: incrementally add and/or remove tags. */
strv_sort(add);
strv_sort(remove);
r = bus_message_new_method_call(bus, &m, bus_hostname, "AddAndRemoveTags");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, add);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_append_strv(m, remove);
if (r < 0)
return bus_log_create_error(r);
}
r = sd_bus_call(bus, m, /* usec= */ 0, &error, /* ret_reply= */ NULL);
if (r < 0)
+541 -47
View File
@@ -17,6 +17,7 @@
#include "bus-object.h"
#include "bus-polkit.h"
#include "bus-util.h"
#include "common-signal.h"
#include "constants.h"
#include "daemon-util.h"
#include "device-private.h"
@@ -36,7 +37,6 @@
#include "nulstr-util.h"
#include "os-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "service-util.h"
#include "stat-util.h"
#include "string-util.h"
@@ -541,7 +541,7 @@ static int get_firmware_date(Context *c, usec_t *ret) {
return 0;
}
static const char* valid_chassis(const char *chassis) {
static const char* chassis_is_valid(const char *chassis) {
assert(chassis);
return nulstr_get(
@@ -558,10 +558,29 @@ static const char* valid_chassis(const char *chassis) {
chassis);
}
static bool valid_deployment(const char *deployment) {
static bool deployment_is_valid(const char *deployment) {
assert(deployment);
return in_charset(deployment, VALID_DEPLOYMENT_CHARS);
return !isempty(deployment) &&
in_charset(deployment, VALID_DEPLOYMENT_CHARS);
}
static bool pretty_hostname_is_valid(const char *pretty_hostname) {
assert(pretty_hostname);
return string_is_safe(pretty_hostname, STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS);
}
static bool icon_name_is_valid(const char *icon_name) {
assert(icon_name);
return string_is_safe(icon_name, STRING_FILENAME);
}
static bool location_is_valid(const char *location) {
assert(location);
return string_is_safe(location, STRING_ALLOW_BACKSLASHES|STRING_ALLOW_QUOTES|STRING_ALLOW_GLOBS);
}
static const char* fallback_chassis_by_virtualization(void) {
@@ -710,7 +729,7 @@ static const char* fallback_chassis_by_device_tree(Context *c) {
*
* https://github.com/devicetree-org/devicetree-specification/blob/master/source/chapter3-devicenodes.rst */
chassis = valid_chassis(type);
chassis = chassis_is_valid(type);
if (!chassis)
log_debug("Invalid device-tree chassis type \"%s\", ignoring.", type);
return chassis;
@@ -828,7 +847,7 @@ static int context_write_data_static_hostname(Context *c) {
if (isempty(c->data[PROP_STATIC_HOSTNAME])) {
if (unlink(etc_hostname()) < 0 && errno != ENOENT)
return -errno;
return log_error_errno(errno, "Failed to remove '%s': %m", etc_hostname());
TAKE_PTR(s);
return 0;
@@ -836,7 +855,7 @@ static int context_write_data_static_hostname(Context *c) {
r = write_string_file(etc_hostname(), c->data[PROP_STATIC_HOSTNAME], WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL);
if (r < 0)
return r;
return log_error_errno(r, "Failed to write '%s': %m", etc_hostname());
TAKE_PTR(s);
return 0;
@@ -863,19 +882,18 @@ static int context_write_data_machine_info(Context *c) {
r = load_env_file(NULL, etc_machine_info(), &l);
if (r < 0 && r != -ENOENT)
return r;
return log_error_errno(r, "Failed to read '%s': %m", etc_machine_info());
for (HostProperty p = _PROP_MACHINE_INFO_SETTABLE_FIRST; p <= _PROP_MACHINE_INFO_SETTABLE_LAST; p++) {
assert(name[p]);
r = strv_env_assign(&l, name[p], empty_to_null(c->data[p]));
if (r < 0)
return r;
if (strv_env_assign(&l, name[p], empty_to_null(c->data[p])) < 0)
return log_oom();
}
if (strv_isempty(l)) {
if (unlink(etc_machine_info()) < 0 && errno != ENOENT)
return -errno;
return log_error_errno(errno, "Failed to unlink '%s': %m", etc_machine_info());
TAKE_PTR(s);
return 0;
@@ -888,7 +906,7 @@ static int context_write_data_machine_info(Context *c) {
l,
WRITE_ENV_FILE_LABEL);
if (r < 0)
return r;
return log_error_errno(r, "Failed to write '%s': %m", etc_machine_info());
TAKE_PTR(s);
return 0;
@@ -1348,7 +1366,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);
@@ -1367,12 +1385,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;
@@ -1390,7 +1420,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;
@@ -1441,7 +1471,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;
@@ -1466,7 +1496,6 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_
r = context_write_data_static_hostname(c);
if (r < 0) {
log_error_errno(r, "Failed to write static hostname: %m");
if (ERRNO_IS_PRIVILEGE(r))
return sd_bus_error_set(error, BUS_ERROR_FILE_IS_PROTECTED, "Not allowed to update /etc/hostname.");
if (r == -EROFS)
@@ -1475,10 +1504,8 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_
}
r = context_update_kernel_hostname(c, /* transient_hostname= */ NULL);
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");
}
(void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m),
"/org/freedesktop/hostname1", "org.freedesktop.hostname1",
@@ -1510,15 +1537,15 @@ static int set_machine_info(Context *c, sd_bus_message *m, int prop, sd_bus_mess
/* The icon name might ultimately be used as file
* name, so better be safe than sorry */
if (prop == PROP_ICON_NAME && !filename_is_valid(name))
if (prop == PROP_ICON_NAME && !icon_name_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid icon name '%s'", name);
if (prop == PROP_PRETTY_HOSTNAME && string_has_cc(name, NULL))
if (prop == PROP_PRETTY_HOSTNAME && !pretty_hostname_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid pretty hostname '%s'", name);
if (prop == PROP_CHASSIS && !valid_chassis(name))
if (prop == PROP_CHASSIS && !chassis_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid chassis '%s'", name);
if (prop == PROP_DEPLOYMENT && !valid_deployment(name))
if (prop == PROP_DEPLOYMENT && !deployment_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid deployment '%s'", name);
if (prop == PROP_LOCATION && string_has_cc(name, NULL))
if (prop == PROP_LOCATION && !location_is_valid(name))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid location '%s'", name);
}
@@ -1544,7 +1571,6 @@ static int set_machine_info(Context *c, sd_bus_message *m, int prop, sd_bus_mess
r = context_write_data_machine_info(c);
if (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)
@@ -1590,6 +1616,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);
}
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) {
Context *c = ASSERT_PTR(userdata);
int r;
@@ -1615,7 +1687,12 @@ static int method_set_tags(sd_bus_message *m, void *userdata, sd_bus_error *erro
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, &current);
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);
r = bus_verify_polkit_async_full(
@@ -1631,29 +1708,98 @@ static int method_set_tags(sd_bus_message *m, void *userdata, sd_bus_error *erro
if (r == 0)
return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
if (strv_isempty(tags))
c->data[PROP_TAGS] = mfree(c->data[PROP_TAGS]);
else
free_and_replace(c->data[PROP_TAGS], j);
r = context_store_tags(c, tags);
if (r < 0)
return bus_error_from_tags_write(error, r);
r = context_write_data_machine_info(c);
if (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");
return sd_bus_reply_method_return(m, NULL);
}
log_info("Changed tags to '%s'", strempty(c->data[PROP_TAGS]));
static int machine_tags_add_remove(char * const *base, char * const *add, char * const *remove, char ***ret) {
int r;
(void) sd_bus_emit_properties_changed(
sd_bus_message_get_bus(m),
"/org/freedesktop/hostname1",
"org.freedesktop.hostname1",
"Tags",
NULL);
assert(ret);
/* 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));
}
context_read_machine_info(c);
/* Start from the current tags, add the requested ones, then drop the ones to be removed. */
_cleanup_strv_free_ char **current = NULL;
r = machine_tags_from_string(c->data[PROP_TAGS], /* graceful= */ true, &current);
if (r < 0)
return log_error_errno(r, "Failed to parse current machine tags: %m");
_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);
}
@@ -2014,6 +2160,11 @@ static const sd_bus_vtable hostname_vtable[] = {
SD_BUS_NO_RESULT,
method_set_tags,
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_ARGS("b", interactive),
SD_BUS_RESULT("ay", uuid),
@@ -2109,6 +2260,333 @@ 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 && !pretty_hostname_is_valid(name))
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 && !icon_name_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 && !chassis_is_valid(name))
return sd_varlink_error_invalid_parameter_name(link, "newValue");
bus_property = "Chassis";
human_name = "chassis";
break;
case PROP_DEPLOYMENT:
if (name && !deployment_is_valid(name))
return sd_varlink_error_invalid_parameter_name(link, "newValue");
bus_property = "Deployment";
human_name = "deployment";
break;
case PROP_LOCATION:
if (name && !location_is_valid(name))
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;
char **remove;
} SetTagsParameters;
static void set_tags_parameters_done(SetTagsParameters *p) {
assert(p);
strv_free(p->set);
strv_free(p->add);
strv_free(p->remove);
}
static int vl_method_set_tags(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "set", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(SetTagsParameters, set), SD_JSON_NULLABLE },
{ "add", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(SetTagsParameters, add), SD_JSON_NULLABLE },
{ "remove", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(SetTagsParameters, remove), SD_JSON_NULLABLE },
VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
Context *c = ASSERT_PTR(userdata);
int r;
assert(link);
assert(parameters);
_cleanup_(set_tags_parameters_done) SetTagsParameters p = {};
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
/* Both an absent 'set' field and an explicit null map to a NULL strv after dispatching, but only the
* former should keep the current tags — an explicit (possibly empty) 'set' resets the list. Hence
* check for the field's presence separately. */
bool reset = sd_json_variant_by_key(parameters, "set");
if (reset && !machine_tag_list_is_valid(p.set))
return sd_varlink_error_invalid_parameter_name(link, "set");
if (!machine_tag_list_is_valid(p.add))
return sd_varlink_error_invalid_parameter_name(link, "add");
if (!machine_tag_list_is_valid(p.remove))
return sd_varlink_error_invalid_parameter_name(link, "remove");
context_read_machine_info(c);
_cleanup_strv_free_ char **current = NULL;
r = machine_tags_from_string(c->data[PROP_TAGS], /* graceful= */ true, &current);
if (r < 0)
return r;
/* Use either the explicitly specified tag list or the current one as the basis, then apply the
* additions and removals on top. */
_cleanup_strv_free_ char **tags = NULL;
r = machine_tags_add_remove(reset ? p.set : current, p.add, p.remove, &tags);
if (r == -E2BIG)
return sd_varlink_error_invalid_parameter_name(link, "add");
if (r < 0)
return r;
if (strv_equal(tags, current))
return sd_varlink_reply(link, NULL);
r = varlink_verify_polkit_async(
link,
c->bus,
"org.freedesktop.hostname1.set-machine-info",
/* details= */ NULL,
&c->polkit_registry);
if (r <= 0)
return r;
r = context_store_tags(c, tags);
if (r < 0)
return r;
return sd_varlink_reply(link, NULL);
}
static int connect_varlink(Context *c) {
int r;
@@ -2133,6 +2611,14 @@ 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.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);
@@ -2207,6 +2693,14 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m");
r = sd_event_add_signal(context.event, /* ret= */ NULL, (SIGRTMIN+18)|SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, /* userdata= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to install SIGRTMIN+18 handler: %m");
r = sd_event_add_memory_pressure(context.event, /* ret= */ NULL, /* callback= */ NULL, /* userdata= */ NULL);
if (r < 0)
log_debug_errno(r, "Failed to allocate memory pressure event source, ignoring: %m");
r = connect_bus(&context);
if (r < 0)
return r;
+102 -1
View File
@@ -6,45 +6,146 @@
static SD_VARLINK_DEFINE_METHOD(
Describe,
VARLINK_DEFINE_POLKIT_INPUT,
SD_VARLINK_FIELD_COMMENT("The current system hostname."),
SD_VARLINK_DEFINE_OUTPUT(Hostname, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("The statically configured hostname, from /etc/hostname. Null if none is configured."),
SD_VARLINK_DEFINE_OUTPUT(StaticHostname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The pretty (free-form, UTF-8) hostname. Null if none is configured."),
SD_VARLINK_DEFINE_OUTPUT(PrettyHostname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The fallback hostname used when neither a static nor a transient hostname is set."),
SD_VARLINK_DEFINE_OUTPUT(DefaultHostname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Indicates where the current hostname originates from, one of 'static', 'transient' or 'default'."),
SD_VARLINK_DEFINE_OUTPUT(HostnameSource, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("The name of an icon representing this system, following the XDG icon naming spec. Null if none is configured."),
SD_VARLINK_DEFINE_OUTPUT(IconName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The chassis type of this system, e.g. 'desktop', 'laptop', 'server', 'vm', … Null if unknown."),
SD_VARLINK_DEFINE_OUTPUT(Chassis, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("An unique identifier of the system chassis."),
SD_VARLINK_DEFINE_OUTPUT(ChassisAssetTag, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The deployment environment of this system, e.g. 'production' or 'staging'. Null if none is configured."),
SD_VARLINK_DEFINE_OUTPUT(Deployment, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("A human-readable description of the physical location of this system. Null if none is configured."),
SD_VARLINK_DEFINE_OUTPUT(Location, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The operating system kernel name, as reported by uname(2), e.g. 'Linux'."),
SD_VARLINK_DEFINE_OUTPUT(KernelName, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("The kernel release string, as reported by uname(2)."),
SD_VARLINK_DEFINE_OUTPUT(KernelRelease, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("The kernel version string, as reported by uname(2)."),
SD_VARLINK_DEFINE_OUTPUT(KernelVersion, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("'Pretty' name of the OS. This is the primary OS identifier that is suitable for presentation to the user. Typically includes a version too, but doesn't have to."),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemPrettyName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("'Fancy' name of the OS; may contain non-ASCII Unicode chars, as well as basic ANSI sequences. This is similar to 'OperatingSystemPrettyName', but is preferably used on terminals that support ANSI sequences and full Unicode."),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemFancyName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The CPE name of the OS, from the CPE_NAME= field of os-release."),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemCPEName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The home URL of the OS, from the HOME_URL= field of os-release."),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemHomeURL, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The end-of-support time of the OS, in µs since the UNIX epoch. Null if not known."),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemSupportEnd, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The full contents of os-release, as an array of 'KEY=VALUE' strings."),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemReleaseData, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("The OS image identifier, from the IMAGE_ID= field of os-release."),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemImageID, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The OS image version, from the IMAGE_VERSION= field of os-release."),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemImageVersion, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The full contents of /etc/machine-info, as an array of 'KEY=VALUE' strings."),
SD_VARLINK_DEFINE_OUTPUT(MachineInformationData, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("The hardware vendor of this system. Null if not known."),
SD_VARLINK_DEFINE_OUTPUT(HardwareVendor, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The hardware model of this system. Null if not known."),
SD_VARLINK_DEFINE_OUTPUT(HardwareModel, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The hardware serial number of this system. Reading it requires privileges. Null if not known."),
SD_VARLINK_DEFINE_OUTPUT(HardwareSerial, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The hardware SKU (stock keeping unit) of this system. Null if not known."),
SD_VARLINK_DEFINE_OUTPUT(HardwareSKU, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The hardware version of this system. Null if not known."),
SD_VARLINK_DEFINE_OUTPUT(HardwareVersion, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The firmware version of this system. Null if not known."),
SD_VARLINK_DEFINE_OUTPUT(FirmwareVersion, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The firmware vendor of this system. Null if not known."),
SD_VARLINK_DEFINE_OUTPUT(FirmwareVendor, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The firmware build date, in µs since the UNIX epoch. Null if not known."),
SD_VARLINK_DEFINE_OUTPUT(FirmwareDate, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The 128-bit machine ID of this system, formatted as a hexadecimal string."),
SD_VARLINK_DEFINE_OUTPUT(MachineID, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("The 128-bit boot ID of the current boot, formatted as a hexadecimal string."),
SD_VARLINK_DEFINE_OUTPUT(BootID, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("The DMI product UUID of this system. Reading it requires privileges. Null if not known."),
SD_VARLINK_DEFINE_OUTPUT(ProductUUID, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The AF_VSOCK context ID (CID) of this system. Null if not known."),
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."),
SD_VARLINK_DEFINE_INPUT(set, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("Machine tags to add to the list."),
SD_VARLINK_DEFINE_INPUT(add, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
SD_VARLINK_FIELD_COMMENT("Machine tags to remove from the list."),
SD_VARLINK_DEFINE_INPUT(remove, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY),
VARLINK_DEFINE_POLKIT_INPUT);
SD_VARLINK_DEFINE_INTERFACE(
io_systemd_Hostname,
"io.systemd.Hostname",
&vl_method_Describe);
SD_VARLINK_INTERFACE_COMMENT("APIs for querying and changing the system hostname and related machine metadata."),
SD_VARLINK_SYMBOL_COMMENT("Returns hostname, kernel, OS, hardware, firmware and other machine metadata in one call."),
&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);
+20
View File
@@ -510,6 +510,26 @@ TEST(strv_overlap) {
assert_se(!strv_overlap((char **)input_table, (char**)input_table_unique));
}
TEST(strv_remove_strv) {
_cleanup_strv_free_ char **l = NULL;
ASSERT_OK(strv_extend_strv(&l, STRV_MAKE("one", "two", "three", "four"), false));
ASSERT_PTR_EQ(strv_remove_strv(l, STRV_MAKE("two", "four")), l);
ASSERT_TRUE(strv_equal(l, STRV_MAKE("one", "three")));
/* Removing entries not present is a no-op. */
ASSERT_PTR_EQ(strv_remove_strv(l, STRV_MAKE("nope")), l);
ASSERT_TRUE(strv_equal(l, STRV_MAKE("one", "three")));
/* Empty removal list leaves the list untouched. */
ASSERT_PTR_EQ(strv_remove_strv(l, STRV_EMPTY), l);
ASSERT_TRUE(strv_equal(l, STRV_MAKE("one", "three")));
/* Removing from an empty/NULL list is fine. */
ASSERT_NULL(strv_remove_strv(NULL, STRV_MAKE("one")));
}
TEST(strv_sort) {
const char* input_table[] = {
"durian",
+117
View File
@@ -388,6 +388,123 @@ EOF
assert_rc 1 busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 GetMachineInfo s CHASSIS
}
testcase_tags() {
if [[ -f /etc/machine-info ]]; then
cp /etc/machine-info /tmp/machine-info.bak
fi
trap restore_machine_info RETURN
# Start from a clean slate.
hostnamectl tags ""
assert_eq "$(hostnamectl tags)" ""
# Plain arguments replace the whole list; each argument may itself be a colon-separated list, and the
# result is deduplicated and sorted.
hostnamectl tags foo bar:baz
assert_eq "$(hostnamectl tags)" "bar:baz:foo"
# A leading '+' adds, a leading '-' removes, and the prefix applies to all (colon-separated) tags of
# the argument.
hostnamectl tags +quux
assert_eq "$(hostnamectl tags)" "bar:baz:foo:quux"
hostnamectl tags -- -foo
assert_eq "$(hostnamectl tags)" "bar:baz:quux"
hostnamectl tags -- +alpha:beta -bar:baz
assert_eq "$(hostnamectl tags)" "alpha:beta:quux"
# Adding an existing tag and removing a non-existing one are no-ops.
hostnamectl tags +alpha
assert_eq "$(hostnamectl tags)" "alpha:beta:quux"
hostnamectl tags -- -nonexistent
assert_eq "$(hostnamectl tags)" "alpha:beta:quux"
# Plain and prefixed arguments may not be mixed.
(! hostnamectl tags foo +bar)
(! hostnamectl tags +bar foo)
# Invalid tag characters are refused.
(! hostnamectl tags "in valid")
(! hostnamectl tags "+in/valid")
# The AddAndRemoveTags() D-Bus method directly.
hostnamectl tags ""
busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 \
AddAndRemoveTags asas 2 one two 0
assert_eq "$(hostnamectl tags)" "one:two"
busctl call org.freedesktop.hostname1 /org/freedesktop/hostname1 org.freedesktop.hostname1 \
AddAndRemoveTags asas 1 three 1 one
assert_eq "$(hostnamectl tags)" "three:two"
# The SetTags() Varlink method: 'set' resets the list, then 'add'/'remove' are applied on top.
varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.SetTags \
'{"set":["x","y"],"add":["z"],"remove":["x"]}'
assert_eq "$(hostnamectl tags)" "y:z"
# An explicit empty 'set' clears the list first.
varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.SetTags '{"set":[]}'
assert_eq "$(hostnamectl tags)" ""
# Without 'set' the current list is the basis.
varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.SetTags '{"add":["aaa","bbb"]}'
assert_eq "$(hostnamectl tags)" "aaa:bbb"
varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.SetTags '{"remove":["aaa"]}'
assert_eq "$(hostnamectl tags)" "bbb"
# Invalid tags are refused.
(! varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.SetTags '{"add":["in valid"]}')
hostnamectl tags ""
}
testcase_varlink_setters() {
local bus=/run/systemd/io.systemd.Hostname
if [[ -f /etc/hostname ]]; then
cp /etc/hostname /tmp/hostname.bak
fi
if [[ -f /etc/machine-info ]]; then
cp /etc/machine-info /tmp/machine-info.bak
fi
trap 'restore_hostname; restore_machine_info' RETURN
# Static hostname.
varlinkctl call "$bus" io.systemd.Hostname.SetStaticHostname '{"newValue":"vl-static"}'
assert_eq "$(cat /etc/hostname)" "vl-static"
assert_in "Static hostname: vl-static" "$(hostnamectl)"
varlinkctl call "$bus" io.systemd.Hostname.SetStaticHostname '{"newValue":null}'
test ! -e /etc/hostname
# Transient hostname; a null value resets it back to the fallback
varlinkctl call "$bus" io.systemd.Hostname.SetHostname '{"newValue":"vl-transient"}'
assert_eq "$(hostname)" "vl-transient"
varlinkctl call "$bus" io.systemd.Hostname.SetHostname '{"newValue":null}'
assert_neq "$(hostname)" "vl-transient"
# Machine-info fields.
varlinkctl call "$bus" io.systemd.Hostname.SetPrettyHostname '{"newValue":"Pretty VL"}'
assert_eq "$(hostnamectl --json=short | jq --raw-output .PrettyHostname)" "Pretty VL"
varlinkctl call "$bus" io.systemd.Hostname.SetIconName '{"newValue":"computer-vl"}'
assert_eq "$(hostnamectl --json=short | jq --raw-output .IconName)" "computer-vl"
varlinkctl call "$bus" io.systemd.Hostname.SetChassis '{"newValue":"tablet"}'
assert_eq "$(hostnamectl chassis)" "tablet"
varlinkctl call "$bus" io.systemd.Hostname.SetDeployment '{"newValue":"staging"}'
assert_eq "$(hostnamectl --json=short | jq --raw-output .Deployment)" "staging"
varlinkctl call "$bus" io.systemd.Hostname.SetLocation '{"newValue":"Rack 7"}'
assert_eq "$(hostnamectl --json=short | jq --raw-output .Location)" "Rack 7"
# A null value removes the field again.
varlinkctl call "$bus" io.systemd.Hostname.SetLocation '{"newValue":null}'
assert_eq "$(hostnamectl --json=short | jq --raw-output .Location)" "null"
# Invalid values are refused.
(! varlinkctl call "$bus" io.systemd.Hostname.SetChassis '{"newValue":"nonsense"}')
(! varlinkctl call "$bus" io.systemd.Hostname.SetIconName '{"newValue":"in/valid"}')
(! varlinkctl call "$bus" io.systemd.Hostname.SetStaticHostname '{"newValue":"invalid hostname"}')
}
run_testcases
touch /testok
+4
View File
@@ -503,6 +503,10 @@ units = [
'file' : 'systemd-machine-id-commit.service',
'symlinks' : ['sysinit.target.wants/'],
},
{
'file' : 'systemd-machine-tag@.service',
'conditions' : ['ENABLE_HOSTNAMED'],
},
{
'file' : 'systemd-machined.service.in',
'conditions' : ['ENABLE_MACHINED'],
+23
View File
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Add Machine Tag %i
Documentation=man:systemd-machine-tag@.service(8)
DefaultDependencies=no
ConditionPathExists=!/etc/initrd-release
After=local-fs.target systemd-hostnamed.socket
Requisite=local-fs.target systemd-hostnamed.socket
Before=shutdown.target
Conflicts=shutdown.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.SetTags '{"add":["%i"]}'