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:
umutcagand committed 2026-10-17 11:24:21 +03:00
1 parent a03e1e3124
commit ef23a1e3fd
9 files changed
+335 -35

No files matched your search

+19
View File
@@ -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
+29 -8
View File
@@ -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) &lt;&lt; 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>
+8
View File
@@ -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>
+1
View File
@@ -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
View File
@@ -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 },
+13 -3
View File
@@ -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);
+127 -1
View File
@@ -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",
+6 -4
View File
@@ -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" ]];
+111
View File
@@ -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