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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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
@@ -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, ¤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);
|
||||
|
||||
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, ¤t);
|
||||
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, ¤t);
|
||||
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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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"]}'
|
||||
Reference in New Issue
Block a user