mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
core,shared: support ConfigurationDirectory flags over D-Bus
Expose ConfigurationDirectorySymlink with the same a(sst) signature as the other directory types and route transient ConfigurationDirectory= assignments through the tuple parser. Keep the name and signature for uniform client handling; its destination is always empty because configuration directories do not support symlinks. Preserve the plain as property for ordinary names and empty assignments. Emit empty arrays for resets of all five directory types, including the four types whose client previously discarded empty assignments. Show tuple flags in systemctl, retaining unknown 64-bit values numerically for compatibility with newer managers. Document literal-colon escaping, which now needs an extra level for transient ConfigurationDirectory=, and the changed output/reset behavior in NEWS. Read client messages back to check property names, signatures, fields and empty arrays. Cover flags, raw D-Bus validation and reload/restart path round-trips in TEST-23, resetting failed transient units during cleanup. Fixes #43611.
This commit is contained in:
1 parent
a03e1e3124
commit
ef23a1e3fd
9 files changed
+335
-35
No files matched your search
@@ -11,6 +11,20 @@ CHANGES WITH 263 in spe:
|
||||
|
||||
Feature Removals and Incompatible Changes:
|
||||
|
||||
* ConfigurationDirectory= passed as a transient-unit property over
|
||||
D-Bus now uses the same directory:symlink:flags parser as the other
|
||||
*Directory= settings, allowing the read-only flag with an empty
|
||||
symlink field. Literal colons need an additional level of escaping
|
||||
in the client value. For example, use the shell-quoted argument
|
||||
--property='ConfigurationDirectory=foo\\:bar' for a directory named
|
||||
foo:bar. Unit file syntax and the signature of the plain D-Bus
|
||||
string array property are unchanged.
|
||||
|
||||
* Empty transient assignments to RuntimeDirectory=, StateDirectory=,
|
||||
CacheDirectory= and LogsDirectory= now reset the corresponding
|
||||
directory list instead of being ignored. ConfigurationDirectory=
|
||||
continues to support resetting the list with an empty assignment.
|
||||
|
||||
* Transient *Directory= drop-ins now escape colons and quotes in
|
||||
paths set through either D-Bus signature, preserving the paths
|
||||
when the unit is reloaded.
|
||||
@@ -20,6 +34,11 @@ CHANGES WITH 263 in spe:
|
||||
DynamicUser= directories. Unit files reject these symlink destinations
|
||||
too, matching the existing restriction on source paths.
|
||||
|
||||
* systemctl show now includes directory flags in *DirectorySymlink
|
||||
values. Read-only entries are shown as source:destination:ro instead
|
||||
of source:destination. Unknown flags are shown numerically so newer
|
||||
manager properties can still be displayed.
|
||||
|
||||
* systemd-networkd and networkctl have been merged into a single
|
||||
multi-call binary, with the former becoming a symlink to the latter.
|
||||
This should generally be invisible to users, but some packaging and
|
||||
|
||||
@@ -3574,6 +3574,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as LogsDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(sst) ConfigurationDirectorySymlink = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u ConfigurationDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as ConfigurationDirectory = ['...', ...];
|
||||
@@ -4209,8 +4211,6 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
|
||||
<!--property ConfigurationDirectoryMode is not documented!-->
|
||||
|
||||
<!--property ConfigurationDirectory is not documented!-->
|
||||
|
||||
<!--property TimeoutCleanUSec is not documented!-->
|
||||
|
||||
<!--property MemoryDenyWriteExecute is not documented!-->
|
||||
@@ -4967,6 +4967,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectorySymlink"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectory"/>
|
||||
@@ -5203,6 +5205,15 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
|
||||
#define SD_EXEC_DIRECTORY_READ_ONLY (UINT64_C(1) << 0)
|
||||
</programlisting>
|
||||
|
||||
<para><varname>ConfigurationDirectory</varname> lists the configuration directories of the unit, as
|
||||
configured by the unit file setting of the same name, and
|
||||
<varname>ConfigurationDirectorySymlink</varname> exposes the same <varname>flags</varname> parameter
|
||||
for them. Unlike the other directory types, <varname>ConfigurationDirectory</varname> does not
|
||||
support symlinks, so the destination member of <varname>ConfigurationDirectorySymlink</varname>
|
||||
must be left empty and is always returned as an empty string. The property retains the same
|
||||
tuple signature and naming convention as the other directory types so clients can handle their
|
||||
flags uniformly.</para>
|
||||
|
||||
<para><varname>ExtraFileDescriptorNames</varname> contains file descriptor names passed to the service via
|
||||
the <varname>ExtraFileDescriptors</varname> property in the <function>StartTransientUnit()</function>
|
||||
method. See <citerefentry><refentrytitle>sd_listen_fds</refentrytitle><manvolnum>3</manvolnum></citerefentry>
|
||||
@@ -5884,6 +5895,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as LogsDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(sst) ConfigurationDirectorySymlink = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u ConfigurationDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as ConfigurationDirectory = ['...', ...];
|
||||
@@ -6531,8 +6544,6 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
|
||||
<!--property ConfigurationDirectoryMode is not documented!-->
|
||||
|
||||
<!--property ConfigurationDirectory is not documented!-->
|
||||
|
||||
<!--property TimeoutCleanUSec is not documented!-->
|
||||
|
||||
<!--property MemoryDenyWriteExecute is not documented!-->
|
||||
@@ -7265,6 +7276,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectorySymlink"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectory"/>
|
||||
@@ -8010,6 +8023,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as LogsDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(sst) ConfigurationDirectorySymlink = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u ConfigurationDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as ConfigurationDirectory = ['...', ...];
|
||||
@@ -8581,8 +8596,6 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
|
||||
<!--property ConfigurationDirectoryMode is not documented!-->
|
||||
|
||||
<!--property ConfigurationDirectory is not documented!-->
|
||||
|
||||
<!--property TimeoutCleanUSec is not documented!-->
|
||||
|
||||
<!--property MemoryDenyWriteExecute is not documented!-->
|
||||
@@ -9217,6 +9230,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectorySymlink"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectory"/>
|
||||
@@ -10084,6 +10099,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as LogsDirectory = ['...', ...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly a(sst) ConfigurationDirectorySymlink = [...];
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly u ConfigurationDirectoryMode = ...;
|
||||
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
|
||||
readonly as ConfigurationDirectory = ['...', ...];
|
||||
@@ -10637,8 +10654,6 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
|
||||
<!--property ConfigurationDirectoryMode is not documented!-->
|
||||
|
||||
<!--property ConfigurationDirectory is not documented!-->
|
||||
|
||||
<!--property TimeoutCleanUSec is not documented!-->
|
||||
|
||||
<!--property MemoryDenyWriteExecute is not documented!-->
|
||||
@@ -11255,6 +11270,8 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectory"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectorySymlink"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectoryMode"/>
|
||||
|
||||
<variablelist class="dbus-property" generated="True" extra-ref="ConfigurationDirectory"/>
|
||||
@@ -13013,6 +13030,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<varname>OOMRules</varname> were added in version 261.</para>
|
||||
<para><varname>LUOSession</varname> was added in version 262.</para>
|
||||
<para><varname>RestartRandomizedDelayUSec</varname> was added in version 262.</para>
|
||||
<para><varname>ConfigurationDirectorySymlink</varname> was added in version 263.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Socket Unit Objects</title>
|
||||
@@ -13092,6 +13110,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<para><varname>XAttrEntryPoint</varname>,
|
||||
<varname>XAttrListen</varname>, and
|
||||
<varname>XAttrAccept</varname> were added in version 262.</para>
|
||||
<para><varname>ConfigurationDirectorySymlink</varname> was added in version 263.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Mount Unit Objects</title>
|
||||
@@ -13163,6 +13182,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<varname>IOPressureWatch</varname>,
|
||||
<varname>CPUSetPartition</varname>, and
|
||||
<varname>OOMRules</varname> were added in version 261.</para>
|
||||
<para><varname>ConfigurationDirectorySymlink</varname> was added in version 263.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Swap Unit Objects</title>
|
||||
@@ -13232,6 +13252,7 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
|
||||
<varname>IOPressureWatch</varname>,
|
||||
<varname>CPUSetPartition</varname>, and
|
||||
<varname>OOMRules</varname> were added in version 261.</para>
|
||||
<para><varname>ConfigurationDirectorySymlink</varname> was added in version 263.</para>
|
||||
</refsect2>
|
||||
<refsect2>
|
||||
<title>Slice Unit Objects</title>
|
||||
|
||||
@@ -1676,6 +1676,14 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
|
||||
flag without a destination symlink, the second parameter can be empty, for example:
|
||||
<programlisting>ConfigurationDirectory=foo::ro</programlisting></para>
|
||||
|
||||
<para>Because <literal>:</literal> separates the parameters, a directory name that contains a
|
||||
literal colon must have it escaped as <literal>\:</literal>. Note that when these settings are
|
||||
instead passed over D-Bus to create a transient unit, for example with
|
||||
<command>systemd-run --property=</command>, the value is unescaped once more by the client, so a
|
||||
literal colon requires <literal>\\:</literal> in the value received by the client after shell
|
||||
quoting. For example:
|
||||
<programlisting>systemd-run --property='ConfigurationDirectory=foo\\:bar' /usr/bin/true</programlisting></para>
|
||||
|
||||
<para>The directories defined by these options are always created under the standard paths used by systemd
|
||||
(<filename>/var/</filename>, <filename>/run/</filename>, <filename>/etc/</filename>, …). If the service needs
|
||||
directories in a different location, a different mechanism has to be used to create them.</para>
|
||||
|
||||
@@ -1375,6 +1375,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
|
||||
SD_BUS_PROPERTY("LogsDirectoryAccounting", "b", bus_property_get_bool, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS].exec_quota.quota_accounting), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("LogsDirectoryQuota", "(tus)", property_get_exec_quota, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS].exec_quota), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("LogsDirectory", "as", property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS]), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("ConfigurationDirectorySymlink", "a(sst)", property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_CONFIGURATION]), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("ConfigurationDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_CONFIGURATION].mode), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("ConfigurationDirectory", "as", property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_CONFIGURATION]), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
SD_BUS_PROPERTY("TimeoutCleanUSec", "t", bus_property_get_usec, offsetof(ExecContext, timeout_clean_usec), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||
|
||||
+21
-19
@@ -2079,9 +2079,12 @@ static int bus_append_extension_images(sd_bus_message *m, const char *field, con
|
||||
|
||||
static int bus_append_directory(sd_bus_message *m, const char *field, const char *eq) {
|
||||
_cleanup_strv_free_ char **symlinks = NULL, **symlinks_ro = NULL, **sources = NULL, **sources_ro = NULL;
|
||||
ExecDirectoryType type;
|
||||
const char *p = eq;
|
||||
int r;
|
||||
|
||||
assert_se((type = exec_directory_type_from_string(field)) >= 0);
|
||||
|
||||
/* Adding new directories is supported from both *DirectorySymlink methods and the
|
||||
* older ones, so first parse the input, and if we are given a new-style src:dst
|
||||
* tuple use the new method, else use the old one. */
|
||||
@@ -2102,6 +2105,10 @@ static int bus_append_directory(sd_bus_message *m, const char *field, const char
|
||||
|
||||
path_simplify(source);
|
||||
|
||||
if (!isempty(dest) && type == EXEC_DIRECTORY_CONFIGURATION)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Symlink destination is not supported for %s=: '%s'", field, tuple);
|
||||
|
||||
if (isempty(dest) && isempty(flags)) {
|
||||
r = strv_consume(&sources, TAKE_PTR(source));
|
||||
if (r < 0)
|
||||
@@ -2114,7 +2121,8 @@ static int bus_append_directory(sd_bus_message *m, const char *field, const char
|
||||
} else {
|
||||
ExecDirectoryFlags exec_directory_flags = exec_directory_flags_from_string(flags);
|
||||
if (exec_directory_flags < 0 || (exec_directory_flags & ~_EXEC_DIRECTORY_FLAGS_PUBLIC) != 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse flags for %s=: '%s'", field, flags);
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Failed to parse flags for %s=: '%s'", field, flags);
|
||||
|
||||
if (!isempty(dest)) {
|
||||
path_simplify(dest);
|
||||
@@ -2126,7 +2134,11 @@ static int bus_append_directory(sd_bus_message *m, const char *field, const char
|
||||
}
|
||||
}
|
||||
|
||||
if (!strv_isempty(sources)) {
|
||||
bool have_new = !strv_isempty(symlinks) || !strv_isempty(symlinks_ro) || !strv_isempty(sources_ro);
|
||||
|
||||
/* Send the old property if we have plain sources, and also if we parsed nothing at all: an empty
|
||||
* assignment resets the list, and only the old property can express that. */
|
||||
if (!strv_isempty(sources) || !have_new) {
|
||||
r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
@@ -2152,27 +2164,17 @@ static int bus_append_directory(sd_bus_message *m, const char *field, const char
|
||||
return bus_log_create_error(r);
|
||||
}
|
||||
|
||||
/* For State and Runtime directories we support an optional destination parameter, which
|
||||
* will be used to create a symlink to the source. But it is new so we cannot change the
|
||||
* old DBUS signatures, so append a new message type. */
|
||||
if (!strv_isempty(symlinks) || !strv_isempty(symlinks_ro) || !strv_isempty(sources_ro)) {
|
||||
const char *symlink_field;
|
||||
/* For State, Runtime, Cache and Logs directories we support an optional destination parameter,
|
||||
* which will be used to create a symlink to the source. Additionally all directory types support
|
||||
* a flags parameter. But these are new so we cannot change the old D-Bus signatures, hence append
|
||||
* a new message type. */
|
||||
if (have_new) {
|
||||
const char *symlink_field = exec_directory_type_symlink_to_string(type);
|
||||
|
||||
r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
if (streq(field, "StateDirectory"))
|
||||
symlink_field = "StateDirectorySymlink";
|
||||
else if (streq(field, "RuntimeDirectory"))
|
||||
symlink_field = "RuntimeDirectorySymlink";
|
||||
else if (streq(field, "CacheDirectory"))
|
||||
symlink_field = "CacheDirectorySymlink";
|
||||
else if (streq(field, "LogsDirectory"))
|
||||
symlink_field = "LogsDirectorySymlink";
|
||||
else
|
||||
assert_not_reached();
|
||||
|
||||
r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, symlink_field);
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
@@ -2636,7 +2638,6 @@ static const BusProperty execute_properties[] = {
|
||||
{ "NoExecPaths", bus_append_strv },
|
||||
{ "ExecSearchPath", bus_append_strv_colon },
|
||||
{ "ExtensionDirectories", bus_append_strv },
|
||||
{ "ConfigurationDirectory", bus_append_strv },
|
||||
{ "SupplementaryGroups", bus_append_strv },
|
||||
{ "SystemCallArchitectures", bus_append_strv },
|
||||
{ "SyslogLevel", bus_append_log_level_from_string },
|
||||
@@ -2706,6 +2707,7 @@ static const BusProperty execute_properties[] = {
|
||||
{ "RuntimeDirectory", bus_append_directory },
|
||||
{ "CacheDirectory", bus_append_directory },
|
||||
{ "LogsDirectory", bus_append_directory },
|
||||
{ "ConfigurationDirectory", bus_append_directory },
|
||||
{ "ProtectHostname", bus_append_protect_hostname },
|
||||
{ "ProtectHostnameEx", bus_append_protect_hostname }, /* compat */
|
||||
{ "PrivateTmp", bus_append_boolean_or_ex_string },
|
||||
|
||||
@@ -2102,7 +2102,9 @@ static int print_property(
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
return 1;
|
||||
} else if (STR_IN_SET(name, "StateDirectorySymlink", "RuntimeDirectorySymlink", "CacheDirectorySymlink", "LogsDirectorySymlink")) {
|
||||
} else if (STR_IN_SET(name,
|
||||
"StateDirectorySymlink", "RuntimeDirectorySymlink", "CacheDirectorySymlink",
|
||||
"LogsDirectorySymlink", "ConfigurationDirectorySymlink")) {
|
||||
const char *a, *p;
|
||||
uint64_t symlink_flags;
|
||||
|
||||
@@ -2110,8 +2112,16 @@ static int print_property(
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
while ((r = sd_bus_message_read(m, "(sst)", &a, &p, &symlink_flags)) > 0)
|
||||
bus_print_property_valuef(name, expected_value, flags, "%s:%s", a, p);
|
||||
while ((r = sd_bus_message_read(m, "(sst)", &a, &p, &symlink_flags)) > 0) {
|
||||
const char *flags_string = exec_directory_flags_to_string(symlink_flags);
|
||||
if (flags_string)
|
||||
bus_print_property_valuef(name, expected_value, flags, "%s:%s%s%s", a, p,
|
||||
isempty(flags_string) ? "" : ":", flags_string);
|
||||
else
|
||||
/* Preserve unknown bits from a newer manager without aborting the dump. */
|
||||
bus_print_property_valuef(name, expected_value, flags, "%s:%s:%" PRIu64,
|
||||
a, p, symlink_flags);
|
||||
}
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
|
||||
@@ -16,6 +16,112 @@
|
||||
static sd_bus *arg_bus = NULL;
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_bus, sd_bus_unrefp);
|
||||
|
||||
static void assert_directory_property(
|
||||
sd_bus_message *m,
|
||||
const char *property,
|
||||
const char* const* sources,
|
||||
const char *destination,
|
||||
uint64_t flags) {
|
||||
|
||||
const char *name;
|
||||
|
||||
ASSERT_OK_POSITIVE(sd_bus_message_enter_container(m, 'r', "sv"));
|
||||
ASSERT_OK_POSITIVE(sd_bus_message_read(m, "s", &name));
|
||||
ASSERT_STREQ(name, property);
|
||||
ASSERT_OK_POSITIVE(sd_bus_message_enter_container(m, 'v', destination ? "a(sst)" : "as"));
|
||||
|
||||
if (destination) {
|
||||
ASSERT_OK_POSITIVE(sd_bus_message_enter_container(m, 'a', "(sst)"));
|
||||
STRV_FOREACH(source, sources) {
|
||||
const char *actual_source, *actual_destination;
|
||||
uint64_t actual_flags;
|
||||
|
||||
ASSERT_OK_POSITIVE(sd_bus_message_read(m, "(sst)",
|
||||
&actual_source, &actual_destination, &actual_flags));
|
||||
ASSERT_STREQ(actual_source, *source);
|
||||
ASSERT_STREQ(actual_destination, destination);
|
||||
ASSERT_EQ(actual_flags, flags);
|
||||
}
|
||||
ASSERT_OK_POSITIVE(sd_bus_message_at_end(m, false));
|
||||
ASSERT_OK(sd_bus_message_exit_container(m));
|
||||
} else {
|
||||
_cleanup_strv_free_ char **actual = NULL;
|
||||
|
||||
ASSERT_OK_POSITIVE(sd_bus_message_read_strv(m, &actual));
|
||||
ASSERT_EQ(strv_length(actual), strv_length((char**) sources));
|
||||
for (size_t i = 0; i < strv_length(actual); i++)
|
||||
ASSERT_STREQ(actual[i], sources[i]);
|
||||
}
|
||||
|
||||
ASSERT_OK_POSITIVE(sd_bus_message_at_end(m, false));
|
||||
ASSERT_OK(sd_bus_message_exit_container(m));
|
||||
ASSERT_OK_POSITIVE(sd_bus_message_at_end(m, false));
|
||||
ASSERT_OK(sd_bus_message_exit_container(m));
|
||||
}
|
||||
|
||||
static void test_directory_assignment_one(
|
||||
const char *field,
|
||||
const char *value,
|
||||
const char *property,
|
||||
const char* const* sources,
|
||||
const char *destination,
|
||||
uint64_t flags) {
|
||||
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_free_ char *assignment = NULL;
|
||||
|
||||
ASSERT_NOT_NULL(assignment = strjoin(field, "=", value));
|
||||
ASSERT_OK(sd_bus_message_new(arg_bus, &m, SD_BUS_MESSAGE_METHOD_CALL));
|
||||
ASSERT_EQ(bus_append_unit_property_assignment(m, UNIT_SERVICE, assignment), 1);
|
||||
ASSERT_OK(sd_bus_message_seal(m, 1, 0));
|
||||
ASSERT_OK(sd_bus_message_rewind(m, true));
|
||||
assert_directory_property(m, property, sources, destination, flags);
|
||||
ASSERT_OK_POSITIVE(sd_bus_message_at_end(m, true));
|
||||
}
|
||||
|
||||
TEST(directory_properties) {
|
||||
static const struct {
|
||||
const char *field;
|
||||
const char *symlink_field;
|
||||
} properties[] = {
|
||||
{ "RuntimeDirectory", "RuntimeDirectorySymlink" },
|
||||
{ "StateDirectory", "StateDirectorySymlink" },
|
||||
{ "CacheDirectory", "CacheDirectorySymlink" },
|
||||
{ "LogsDirectory", "LogsDirectorySymlink" },
|
||||
{ "ConfigurationDirectory", "ConfigurationDirectorySymlink" },
|
||||
};
|
||||
|
||||
FOREACH_ELEMENT(p, properties) {
|
||||
test_directory_assignment_one(p->field, "myapp subdir", p->field,
|
||||
STRV_MAKE_CONST("myapp", "subdir"), /* destination= */ NULL, 0);
|
||||
test_directory_assignment_one(p->field, "myapp\\\\:colon", p->field,
|
||||
STRV_MAKE_CONST("myapp:colon"), /* destination= */ NULL, 0);
|
||||
test_directory_assignment_one(p->field, "", p->field,
|
||||
/* sources= */ NULL, /* destination= */ NULL, 0);
|
||||
test_directory_assignment_one(p->field, "myapp::ro", p->symlink_field,
|
||||
STRV_MAKE_CONST("myapp"), "", EXEC_DIRECTORY_READ_ONLY);
|
||||
|
||||
if (!streq(p->field, "ConfigurationDirectory")) {
|
||||
test_directory_assignment_one(p->field, "myapp:link", p->symlink_field,
|
||||
STRV_MAKE_CONST("myapp"), "link", 0);
|
||||
test_directory_assignment_one(p->field, "myapp:link:ro", p->symlink_field,
|
||||
STRV_MAKE_CONST("myapp"), "link", EXEC_DIRECTORY_READ_ONLY);
|
||||
}
|
||||
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_free_ char *assignment = NULL;
|
||||
|
||||
ASSERT_NOT_NULL(assignment = strjoin(p->field, "=plain myapp::ro"));
|
||||
ASSERT_OK(sd_bus_message_new(arg_bus, &m, SD_BUS_MESSAGE_METHOD_CALL));
|
||||
ASSERT_EQ(bus_append_unit_property_assignment(m, UNIT_SERVICE, assignment), 1);
|
||||
ASSERT_OK(sd_bus_message_seal(m, 1, 0));
|
||||
ASSERT_OK(sd_bus_message_rewind(m, true));
|
||||
assert_directory_property(m, p->field, STRV_MAKE_CONST("plain"), /* destination= */ NULL, 0);
|
||||
assert_directory_property(m, p->symlink_field, STRV_MAKE_CONST("myapp"), "", EXEC_DIRECTORY_READ_ONLY);
|
||||
ASSERT_OK_POSITIVE(sd_bus_message_at_end(m, true));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(exec_directory_flags) {
|
||||
ASSERT_STREQ(exec_directory_flags_to_string(0), "");
|
||||
ASSERT_STREQ(exec_directory_flags_to_string(EXEC_DIRECTORY_READ_ONLY), "ro");
|
||||
@@ -427,7 +533,6 @@ TEST(execute_properties) {
|
||||
"NoExecPaths=/tmp /var/tmp",
|
||||
"ExecSearchPath=/usr/bin:/usr/local/bin",
|
||||
"ExtensionDirectories=/var/lib/extensions /opt/extensions",
|
||||
"ConfigurationDirectory=myapp subdir",
|
||||
"SupplementaryGroups=wheel audio video",
|
||||
"SystemCallArchitectures=native x86-64",
|
||||
|
||||
@@ -607,6 +712,27 @@ TEST(execute_properties) {
|
||||
"CacheDirectory=myapp subdir",
|
||||
"LogsDirectory=myapp",
|
||||
"LogsDirectory=myapp subdir",
|
||||
"ConfigurationDirectory=myapp",
|
||||
"ConfigurationDirectory=myapp subdir",
|
||||
|
||||
"StateDirectory=myapp:link",
|
||||
"StateDirectory=myapp:link:ro",
|
||||
"StateDirectory=myapp::ro",
|
||||
"RuntimeDirectory=myapp::ro",
|
||||
"CacheDirectory=myapp::ro",
|
||||
"LogsDirectory=myapp::ro",
|
||||
/* ConfigurationDirectory= takes flags, but no symlink destination */
|
||||
"ConfigurationDirectory=myapp::ro",
|
||||
/* A literal colon in a directory name needs an extra level of escaping */
|
||||
"StateDirectory=myapp\\\\:colon",
|
||||
"ConfigurationDirectory=myapp\\\\:colon",
|
||||
"-EINVAL ConfigurationDirectory=myapp:link",
|
||||
"-EINVAL ConfigurationDirectory=myapp:link:ro",
|
||||
"-EINVAL StateDirectory=myapp::rw",
|
||||
"-EINVAL ConfigurationDirectory=myapp::rw",
|
||||
/* An empty assignment resets the list */
|
||||
"StateDirectory=",
|
||||
"ConfigurationDirectory=",
|
||||
|
||||
"ProtectHostname=true",
|
||||
"ProtectHostname=false",
|
||||
|
||||
@@ -242,20 +242,21 @@ if ! systemd-detect-virt -cq; then
|
||||
fi
|
||||
|
||||
# {Cache,Configuration,Logs,Runtime,State}Directory=
|
||||
# All *Directory= directives support tuple syntax (directory:symlink:flags for
|
||||
# {Runtime,State,Cache,Logs}Directory=, directory::flags for ConfigurationDirectory=),
|
||||
# which requires an additional level of escaping for the colon character.
|
||||
ARGUMENTS=(
|
||||
-p CacheDirectory="foo/bar/baz also\ with\ spaces"
|
||||
-p CacheDirectory="foo"
|
||||
-p CacheDirectory="context"
|
||||
-p CacheDirectoryMode="0123"
|
||||
-p CacheDirectoryMode="0666"
|
||||
-p ConfigurationDirectory="context/foo also_context/bar context/nested/baz context/semi\:colon"
|
||||
-p ConfigurationDirectory="context/foo also_context/bar context/nested/baz context/semi\\\:colon"
|
||||
-p ConfigurationDirectoryMode="0400"
|
||||
-p LogsDirectory="context/foo"
|
||||
-p LogsDirectory=""
|
||||
-p LogsDirectory="context/a/very/nested/logs/dir"
|
||||
-p RuntimeDirectory="context/with\ spaces"
|
||||
# Note: {Runtime,State,Cache,Logs}Directory= directives support the directory:symlink syntax, which
|
||||
# requires an additional level of escaping for the colon character
|
||||
-p RuntimeDirectory="also_context:a\ symlink\ with\ \\\:\ col\\\:ons\ and\ \ spaces"
|
||||
-p RuntimeDirectoryPreserve=yes
|
||||
-p StateDirectory="context"
|
||||
@@ -270,8 +271,9 @@ systemd-run --wait --pipe "${ARGUMENTS[@]}" \
|
||||
systemd-run --wait --pipe "${ARGUMENTS[@]}" \
|
||||
bash -xec '[[ $CONFIGURATION_DIRECTORY == /etc/also_context/bar:/etc/context/foo:/etc/context/nested/baz:/etc/context/semi:colon ]];
|
||||
[[ $(stat -c "%a" "${CONFIGURATION_DIRECTORY%%:*}") == 400 ]]'
|
||||
# Note: the empty LogsDirectory= above resets the list, so context/foo is gone
|
||||
systemd-run --wait --pipe "${ARGUMENTS[@]}" \
|
||||
bash -xec '[[ $LOGS_DIRECTORY == /var/log/context/a/very/nested/logs/dir:/var/log/context/foo ]];
|
||||
bash -xec '[[ $LOGS_DIRECTORY == /var/log/context/a/very/nested/logs/dir ]];
|
||||
[[ $(stat -c "%a" "${LOGS_DIRECTORY##*:}") == 755 ]]'
|
||||
systemd-run --wait --pipe "${ARGUMENTS[@]}" \
|
||||
bash -xec '[[ $RUNTIME_DIRECTORY == "/run/also_context:/run/context/with spaces" ]];
|
||||
|
||||
@@ -6,11 +6,28 @@
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck source=test/units/util.sh
|
||||
. "$(dirname "$0")"/util.sh
|
||||
|
||||
# Test unit configuration/state/cache/log/runtime data cleanup
|
||||
|
||||
export HOME=/root
|
||||
export XDG_RUNTIME_DIR=/run/user/0
|
||||
|
||||
at_exit() {
|
||||
set +e
|
||||
|
||||
systemctl --user stop test-execdir-flags.service test-execdir-colon.service test-execdir-raw.service
|
||||
systemctl --user reset-failed test-execdir-flags.service test-execdir-colon.service test-execdir-raw.service
|
||||
rm -fr "$HOME"/.config/corge "$HOME"/.config/foo "$HOME"/.config/link "$HOME"/.config/quux* "$HOME"/.local/state/bar "$HOME"/.local/state/foo \
|
||||
"$HOME"/.local/state/grault* "$HOME"/.local/state/private \
|
||||
"$HOME"/.local/state/waldo "$HOME"/.config/grault* \
|
||||
"$HOME"/.local/state/execdir-test "$HOME"/.config/execdir-test \
|
||||
"$HOME"/.cache/execdir-test "$HOME"/.local/state/log/execdir-test "$XDG_RUNTIME_DIR"/execdir-test
|
||||
}
|
||||
|
||||
trap at_exit EXIT
|
||||
|
||||
systemctl start user@0.service
|
||||
|
||||
( ! test -d "$HOME"/.local/state/foo)
|
||||
@@ -63,3 +80,97 @@ test -L "$HOME"/.local/state/bar
|
||||
|
||||
rm "$HOME"/.local/state/foo
|
||||
rmdir "$HOME"/.config/foo
|
||||
|
||||
# ConfigurationDirectory= accepts the flags field too, but no symlink destination
|
||||
assert_fail systemd-run --user -p ConfigurationDirectory=quux::ro --wait bash -c "echo foo >$HOME/.config/quux/baz"
|
||||
test -d "$HOME"/.config/quux
|
||||
( ! test -f "$HOME"/.config/quux/baz)
|
||||
assert_fail systemd-run --user -p ConfigurationDirectory=quux:link --wait true
|
||||
|
||||
# Bypass the client parser to exercise the manager-side validation. Include a valid
|
||||
# ExecStart= so an unrelated missing-command error cannot make the test pass.
|
||||
if output=$(busctl --user call \
|
||||
org.freedesktop.systemd1 /org/freedesktop/systemd1 \
|
||||
org.freedesktop.systemd1.Manager StartTransientUnit \
|
||||
'ssa(sv)a(sa(sv))' test-execdir-raw.service fail 2 \
|
||||
ExecStart 'a(sasb)' 1 /usr/bin/true 1 /usr/bin/true false \
|
||||
ConfigurationDirectorySymlink 'a(sst)' 1 execdir-test/source execdir-test/link 0 \
|
||||
0 2>&1); then
|
||||
echo 'Manager accepted a ConfigurationDirectorySymlink destination' >&2
|
||||
exit 1
|
||||
fi
|
||||
assert_in 'Symlink destination is not supported for ConfigurationDirectory=' "$output"
|
||||
|
||||
# A 'private' source is refused by the manager
|
||||
assert_fail systemd-run --user -p StateDirectory=private/waldo::ro --wait true
|
||||
( ! test -e "$HOME"/.local/state/private)
|
||||
|
||||
# The reserved directory must not be created through a symlink destination either.
|
||||
for type in Runtime State Cache Logs; do
|
||||
for destination in private private/execdir-test; do
|
||||
assert_fail systemd-run --user -p "${type}Directory=execdir-test/source:$destination" --wait true
|
||||
done
|
||||
done
|
||||
|
||||
# An empty assignment resets the list, so nothing is created below
|
||||
systemd-run --user -p ConfigurationDirectory=corge -p ConfigurationDirectory= --wait true
|
||||
( ! test -e "$HOME"/.config/corge)
|
||||
|
||||
# A flags field without a symlink destination must round-trip through the transient
|
||||
# unit, i.e. it must not end up serialized with a literal "(null)" destination
|
||||
systemd-run --user --unit=test-execdir-flags -p StateDirectory=waldo::ro -p ConfigurationDirectory=quux::ro sleep infinity
|
||||
systemctl --user cat test-execdir-flags.service | grep "^StateDirectory=waldo::ro$" >/dev/null
|
||||
systemctl --user cat test-execdir-flags.service | grep "^ConfigurationDirectory=quux::ro$" >/dev/null
|
||||
systemctl --user daemon-reload
|
||||
assert_eq "$(systemctl --user show -P StateDirectorySymlink test-execdir-flags.service)" "waldo::ro"
|
||||
assert_eq "$(systemctl --user show -P ConfigurationDirectorySymlink test-execdir-flags.service)" "quux::ro"
|
||||
|
||||
# A literal colon in a directory name must survive the transient drop-in round-trip
|
||||
systemd-run --user --unit=test-execdir-colon \
|
||||
-p 'StateDirectory=grault\\:garply' -p 'ConfigurationDirectory=grault\\:garply' sleep infinity
|
||||
assert_eq "$(systemctl --user show -P StateDirectory test-execdir-colon.service)" "grault:garply"
|
||||
assert_eq "$(systemctl --user show -P ConfigurationDirectory test-execdir-colon.service)" "grault:garply"
|
||||
systemctl --user daemon-reload
|
||||
assert_eq "$(systemctl --user show -P StateDirectory test-execdir-colon.service)" "grault:garply"
|
||||
assert_eq "$(systemctl --user show -P ConfigurationDirectory test-execdir-colon.service)" "grault:garply"
|
||||
|
||||
# Exercise both D-Bus signatures with names that need escaping at both the transient
|
||||
# unit file and executor serialization layers. Read the typed property to avoid
|
||||
# confusing systemctl's display escaping with the actual stored path.
|
||||
for property in StateDirectory ConfigurationDirectory StateDirectorySymlink ConfigurationDirectorySymlink; do
|
||||
for name in 'with space' 'with"quote' 'with:colon'; do
|
||||
source="execdir-test/$name"
|
||||
destination=''
|
||||
if [[ "$property" == *Symlink ]]; then
|
||||
if [[ "$property" == StateDirectorySymlink ]]; then
|
||||
destination="execdir-test/link-$name"
|
||||
fi
|
||||
value=('a(sst)' 1 "$source" "$destination" 1)
|
||||
else
|
||||
value=(as 1 "$source")
|
||||
fi
|
||||
|
||||
busctl --user call \
|
||||
org.freedesktop.systemd1 /org/freedesktop/systemd1 \
|
||||
org.freedesktop.systemd1.Manager StartTransientUnit \
|
||||
'ssa(sv)a(sa(sv))' test-execdir-raw.service fail 4 \
|
||||
Type s oneshot RemainAfterExit b true \
|
||||
ExecStart 'a(sasb)' 1 /usr/bin/true 1 /usr/bin/true false \
|
||||
"$property" "${value[@]}" 0
|
||||
systemctl --user start test-execdir-raw.service
|
||||
for phase in before after; do
|
||||
if [[ "$phase" == after ]]; then
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user restart test-execdir-raw.service
|
||||
fi
|
||||
assert_eq "$(busctl --user --json=short get-property \
|
||||
org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/test_2dexecdir_2draw_2eservice \
|
||||
org.freedesktop.systemd1.Service "$property" | jq -r \
|
||||
'if .type == "as" then .data[0] else .data[0][0] end')" "$source"
|
||||
if [[ "$property" == *Symlink ]]; then
|
||||
assert_eq "$(systemctl --user show -P "$property" test-execdir-raw.service)" "$source:$destination:ro"
|
||||
fi
|
||||
done
|
||||
systemctl --user stop test-execdir-raw.service
|
||||
done
|
||||
done
|
||||
Reference in new issue
Block a user