- support new FS_IOC_ADD_ENCRYPTION_KEY ioctl for setting up fscrypt
- maybe pre-create ~/.cache as subvol so that it can have separate quota
easily?
- - if kernel 5.12 uid mapping mounts exist, use that instead of recursive
- chowns.
- add a switch to homectl (maybe called --first-boot) where it will check if
any non-system users exist, and if not prompts interactively for basic user
info, mimicking systemd-firstboot. Then, place this in a service that runs
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly (bas) RestrictAddressFamilies = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) RuntimeDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s RuntimeDirectoryPreserve = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u RuntimeDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as RuntimeDirectory = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) StateDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u StateDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as StateDirectory = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) CacheDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u CacheDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as CacheDirectory = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) LogsDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u LogsDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as LogsDirectory = ['...', ...];
<!--property RuntimeDirectoryMode is not documented!-->
- <!--property RuntimeDirectory is not documented!-->
-
<!--property StateDirectoryMode is not documented!-->
- <!--property StateDirectory is not documented!-->
-
<!--property CacheDirectoryMode is not documented!-->
- <!--property CacheDirectory is not documented!-->
-
<!--property LogsDirectoryMode is not documented!-->
- <!--property LogsDirectory is not documented!-->
-
<!--property ConfigurationDirectoryMode is not documented!-->
<!--property ConfigurationDirectory is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="RestrictAddressFamilies"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectoryPreserve"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectory"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="StateDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectory"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="CacheDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectory"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="LogsDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectory"/>
the <literal>MemoryMax</literal> or <literal>MemoryHigh</literal> (whichever is lower) limit set by the cgroup
memory controller is reached. It will take into consideration limits on all parent slices, other than the
limits set on the unit itself.</para>
+
+ <para><varname>RuntimeDirectorySymlink</varname>, <varname>StateDirectorySymlink</varname>,
+ <varname>CacheDirectorySymlink</varname> and <varname>LogsDirectorySymlink</varname> respectively
+ implement the destination parameter of the unit files settings <varname>RuntimeDirectory</varname>,
+ <varname>StateDirectory</varname>, <varname>CacheDirectory</varname> and <varname>LogsDirectory</varname>,
+ which will create a symlink of the given name to the respective directory. The messages take an unused
+ <varname>flags</varname> parameter, reserved for future backward-compatible changes.</para>
</refsect2>
</refsect1>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly (bas) RestrictAddressFamilies = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) RuntimeDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s RuntimeDirectoryPreserve = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u RuntimeDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as RuntimeDirectory = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) StateDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u StateDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as StateDirectory = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) CacheDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u CacheDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as CacheDirectory = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) LogsDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u LogsDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as LogsDirectory = ['...', ...];
<!--property RuntimeDirectoryMode is not documented!-->
- <!--property RuntimeDirectory is not documented!-->
-
<!--property StateDirectoryMode is not documented!-->
- <!--property StateDirectory is not documented!-->
-
<!--property CacheDirectoryMode is not documented!-->
- <!--property CacheDirectory is not documented!-->
-
<!--property LogsDirectoryMode is not documented!-->
- <!--property LogsDirectory is not documented!-->
-
<!--property ConfigurationDirectoryMode is not documented!-->
<!--property ConfigurationDirectory is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="RestrictAddressFamilies"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectoryPreserve"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectory"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="StateDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectory"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="CacheDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectory"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="LogsDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectory"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly (bas) RestrictAddressFamilies = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) RuntimeDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s RuntimeDirectoryPreserve = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u RuntimeDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as RuntimeDirectory = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) StateDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u StateDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as StateDirectory = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) CacheDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u CacheDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as CacheDirectory = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) LogsDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u LogsDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as LogsDirectory = ['...', ...];
<!--property RuntimeDirectoryMode is not documented!-->
- <!--property RuntimeDirectory is not documented!-->
-
<!--property StateDirectoryMode is not documented!-->
- <!--property StateDirectory is not documented!-->
-
<!--property CacheDirectoryMode is not documented!-->
- <!--property CacheDirectory is not documented!-->
-
<!--property LogsDirectoryMode is not documented!-->
- <!--property LogsDirectory is not documented!-->
-
<!--property ConfigurationDirectoryMode is not documented!-->
<!--property ConfigurationDirectory is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="RestrictAddressFamilies"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectoryPreserve"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectory"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="StateDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectory"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="CacheDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectory"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="LogsDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectory"/>
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly (bas) RestrictAddressFamilies = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) RuntimeDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s RuntimeDirectoryPreserve = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u RuntimeDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as RuntimeDirectory = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) StateDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u StateDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as StateDirectory = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) CacheDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u CacheDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as CacheDirectory = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+ readonly a(sst) LogsDirectorySymlink = [...];
+ @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly u LogsDirectoryMode = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as LogsDirectory = ['...', ...];
<!--property RuntimeDirectoryMode is not documented!-->
- <!--property RuntimeDirectory is not documented!-->
-
<!--property StateDirectoryMode is not documented!-->
- <!--property StateDirectory is not documented!-->
-
<!--property CacheDirectoryMode is not documented!-->
- <!--property CacheDirectory is not documented!-->
-
<!--property LogsDirectoryMode is not documented!-->
- <!--property LogsDirectory is not documented!-->
-
<!--property ConfigurationDirectoryMode is not documented!-->
<!--property ConfigurationDirectory is not documented!-->
<variablelist class="dbus-property" generated="True" extra-ref="RestrictAddressFamilies"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectoryPreserve"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="RuntimeDirectory"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="StateDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="StateDirectory"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="CacheDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="CacheDirectory"/>
+ <variablelist class="dbus-property" generated="True" extra-ref="LogsDirectorySymlink"/>
+
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectoryMode"/>
<variablelist class="dbus-property" generated="True" extra-ref="LogsDirectory"/>
source path is taken relative to the image's root directory. This permits setting up bind mounts within the
container image. The source path may be specified as empty string, in which case a temporary directory below
the host's <filename>/var/tmp/</filename> directory is used. It is automatically removed when the container is
- shut down. Mount options are comma-separated and currently, only <option>rbind</option> and
- <option>norbind</option> are allowed, controlling whether to create a recursive or a regular bind
- mount. Defaults to "rbind". Backslash escapes are interpreted, so <literal>\:</literal> may be used to embed
- colons in either path. This option may be specified multiple times for creating multiple independent bind
- mount points. The <option>--bind-ro=</option> option creates read-only bind mounts.</para>
+ shut down. The <option>--bind-ro=</option> option creates read-only bind mounts. Backslash escapes are interpreted,
+ so <literal>\:</literal> may be used to embed colons in either path. This option may be specified
+ multiple times for creating multiple independent bind mount points.</para>
+
+ <para>Mount options are comma-separated. <option>rbind</option> and <option>norbind</option> control whether
+ to create a recursive or a regular bind mount. Defaults to "rbind". <option>idmap</option> and <option>noidmap</option>
+ control if the bind mount should use filesystem id mappings. Using this option requires support by the source filesystem
+ for id mappings. Defaults to "noidmap".</para>
<para>Note that when this option is used in combination with <option>--private-users</option>, the resulting
mount points will be owned by the <constant>nobody</constant> user. That's because the mount and its files and
directories continue to be owned by the relevant host users and groups, which do not exist in the container,
and thus show up under the wildcard UID 65534 (nobody). If such bind mounts are created, it is recommended to
- make them read-only, using <option>--bind-ro=</option>.</para></listitem>
+ make them read-only, using <option>--bind-ro=</option>. Alternatively you can use the "idmap" mount option to
+ map the filesystem ids.</para></listitem>
</varlistentry>
<varlistentry>
configuration or lifetime guarantees, please consider using
<citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+ <para><varname>RuntimeDirectory=</varname>, <varname>StateDirectory=</varname>, <varname>CacheDirectory=</varname>
+ and <varname>LogsDirectory=</varname> optionally support a second parameter, separated by <literal>:</literal>.
+ The second parameter will be interpreted as a destination path that will be created as a symlink to the directory.
+ The symlinks will be created after any <varname>BindPaths=</varname> or <varname>TemporaryFileSystem=</varname>
+ options have been set up, to make ephemeral symlinking possible. The same source can have multiple symlinks, by
+ using the same first parameter, but a diferent second parameter.</para></listitem>
+
<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>
<programlisting>RuntimeDirectory=foo/bar
StateDirectory=aaa/bbb ccc</programlisting>
then the environment variable <literal>RUNTIME_DIRECTORY</literal> is set with <literal>/run/foo/bar</literal>, and
- <literal>STATE_DIRECTORY</literal> is set with <literal>/var/lib/aaa/bbb:/var/lib/ccc</literal>.</para></listitem>
+ <literal>STATE_DIRECTORY</literal> is set with <literal>/var/lib/aaa/bbb:/var/lib/ccc</literal>.</para>
+
+ <para>Example: if a system service unit has the following,
+ <programlisting>RuntimeDirectory=foo:bar foo:baz</programlisting>
+ the service manager creates <filename index='false'>/run/foo</filename> (if it does not exist), and
+ <filename index='false'>/run/bar</filename> plus <filename index='false'>/run/baz</filename> as symlinks to
+ <filename index='false'>/run/foo</filename>.</para>
</varlistentry>
<varlistentry>
services will have acquired an <varname>OnFailure=</varname> dependency on
<filename index='false'>failure-handler@%N.service</filename>. The
template instance units will also have gained the dependency which results
- in the creation of a recursive dependency chain. We can break the chain by
- disabling the drop-in for the template instance units via a symlink to
+ in the creation of a recursive dependency chain. systemd will try to detect
+ these recursive dependency chains where a template unit directly and
+ recursively depends on itself and will remove such dependencies
+ automatically if it finds them. If systemd doesn't detect the recursive
+ dependency chain, we can break the chain ourselves by disabling the drop-in
+ for the template instance units via a symlink to
<filename index='false'>/dev/null</filename>:</para>
<programlisting>
if (name_type < 0)
return name_type;
- r = add_names(unit_ids_map, unit_name_map, unit_name, NULL, name_type, instance, &names, unit_name);
- if (r < 0)
- return r;
+ if (ret_names) {
+ r = add_names(unit_ids_map, unit_name_map, unit_name, NULL, name_type, instance, &names, unit_name);
+ if (r < 0)
+ return r;
+ }
/* First try to load fragment under the original name */
r = unit_ids_map_get(unit_ids_map, unit_name, &fragment);
return log_debug_errno(r, "Cannot load template %s: %m", template);
}
- if (fragment) {
+ if (fragment && ret_names) {
const char *fragment_basename = basename(fragment);
if (!streq(fragment_basename, unit_name)) {
}
*ret_fragment_path = fragment;
- *ret_names = TAKE_PTR(names);
+ if (ret_names)
+ *ret_names = TAKE_PTR(names);
return 0;
}
#include "alloc-util.h"
#include "glob-util.h"
#include "hexdecoct.h"
+#include "memory-util.h"
#include "path-util.h"
#include "special.h"
#include "string-util.h"
return true;
}
+
+bool unit_name_prefix_equal(const char *a, const char *b) {
+ const char *p, *q;
+
+ assert(a);
+ assert(b);
+
+ if (!unit_name_is_valid(a, UNIT_NAME_ANY) || !unit_name_is_valid(b, UNIT_NAME_ANY))
+ return false;
+
+ p = strchr(a, '@');
+ if (!p)
+ p = strrchr(a, '.');
+
+ q = strchr(b, '@');
+ if (!q)
+ q = strrchr(b, '.');
+
+ assert(p);
+ assert(q);
+
+ return memcmp_nn(a, p - a, b, q - b) == 0;
+}
int slice_build_parent_slice(const char *slice, char **ret);
int slice_build_subslice(const char *slice, const char *name, char **subslice);
bool slice_name_is_valid(const char *name);
+
+bool unit_name_prefix_equal(const char *a, const char *b);
CGroupMask delegated_mask;
const char *p;
void *pidp;
- int r, q;
+ int ret, r;
assert(u);
delegated_mask = unit_get_delegate_mask(u);
- r = 0;
+ ret = 0;
SET_FOREACH(pidp, pids) {
pid_t pid = PTR_TO_PID(pidp);
/* First, attach the PID to the main cgroup hierarchy */
- q = cg_attach(SYSTEMD_CGROUP_CONTROLLER, p, pid);
- if (q < 0) {
- bool again = MANAGER_IS_USER(u->manager) && ERRNO_IS_PRIVILEGE(q);
+ r = cg_attach(SYSTEMD_CGROUP_CONTROLLER, p, pid);
+ if (r < 0) {
+ bool again = MANAGER_IS_USER(u->manager) && ERRNO_IS_PRIVILEGE(r);
- log_unit_full_errno(u, again ? LOG_DEBUG : LOG_INFO, q,
+ log_unit_full_errno(u, again ? LOG_DEBUG : LOG_INFO, r,
"Couldn't move process "PID_FMT" to%s requested cgroup '%s': %m",
pid, again ? " directly" : "", empty_to_root(p));
continue; /* When the bus thing worked via the bus we are fully done for this PID. */
}
- if (r >= 0)
- r = q; /* Remember first error */
+ if (ret >= 0)
+ ret = r; /* Remember first error */
continue;
- }
+ } else if (ret >= 0)
+ ret++; /* Count successful additions */
- q = cg_all_unified();
- if (q < 0)
- return q;
- if (q > 0)
+ r = cg_all_unified();
+ if (r < 0)
+ return r;
+ if (r > 0)
continue;
/* In the legacy hierarchy, attach the process to the request cgroup if possible, and if not to the
/* If this controller is delegated and realized, honour the caller's request for the cgroup suffix. */
if (delegated_mask & u->cgroup_realized_mask & bit) {
- q = cg_attach(cgroup_controller_to_string(c), p, pid);
- if (q >= 0)
+ r = cg_attach(cgroup_controller_to_string(c), p, pid);
+ if (r >= 0)
continue; /* Success! */
- log_unit_debug_errno(u, q, "Failed to attach PID " PID_FMT " to requested cgroup %s in controller %s, falling back to unit's cgroup: %m",
+ log_unit_debug_errno(u, r, "Failed to attach PID " PID_FMT " to requested cgroup %s in controller %s, falling back to unit's cgroup: %m",
pid, empty_to_root(p), cgroup_controller_to_string(c));
}
if (!realized)
continue; /* Not even realized in the root slice? Then let's not bother */
- q = cg_attach(cgroup_controller_to_string(c), realized, pid);
- if (q < 0)
- log_unit_debug_errno(u, q, "Failed to attach PID " PID_FMT " to realized cgroup %s in controller %s, ignoring: %m",
+ r = cg_attach(cgroup_controller_to_string(c), realized, pid);
+ if (r < 0)
+ log_unit_debug_errno(u, r, "Failed to attach PID " PID_FMT " to realized cgroup %s in controller %s, ignoring: %m",
pid, realized, cgroup_controller_to_string(c));
}
}
- return r;
+ return ret;
}
static bool unit_has_mask_realized(
return sd_bus_message_close_container(reply);
}
+static int bus_property_get_exec_dir(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ ExecDirectory *d = userdata;
+ int r;
+
+ assert(bus);
+ assert(d);
+ assert(property);
+ assert(reply);
+
+ r = sd_bus_message_open_container(reply, 'a', "s");
+ if (r < 0)
+ return r;
+
+ for (size_t i = 0; i < d->n_items; i++) {
+ r = sd_bus_message_append_basic(reply, 's', d->items[i].path);
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_message_close_container(reply);
+}
+
+static int bus_property_get_exec_dir_symlink(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ ExecDirectory *d = userdata;
+ int r;
+
+ assert(bus);
+ assert(d);
+ assert(property);
+ assert(reply);
+
+ r = sd_bus_message_open_container(reply, 'a', "(sst)");
+ if (r < 0)
+ return r;
+
+ for (size_t i = 0; i < d->n_items; i++) {
+ char **dst;
+
+ STRV_FOREACH(dst, d->items[i].symlinks) {
+ r = sd_bus_message_append(reply, "(sst)", d->items[i].path, *dst, 0 /* flags, unused for now */);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return sd_bus_message_close_container(reply);
+}
+
const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Personality", "s", property_get_personality, offsetof(ExecContext, personality), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LockPersonality", "b", bus_property_get_bool, offsetof(ExecContext, lock_personality), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RuntimeDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectoryPreserve", "s", property_get_exec_preserve_mode, offsetof(ExecContext, runtime_directory_preserve_mode), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RuntimeDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME].mode), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("RuntimeDirectory", "as", NULL, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME].paths), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RuntimeDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_RUNTIME]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("StateDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("StateDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE].mode), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("StateDirectory", "as", NULL, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE].paths), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("StateDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_STATE]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("CacheDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("CacheDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE].mode), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("CacheDirectory", "as", NULL, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE].paths), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("CacheDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_CACHE]), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LogsDirectorySymlink", "a(sst)", bus_property_get_exec_dir_symlink, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("LogsDirectoryMode", "u", bus_property_get_mode, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS].mode), SD_BUS_VTABLE_PROPERTY_CONST),
- SD_BUS_PROPERTY("LogsDirectory", "as", NULL, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS].paths), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("LogsDirectory", "as", bus_property_get_exec_dir, offsetof(ExecContext, directories[EXEC_DIRECTORY_LOGS]), 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", NULL, offsetof(ExecContext, directories[EXEC_DIRECTORY_CONFIGURATION].paths), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("ConfigurationDirectory", "as", bus_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),
SD_BUS_PROPERTY("MemoryDenyWriteExecute", "b", bus_property_get_bool, offsetof(ExecContext, memory_deny_write_execute), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RestrictRealtime", "b", bus_property_get_bool, offsetof(ExecContext, restrict_realtime), SD_BUS_VTABLE_PROPERTY_CONST),
d = c->directories + i;
if (strv_isempty(l)) {
- d->paths = strv_free(d->paths);
+ exec_directory_done(d);
unit_write_settingf(u, flags, name, "%s=", name);
} else {
_cleanup_free_ char *joined = NULL;
+ char **source;
- r = strv_extend_strv(&d->paths, l, true);
- if (r < 0)
- return r;
+ STRV_FOREACH(source, l) {
+ r = exec_directory_add(&d->items, &d->n_items, *source, NULL);
+ if (r < 0)
+ return log_oom();
+ }
joined = unit_concat_strv(l, UNIT_ESCAPE_SPECIFIERS);
if (!joined)
extension_images = mount_image_free_many(extension_images, &n_extension_images);
return 1;
+
+ } else if (STR_IN_SET(name, "StateDirectorySymlink", "RuntimeDirectorySymlink", "CacheDirectorySymlink", "LogsDirectorySymlink")) {
+ char *source, *destination;
+ ExecDirectory *directory;
+ uint64_t symlink_flags; /* No flags for now, reserved for future uses. */
+ ExecDirectoryType i;
+
+ assert_se((i = exec_directory_type_symlink_from_string(name)) >= 0);
+ directory = c->directories + i;
+
+ r = sd_bus_message_enter_container(message, 'a', "(sst)");
+ if (r < 0)
+ return r;
+
+ while ((r = sd_bus_message_read(message, "(sst)", &source, &destination, &symlink_flags)) > 0) {
+ if (!path_is_valid(source))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not valid.", source);
+ if (path_is_absolute(source))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is absolute.", source);
+ if (!path_is_normalized(source))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not normalized.", source);
+ if (!path_is_valid(destination))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not valid.", destination);
+ if (path_is_absolute(destination))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is absolute.", destination);
+ if (!path_is_normalized(destination))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not normalized.", destination);
+ if (symlink_flags != 0)
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be zero.");
+
+ if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+ _cleanup_free_ char *destination_escaped = NULL, *source_escaped = NULL;
+ ExecDirectoryItem *item = NULL;
+
+ /* Adding new directories is supported from both *DirectorySymlink methods and the
+ * older ones, so try to find an existing configuration first and create it if it's
+ * not there yet. */
+ for (size_t j = 0; j < directory->n_items; ++j)
+ if (path_equal(source, directory->items[j].path)) {
+ item = &directory->items[j];
+ break;
+ }
+
+ if (item)
+ r = strv_extend(&item->symlinks, destination);
+ else
+ r = exec_directory_add(&directory->items, &directory->n_items, source, STRV_MAKE(destination));
+ if (r < 0)
+ return r;
+
+ /* Need to store them in the unit with the escapes, so that they can be parsed again */
+ source_escaped = xescape(source, ":");
+ destination_escaped = xescape(destination, ":");
+ if (!source_escaped || !destination_escaped)
+ return -ENOMEM;
+
+ unit_write_settingf(
+ u, flags|UNIT_ESCAPE_SPECIFIERS, exec_directory_type_to_string(i),
+ "%s=%s:%s",
+ exec_directory_type_to_string(i),
+ source_escaped,
+ destination_escaped);
+ }
+ }
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(message);
+ if (r < 0)
+ return r;
+
+ return 1;
+
}
return 0;
}
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
- _cleanup_free_ char *pre = NULL, *joined = NULL;
+ _cleanup_free_ char *joined = NULL;
const char *n;
if (!p->prefix[t])
continue;
- if (strv_isempty(c->directories[t].paths))
+ if (c->directories[t].n_items == 0)
continue;
n = exec_directory_env_name_to_string(t);
if (!n)
continue;
- pre = strjoin(p->prefix[t], "/");
- if (!pre)
- return -ENOMEM;
+ for (size_t i = 0; i < c->directories[t].n_items; i++) {
+ _cleanup_free_ char *prefixed = NULL;
- joined = strv_join_full(c->directories[t].paths, ":", pre, true);
- if (!joined)
- return -ENOMEM;
+ prefixed = path_join(p->prefix[t], c->directories[t].items[i].path);
+ if (!prefixed)
+ return -ENOMEM;
+
+ if (!strextend_with_separator(&joined, ":", prefixed))
+ return -ENOMEM;
+ }
x = strjoin(n, "=", joined);
if (!x)
if (params && !params->prefix[t])
continue;
- if (!strv_isempty(context->directories[t].paths))
+ if (context->directories[t].n_items > 0)
return true;
}
}
if (context->dynamic_user &&
- (!strv_isempty(context->directories[EXEC_DIRECTORY_STATE].paths) ||
- !strv_isempty(context->directories[EXEC_DIRECTORY_CACHE].paths) ||
- !strv_isempty(context->directories[EXEC_DIRECTORY_LOGS].paths)))
+ (context->directories[EXEC_DIRECTORY_STATE].n_items > 0 ||
+ context->directories[EXEC_DIRECTORY_CACHE].n_items > 0 ||
+ context->directories[EXEC_DIRECTORY_LOGS].n_items > 0))
return true;
if (context->log_namespace)
return true;
}
+static int create_many_symlinks(const char *root, const char *source, char **symlinks) {
+ _cleanup_free_ char *src_abs = NULL;
+ char **dst;
+ int r;
+
+ assert(source);
+
+ src_abs = path_join(root, source);
+ if (!src_abs)
+ return -ENOMEM;
+
+ STRV_FOREACH(dst, symlinks) {
+ _cleanup_free_ char *dst_abs = NULL;
+
+ dst_abs = path_join(root, *dst);
+ if (!dst_abs)
+ return -ENOMEM;
+
+ r = mkdir_parents_label(dst_abs, 0755);
+ if (r < 0)
+ return r;
+
+ r = symlink_idempotent(src_abs, dst_abs, true);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
static int setup_exec_directory(
const ExecContext *context,
const ExecParameters *params,
uid_t uid,
gid_t gid,
ExecDirectoryType type,
+ bool needs_mount_namespace,
int *exit_status) {
static const int exit_status_table[_EXEC_DIRECTORY_TYPE_MAX] = {
[EXEC_DIRECTORY_LOGS] = EXIT_LOGS_DIRECTORY,
[EXEC_DIRECTORY_CONFIGURATION] = EXIT_CONFIGURATION_DIRECTORY,
};
- char **rt;
int r;
assert(context);
gid = 0;
}
- STRV_FOREACH(rt, context->directories[type].paths) {
+ for (size_t i = 0; i < context->directories[type].n_items; i++) {
_cleanup_free_ char *p = NULL, *pp = NULL;
- p = path_join(params->prefix[type], *rt);
+ p = path_join(params->prefix[type], context->directories[type].items[i].path);
if (!p) {
r = -ENOMEM;
goto fail;
if (r < 0)
goto fail;
- if (!path_extend(&pp, *rt)) {
+ if (!path_extend(&pp, context->directories[type].items[i].path)) {
r = -ENOMEM;
goto fail;
}
goto fail;
}
- /* And link it up from the original place */
+ /* And link it up from the original place. Note that if a mount namespace is going to be
+ * used, then this symlink remains on the host, and a new one for the child namespace will
+ * be created later. */
r = symlink_idempotent(pp, p, true);
if (r < 0)
goto fail;
if (r < 0)
goto fail;
- q = path_join(params->prefix[type], "private", *rt);
+ q = path_join(params->prefix[type], "private", context->directories[type].items[i].path);
if (!q) {
r = -ENOMEM;
goto fail;
if (((st.st_mode ^ context->directories[type].mode) & 07777) != 0)
log_warning("%s \'%s\' already exists but the mode is different. "
"(File system: %o %sMode: %o)",
- exec_directory_type_to_string(type), *rt,
+ exec_directory_type_to_string(type), context->directories[type].items[i].path,
st.st_mode & 07777, exec_directory_type_to_string(type), context->directories[type].mode & 07777);
continue;
goto fail;
}
+ /* If we are not going to run in a namespace, set up the symlinks - otherwise
+ * they are set up later, to allow configuring empty var/run/etc. */
+ if (!needs_mount_namespace)
+ for (size_t i = 0; i < context->directories[type].n_items; i++) {
+ r = create_many_symlinks(params->prefix[type],
+ context->directories[type].items[i].path,
+ context->directories[type].items[i].symlinks);
+ if (r < 0)
+ goto fail;
+ }
+
return 0;
fail:
if (!params->prefix[t])
continue;
- n += strv_length(context->directories[t].paths);
+ n += context->directories[t].n_items;
}
if (n <= 0) {
}
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
- char **suffix;
-
if (!params->prefix[t])
continue;
- if (strv_isempty(context->directories[t].paths))
+ if (context->directories[t].n_items == 0)
continue;
if (exec_directory_is_private(context, t) &&
goto finish;
}
- STRV_FOREACH(suffix, context->directories[t].paths) {
+ for (size_t i = 0; i < context->directories[t].n_items; i++) {
char *s, *d;
if (exec_directory_is_private(context, t))
- s = path_join(params->prefix[t], "private", *suffix);
+ s = path_join(params->prefix[t], "private", context->directories[t].items[i].path);
else
- s = path_join(params->prefix[t], *suffix);
+ s = path_join(params->prefix[t], context->directories[t].items[i].path);
if (!s) {
r = -ENOMEM;
goto finish;
/* When RootDirectory= or RootImage= are set, then the symbolic link to the private
* directory is not created on the root directory. So, let's bind-mount the directory
* on the 'non-private' place. */
- d = path_join(params->prefix[t], *suffix);
+ d = path_join(params->prefix[t], context->directories[t].items[i].path);
else
d = strdup(s);
if (!d) {
return r;
}
+/* ret_symlinks will contain a list of pairs src:dest that describes
+ * the symlinks to create later on. For example, the symlinks needed
+ * to safely give private directories to DynamicUser=1 users. */
+static int compile_symlinks(
+ const ExecContext *context,
+ const ExecParameters *params,
+ char ***ret_symlinks) {
+
+ _cleanup_strv_free_ char **symlinks = NULL;
+ int r;
+
+ assert(context);
+ assert(params);
+ assert(ret_symlinks);
+
+ for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) {
+ for (size_t i = 0; i < context->directories[dt].n_items; i++) {
+ _cleanup_free_ char *private_path = NULL, *path = NULL;
+ char **symlink;
+
+ STRV_FOREACH(symlink, context->directories[dt].items[i].symlinks) {
+ _cleanup_free_ char *src_abs = NULL, *dst_abs = NULL;
+
+ src_abs = path_join(params->prefix[dt], context->directories[dt].items[i].path);
+ dst_abs = path_join(params->prefix[dt], *symlink);
+ if (!src_abs || !dst_abs)
+ return -ENOMEM;
+
+ r = strv_consume_pair(&symlinks, TAKE_PTR(src_abs), TAKE_PTR(dst_abs));
+ if (r < 0)
+ return r;
+ }
+
+ if (!exec_directory_is_private(context, dt))
+ continue;
+
+ private_path = path_join(params->prefix[dt], "private", context->directories[dt].items[i].path);
+ if (!private_path)
+ return -ENOMEM;
+
+ path = path_join(params->prefix[dt], context->directories[dt].items[i].path);
+ if (!path)
+ return -ENOMEM;
+
+ r = strv_consume_pair(&symlinks, TAKE_PTR(private_path), TAKE_PTR(path));
+ if (r < 0)
+ return r;
+ }
+ }
+
+ *ret_symlinks = TAKE_PTR(symlinks);
+
+ return 0;
+}
+
static bool insist_on_sandboxing(
const ExecContext *context,
const char *root_dir,
const ExecRuntime *runtime,
char **error_path) {
- _cleanup_strv_free_ char **empty_directories = NULL;
+ _cleanup_strv_free_ char **empty_directories = NULL, **symlinks = NULL;
const char *tmp_dir = NULL, *var_tmp_dir = NULL;
const char *root_dir = NULL, *root_image = NULL;
_cleanup_free_ char *creds_path = NULL, *incoming_dir = NULL, *propagate_dir = NULL;
if (r < 0)
return r;
+ /* Symlinks for exec dirs are set up after other mounts, before they are made read-only. */
+ r = compile_symlinks(context, params, &symlinks);
+ if (r < 0)
+ return r;
+
needs_sandboxing = (params->flags & EXEC_APPLY_SANDBOXING) && !(command_flags & EXEC_COMMAND_FULLY_PRIVILEGED);
if (needs_sandboxing) {
/* The runtime struct only contains the parent of the private /tmp,
needs_sandboxing ? context->exec_paths : NULL,
needs_sandboxing ? context->no_exec_paths : NULL,
empty_directories,
+ symlinks,
bind_mounts,
n_bind_mounts,
context->temporary_filesystems,
* directories. */
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
- char **i;
-
if (t == EXEC_DIRECTORY_CONFIGURATION)
continue;
if (!p->prefix[t])
continue;
- STRV_FOREACH(i, c->directories[t].paths) {
+ for (size_t i = 0; i < c->directories[t].n_items; i++) {
char *e;
if (exec_directory_is_private(c, t))
- e = path_join(p->prefix[t], "private", *i);
+ e = path_join(p->prefix[t], "private", c->directories[t].items[i].path);
else
- e = path_join(p->prefix[t], *i);
+ e = path_join(p->prefix[t], c->directories[t].items[i].path);
if (!e)
return -ENOMEM;
}
}
+ needs_mount_namespace = exec_needs_mount_namespace(context, params, runtime);
+
for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) {
- r = setup_exec_directory(context, params, uid, gid, dt, exit_status);
+ r = setup_exec_directory(context, params, uid, gid, dt, needs_mount_namespace, exit_status);
if (r < 0)
return log_unit_error_errno(unit, r, "Failed to set up special execution directory in %s: %m", params->prefix[dt]);
}
log_unit_warning(unit, "PrivateIPC=yes is configured, but the kernel does not support IPC namespaces, ignoring.");
}
- needs_mount_namespace = exec_needs_mount_namespace(context, params, runtime);
if (needs_mount_namespace) {
_cleanup_free_ char *error_path = NULL;
c->address_families = set_free(c->address_families);
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++)
- c->directories[t].paths = strv_free(c->directories[t].paths);
+ exec_directory_done(&c->directories[t]);
c->log_level_max = -1;
}
int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) {
- char **i;
-
assert(c);
if (!runtime_prefix)
return 0;
- STRV_FOREACH(i, c->directories[EXEC_DIRECTORY_RUNTIME].paths) {
+ for (size_t i = 0; i < c->directories[EXEC_DIRECTORY_RUNTIME].n_items; i++) {
_cleanup_free_ char *p = NULL;
if (exec_directory_is_private(c, EXEC_DIRECTORY_RUNTIME))
- p = path_join(runtime_prefix, "private", *i);
+ p = path_join(runtime_prefix, "private", c->directories[EXEC_DIRECTORY_RUNTIME].items[i].path);
else
- p = path_join(runtime_prefix, *i);
+ p = path_join(runtime_prefix, c->directories[EXEC_DIRECTORY_RUNTIME].items[i].path);
if (!p)
return -ENOMEM;
/* We execute this synchronously, since we need to be sure this is gone when we start the
* service next. */
(void) rm_rf(p, REMOVE_ROOT);
+
+ char **symlink;
+ STRV_FOREACH(symlink, c->directories[EXEC_DIRECTORY_RUNTIME].items[i].symlinks) {
+ _cleanup_free_ char *symlink_abs = NULL;
+
+ if (exec_directory_is_private(c, EXEC_DIRECTORY_RUNTIME))
+ symlink_abs = path_join(runtime_prefix, "private", *symlink);
+ else
+ symlink_abs = path_join(runtime_prefix, *symlink);
+ if (!symlink_abs)
+ return -ENOMEM;
+
+ (void) unlink(symlink_abs);
+ }
+
}
return 0;
for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) {
fprintf(f, "%s%sMode: %04o\n", prefix, exec_directory_type_to_string(dt), c->directories[dt].mode);
- STRV_FOREACH(d, c->directories[dt].paths)
- fprintf(f, "%s%s: %s\n", prefix, exec_directory_type_to_string(dt), *d);
+ for (size_t i = 0; i < c->directories[dt].n_items; i++) {
+ fprintf(f, "%s%s: %s\n", prefix, exec_directory_type_to_string(dt), c->directories[dt].items[i].path);
+
+ STRV_FOREACH(d, c->directories[dt].items[i].symlinks)
+ fprintf(f, "%s%s: %s:%s\n", prefix, exec_directory_type_symlink_to_string(dt), c->directories[dt].items[i].path, *d);
+ }
}
fprintf(f, "%sTimeoutCleanSec: %s\n", prefix, FORMAT_TIMESPAN(c->timeout_clean_usec, USEC_PER_SEC));
assert(ret);
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
- char **i;
-
if (!FLAGS_SET(mask, 1U << t))
continue;
if (!prefix[t])
continue;
- STRV_FOREACH(i, c->directories[t].paths) {
+ for (size_t i = 0; i < c->directories[t].n_items; i++) {
char *j;
- j = path_join(prefix[t], *i);
+ j = path_join(prefix[t], c->directories[t].items[i].path);
if (!j)
return -ENOMEM;
/* Also remove private directories unconditionally. */
if (t != EXEC_DIRECTORY_CONFIGURATION) {
- j = path_join(prefix[t], "private", *i);
+ j = path_join(prefix[t], "private", c->directories[t].items[i].path);
+ if (!j)
+ return -ENOMEM;
+
+ r = strv_consume(&l, j);
+ if (r < 0)
+ return r;
+ }
+
+ char **symlink;
+ STRV_FOREACH(symlink, c->directories[t].items[i].symlinks) {
+ j = path_join(prefix[t], *symlink);
if (!j)
return -ENOMEM;
assert(ret);
for (ExecDirectoryType t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++)
- if (!strv_isempty(c->directories[t].paths))
+ if (c->directories[t].n_items > 0)
mask |= 1U << t;
*ret = mask;
return mfree(lc);
}
+void exec_directory_done(ExecDirectory *d) {
+ if (!d)
+ return;
+
+ for (size_t i = 0; i < d->n_items; i++) {
+ free(d->items[i].path);
+ strv_free(d->items[i].symlinks);
+ }
+
+ d->items = mfree(d->items);
+ d->n_items = 0;
+ d->mode = 0755;
+}
+
+int exec_directory_add(ExecDirectoryItem **d, size_t *n, const char *path, char **symlinks) {
+ _cleanup_strv_free_ char **s = NULL;
+ _cleanup_free_ char *p = NULL;
+
+ assert(d);
+ assert(n);
+ assert(path);
+
+ p = strdup(path);
+ if (!p)
+ return -ENOMEM;
+
+ if (symlinks) {
+ s = strv_copy(symlinks);
+ if (!s)
+ return -ENOMEM;
+ }
+
+ if (!GREEDY_REALLOC(*d, *n + 1))
+ return -ENOMEM;
+
+ (*d)[(*n) ++] = (ExecDirectoryItem) {
+ .path = TAKE_PTR(p),
+ .symlinks = TAKE_PTR(s),
+ };
+
+ return 0;
+}
+
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_set_credential_hash_ops, char, string_hash_func, string_compare_func, ExecSetCredential, exec_set_credential_free);
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_load_credential_hash_ops, char, string_hash_func, string_compare_func, ExecLoadCredential, exec_load_credential_free);
DEFINE_STRING_TABLE_LOOKUP(exec_directory_type, ExecDirectoryType);
+/* This table maps ExecDirectoryType to the symlink setting it is configured with in the unit */
+static const char* const exec_directory_type_symlink_table[_EXEC_DIRECTORY_TYPE_MAX] = {
+ [EXEC_DIRECTORY_RUNTIME] = "RuntimeDirectorySymlink",
+ [EXEC_DIRECTORY_STATE] = "StateDirectorySymlink",
+ [EXEC_DIRECTORY_CACHE] = "CacheDirectorySymlink",
+ [EXEC_DIRECTORY_LOGS] = "LogsDirectorySymlink",
+ [EXEC_DIRECTORY_CONFIGURATION] = "ConfigurationDirectorySymlink",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(exec_directory_type_symlink, ExecDirectoryType);
+
/* And this table maps ExecDirectoryType too, but to a generic term identifying the type of resource. This
* one is supposed to be generic enough to be used for unit types that don't use ExecContext and per-unit
* directories, specifically .timer units with their timestamp touch file. */
_EXEC_DIRECTORY_TYPE_INVALID = -EINVAL,
} ExecDirectoryType;
+typedef struct ExecDirectoryItem {
+ char *path;
+ char **symlinks;
+} ExecDirectoryItem;
+
typedef struct ExecDirectory {
- char **paths;
mode_t mode;
+ size_t n_items;
+ ExecDirectoryItem *items;
} ExecDirectory;
typedef enum ExecCleanMask {
ExecLoadCredential *exec_load_credential_free(ExecLoadCredential *lc);
DEFINE_TRIVIAL_CLEANUP_FUNC(ExecLoadCredential*, exec_load_credential_free);
+void exec_directory_done(ExecDirectory *d);
+int exec_directory_add(ExecDirectoryItem **d, size_t *n, const char *path, char **symlinks);
+
extern const struct hash_ops exec_set_credential_hash_ops;
extern const struct hash_ops exec_load_credential_hash_ops;
const char* exec_directory_type_to_string(ExecDirectoryType i) _const_;
ExecDirectoryType exec_directory_type_from_string(const char *s) _pure_;
+const char* exec_directory_type_symlink_to_string(ExecDirectoryType i) _const_;
+ExecDirectoryType exec_directory_type_symlink_from_string(const char *s) _pure_;
+
const char* exec_resource_type_to_string(ExecDirectoryType i) _const_;
ExecDirectoryType exec_resource_type_from_string(const char *s) _pure_;
{{type}}.Personality, config_parse_personality, 0, offsetof({{type}}, exec_context.personality)
{{type}}.RuntimeDirectoryPreserve, config_parse_runtime_preserve_mode, 0, offsetof({{type}}, exec_context.runtime_directory_preserve_mode)
{{type}}.RuntimeDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME].mode)
-{{type}}.RuntimeDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME].paths)
+{{type}}.RuntimeDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_RUNTIME])
{{type}}.StateDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE].mode)
-{{type}}.StateDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE].paths)
+{{type}}.StateDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_STATE])
{{type}}.CacheDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CACHE].mode)
-{{type}}.CacheDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CACHE].paths)
+{{type}}.CacheDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CACHE])
{{type}}.LogsDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_LOGS].mode)
-{{type}}.LogsDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_LOGS].paths)
+{{type}}.LogsDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_LOGS])
{{type}}.ConfigurationDirectoryMode, config_parse_mode, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].mode)
-{{type}}.ConfigurationDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].paths)
+{{type}}.ConfigurationDirectory, config_parse_exec_directories, 0, offsetof({{type}}, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION])
{{type}}.SetCredential, config_parse_set_credential, 0, offsetof({{type}}, exec_context)
{{type}}.SetCredentialEncrypted, config_parse_set_credential, 1, offsetof({{type}}, exec_context)
{{type}}.LoadCredential, config_parse_load_credential, 0, offsetof({{type}}, exec_context)
DEFINE_CONFIG_PARSE_ENUM(config_parse_status_unit_format, status_unit_format, StatusUnitFormat, "Failed to parse status unit format");
DEFINE_CONFIG_PARSE_ENUM_FULL(config_parse_socket_timestamping, socket_timestamping_from_string_harder, SocketTimestamping, "Failed to parse timestamping precision");
+bool contains_instance_specifier_superset(const char *s) {
+ const char *p, *q;
+ bool percent = false;
+
+ assert(s);
+
+ p = strchr(s, '@');
+ if (!p)
+ return false;
+
+ p++; /* Skip '@' */
+
+ q = strrchr(p, '.');
+ if (!q)
+ q = p + strlen(p);
+
+ /* If the string is just the instance specifier, it's not a superset of the instance. */
+ if (memcmp_nn(p, q - p, "%i", strlen("%i")) == 0)
+ return false;
+
+ /* %i, %n and %N all expand to the instance or a superset of it. */
+ for (; p < q; p++) {
+ if (*p == '%')
+ percent = !percent;
+ else if (percent) {
+ if (IN_SET(*p, 'n', 'N', 'i'))
+ return true;
+ percent = false;
+ }
+ }
+
+ return false;
+}
+
+/* `name` is the rendered version of `format` via `unit_printf` or similar functions. */
+int unit_is_likely_recursive_template_dependency(Unit *u, const char *name, const char *format) {
+ const char *fragment_path;
+ int r;
+
+ assert(u);
+ assert(name);
+
+ /* If a template unit has a direct dependency on itself that includes the unit instance as part of
+ * the template instance via a unit specifier (%i, %n or %N), this will almost certainly lead to
+ * infinite recursion as systemd will keep instantiating new instances of the template unit.
+ * https://github.com/systemd/systemd/issues/17602 shows a good example of how this can happen in
+ * practice. To guard against this, we check for templates that depend on themselves and have the
+ * instantiated unit instance included as part of the template instance of the dependency via a
+ * specifier.
+ *
+ * For example, if systemd-notify@.service depends on systemd-notify@%n.service, this will result in
+ * infinite recursion.
+ */
+
+ if (!unit_name_is_valid(name, UNIT_NAME_INSTANCE))
+ return false;
+
+ if (!unit_name_prefix_equal(u->id, name))
+ return false;
+
+ if (u->type != unit_name_to_type(name))
+ return false;
+
+ r = unit_file_find_fragment(u->manager->unit_id_map, u->manager->unit_name_map, name, &fragment_path, NULL);
+ if (r < 0)
+ return r;
+
+ /* Fragment paths should also be equal as a custom fragment for a specific template instance
+ * wouldn't necessarily lead to infinite recursion. */
+ if (!path_equal_ptr(u->fragment_path, fragment_path))
+ return false;
+
+ if (!contains_instance_specifier_superset(format))
+ return false;
+
+ return true;
+}
+
int config_parse_unit_deps(
const char *unit,
const char *filename,
continue;
}
+ r = unit_is_likely_recursive_template_dependency(u, k, word);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to determine if '%s' is a recursive dependency, ignoring: %m", k);
+ continue;
+ }
+ if (r > 0) {
+ log_syntax(unit, LOG_DEBUG, filename, line, 0,
+ "Dropping dependency %s=%s that likely leads to infinite recursion.",
+ unit_dependency_to_string(d), word);
+ continue;
+ }
+
r = unit_add_dependency_by_name(u, d, k, true, UNIT_DEPENDENCY_FILE);
if (r < 0)
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to add dependency on %s, ignoring: %m", k);
void *data,
void *userdata) {
- char***rt = data;
+ ExecDirectory *ed = data;
const Unit *u = userdata;
int r;
if (isempty(rvalue)) {
/* Empty assignment resets the list */
- *rt = strv_free(*rt);
+ exec_directory_done(ed);
return 0;
}
for (const char *p = rvalue;;) {
- _cleanup_free_ char *word = NULL, *k = NULL;
+ _cleanup_free_ char *tuple = NULL;
- r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE);
+ r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
- "Invalid syntax, ignoring: %s", rvalue);
+ "Invalid syntax %s=%s, ignoring: %m", lvalue, rvalue);
return 0;
}
if (r == 0)
return 0;
- r = unit_path_printf(u, word, &k);
+ _cleanup_free_ char *src = NULL, *dest = NULL;
+ const char *q = tuple;
+ r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &src, &dest, NULL);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r <= 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r ?: SYNTHETIC_ERRNO(EINVAL),
+ "Invalid syntax in %s=, ignoring: %s", lvalue, tuple);
+ return 0;
+ }
+
+ _cleanup_free_ char *sresolved = NULL;
+ r = unit_path_printf(u, src, &sresolved);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
- "Failed to resolve unit specifiers in \"%s\", ignoring: %m", word);
+ "Failed to resolve unit specifiers in \"%s\", ignoring: %m", src);
continue;
}
- r = path_simplify_and_warn(k, PATH_CHECK_RELATIVE, unit, filename, line, lvalue);
+ r = path_simplify_and_warn(sresolved, PATH_CHECK_RELATIVE, unit, filename, line, lvalue);
if (r < 0)
continue;
- if (path_startswith(k, "private")) {
+ if (path_startswith(sresolved, "private")) {
log_syntax(unit, LOG_WARNING, filename, line, 0,
- "%s= path can't be 'private', ignoring assignment: %s", lvalue, word);
+ "%s= path can't be 'private', ignoring assignment: %s", lvalue, tuple);
continue;
}
- r = strv_push(rt, k);
+ /* For State and Runtime directories we support an optional destination parameter, which
+ * will be used to create a symlink to the source. */
+ _cleanup_strv_free_ char **symlinks = NULL;
+ if (!isempty(dest)) {
+ _cleanup_free_ char *dresolved = NULL;
+
+ if (streq(lvalue, "ConfigurationDirectory")) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Destination parameter is not supported for ConfigurationDirectory, ignoring: %s", tuple);
+ continue;
+ }
+
+ r = unit_path_printf(u, dest, &dresolved);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to resolve unit specifiers in \"%s\", ignoring: %m", dest);
+ continue;
+ }
+
+ r = path_simplify_and_warn(dresolved, PATH_CHECK_RELATIVE, unit, filename, line, lvalue);
+ if (r < 0)
+ continue;
+
+ r = strv_consume(&symlinks, TAKE_PTR(dresolved));
+ if (r < 0)
+ return log_oom();
+ }
+
+ r = exec_directory_add(&ed->items, &ed->n_items, sresolved, symlinks);
if (r < 0)
return log_oom();
- k = NULL;
}
}
#include "conf-parser.h"
#include "unit.h"
+/* These functions are declared in the header to make them accessible to unit tests. */
+bool contains_instance_specifier_superset(const char *s);
+int unit_is_likely_recursive_template_dependency(Unit *u, const char *name, const char *format);
+
/* Config-parsing helpers relevant only for sources under src/core/ */
int parse_crash_chvt(const char *value, int *data);
int parse_confirm_spawn(const char *value, char **console);
drop_nop(mounts, n_mounts);
}
+static int create_symlinks_from_tuples(const char *root, char **strv_symlinks) {
+ char **src, **dst;
+ int r;
+
+ STRV_FOREACH_PAIR(src, dst, strv_symlinks) {
+ _cleanup_free_ char *src_abs = NULL, *dst_abs = NULL;
+
+ src_abs = path_join(root, *src);
+ dst_abs = path_join(root, *dst);
+ if (!src_abs || !dst_abs)
+ return -ENOMEM;
+
+ r = mkdir_parents_label(dst_abs, 0755);
+ if (r < 0)
+ return r;
+
+ r = symlink_idempotent(src_abs, dst_abs, true);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
static int apply_mounts(
const char *root,
const NamespaceInfo *ns_info,
MountEntry *mounts,
size_t *n_mounts,
+ char **exec_dir_symlinks,
char **error_path) {
_cleanup_fclose_ FILE *proc_self_mountinfo = NULL;
normalize_mounts(root, mounts, n_mounts);
}
+ /* Now that all filesystems have been set up, but before the
+ * read-only switches are flipped, create the exec dirs symlinks.
+ * Note that when /var/lib is not empty/tmpfs, these symlinks will already
+ * exist, which means this will be a no-op. */
+ r = create_symlinks_from_tuples(root, exec_dir_symlinks);
+ if (r < 0)
+ return r;
+
/* Create a deny list we can pass to bind_mount_recursive() */
deny_list = new(char*, (*n_mounts)+1);
if (!deny_list)
char** exec_paths,
char** no_exec_paths,
char** empty_directories,
+ char** exec_dir_symlinks,
const BindMount *bind_mounts,
size_t n_bind_mounts,
const TemporaryFileSystem *temporary_filesystems,
(void) base_filesystem_create(root, UID_INVALID, GID_INVALID);
/* Now make the magic happen */
- r = apply_mounts(root, ns_info, mounts, &n_mounts, error_path);
+ r = apply_mounts(root, ns_info, mounts, &n_mounts, exec_dir_symlinks, error_path);
if (r < 0)
goto finish;
char **exec_paths,
char **no_exec_paths,
char **empty_directories,
+ char **exec_dir_symlinks,
const BindMount *bind_mounts,
size_t n_bind_mounts,
const TemporaryFileSystem *temporary_filesystems,
scope_enter_dead(s, SCOPE_FAILURE_RESOURCES);
return r;
}
+ if (r == 0) {
+ log_unit_warning(u, "No PIDs left to attach to the scope's control group, refusing: %m");
+ scope_enter_dead(s, SCOPE_FAILURE_RESOURCES);
+ return -ECHILD;
+ }
+ log_unit_debug(u, "%i %s added to scope's control group.", r, r == 1 ? "process" : "processes");
s->result = SCOPE_SUCCESS;
if (!u->manager->prefix[dt])
continue;
- char **dp;
- STRV_FOREACH(dp, c->directories[dt].paths) {
+ for (size_t i = 0; i < c->directories[dt].n_items; i++) {
_cleanup_free_ char *p = NULL;
- p = path_join(u->manager->prefix[dt], *dp);
+ p = path_join(u->manager->prefix[dt], c->directories[dt].items[i].path);
if (!p)
return -ENOMEM;
/* For the following three directory types we need write access, and /var/ is possibly on the root
* fs. Hence order after systemd-remount-fs.service, to ensure things are writable. */
- if (!strv_isempty(c->directories[EXEC_DIRECTORY_STATE].paths) ||
- !strv_isempty(c->directories[EXEC_DIRECTORY_CACHE].paths) ||
- !strv_isempty(c->directories[EXEC_DIRECTORY_LOGS].paths)) {
+ if (c->directories[EXEC_DIRECTORY_STATE].n_items > 0 ||
+ c->directories[EXEC_DIRECTORY_CACHE].n_items > 0 ||
+ c->directories[EXEC_DIRECTORY_LOGS].n_items > 0) {
r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_REMOUNT_FS_SERVICE, true, UNIT_DEPENDENCY_FILE);
if (r < 0)
return r;
assert_se(hdo = user_record_home_directory(h));
hd = strdupa_safe(hdo); /* copy the string out, since it might change later in the home record object */
- r = home_setup(h, 0, cache, setup, &header_home);
+ r = home_setup(h, 0, setup, cache, &header_home);
if (r < 0)
return r;
assert_se(hdo = user_record_home_directory(h));
hd = strdupa_safe(hdo);
- r = home_setup(h, 0, cache, setup, &header_home);
+ r = home_setup(h, 0, setup, cache, &header_home);
if (r < 0)
return r;
int home_resize_directory(
UserRecord *h,
HomeSetupFlags flags,
- PasswordCache *cache,
HomeSetup *setup,
+ PasswordCache *cache,
UserRecord **ret_home) {
_cleanup_(user_record_unrefp) UserRecord *embedded_home = NULL, *new_home = NULL;
assert(ret_home);
assert(IN_SET(user_record_storage(h), USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT));
- r = home_setup(h, flags, cache, setup, NULL);
+ r = home_setup(h, flags, setup, cache, NULL);
if (r < 0)
return r;
int home_setup_directory(UserRecord *h, HomeSetup *setup);
int home_activate_directory(UserRecord *h, HomeSetup *setup, PasswordCache *cache, UserRecord **ret_home);
int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserRecord **ret_home);
-int home_resize_directory(UserRecord *h, HomeSetupFlags flags, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_home);
+int home_resize_directory(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup, PasswordCache *cache, UserRecord **ret_home);
int home_setup_fscrypt(
UserRecord *h,
- const PasswordCache *cache,
- HomeSetup *setup) {
+ HomeSetup *setup,
+ const PasswordCache *cache) {
_cleanup_(erase_and_freep) void *volume_key = NULL;
struct fscrypt_policy policy = {};
#include "homework.h"
#include "user-record.h"
-int home_setup_fscrypt(UserRecord *h, const PasswordCache *cache, HomeSetup *setup);
+int home_setup_fscrypt(UserRecord *h, HomeSetup *setup, const PasswordCache *cache);
int home_create_fscrypt(UserRecord *h, HomeSetup *setup, char **effective_passwords, UserRecord **ret_home);
#include "strv.h"
#include "sync-util.h"
#include "tmpfile-util.h"
+#include "user-util.h"
/* Round down to the nearest 4K size. Given that newer hardware generally prefers 4K sectors, let's align our
* partitions to that too. In the worst case we'll waste 3.5K per partition that way, but I think I can live
return 0;
}
+static int make_dm_names(UserRecord *h, HomeSetup *setup) {
+ assert(h);
+ assert(h->user_name);
+ assert(setup);
+
+ if (!setup->dm_name) {
+ setup->dm_name = strjoin("home-", h->user_name);
+ if (!setup->dm_name)
+ return log_oom();
+ }
+
+ if (!setup->dm_node) {
+ setup->dm_node = path_join("/dev/mapper/", setup->dm_name);
+ if (!setup->dm_node)
+ return log_oom();
+ }
+
+ return 0;
+}
+
+static int acquire_open_luks_device(
+ UserRecord *h,
+ HomeSetup *setup,
+ bool graceful) {
+
+ _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
+ int r;
+
+ assert(h);
+ assert(setup);
+ assert(!setup->crypt_device);
+
+ r = dlopen_cryptsetup();
+ if (r < 0)
+ return r;
+
+ r = make_dm_names(h, setup);
+ if (r < 0)
+ return r;
+
+ r = sym_crypt_init_by_name(&cd, setup->dm_name);
+ if (IN_SET(r, -ENODEV, -EINVAL, -ENOENT) && graceful)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to initialize cryptsetup context for %s: %m", setup->dm_name);
+
+ cryptsetup_enable_logging(cd);
+
+ setup->crypt_device = TAKE_PTR(cd);
+ return 1;
+}
+
static int luks_open(
- const char *dm_name,
- char **passwords,
+ UserRecord *h,
+ HomeSetup *setup,
const PasswordCache *cache,
- struct crypt_device **ret,
sd_id128_t *ret_found_uuid,
void **ret_volume_key,
size_t *ret_volume_key_size) {
- _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
_cleanup_(erase_and_freep) void *vk = NULL;
sd_id128_t p;
char **list;
size_t vks;
int r;
- assert(dm_name);
- assert(ret);
+ assert(h);
+ assert(setup);
+ assert(!setup->crypt_device);
/* Opens a LUKS device that is already set up. Re-validates the password while doing so (which also
* provides us with the volume key, which we want). */
- r = sym_crypt_init_by_name(&cd, dm_name);
+ r = acquire_open_luks_device(h, setup, /* graceful= */ false);
if (r < 0)
- return log_error_errno(r, "Failed to initialize cryptsetup context for %s: %m", dm_name);
-
- cryptsetup_enable_logging(cd);
+ return r;
- r = sym_crypt_load(cd, CRYPT_LUKS2, NULL);
+ r = sym_crypt_load(setup->crypt_device, CRYPT_LUKS2, NULL);
if (r < 0)
return log_error_errno(r, "Failed to load LUKS superblock: %m");
- r = sym_crypt_get_volume_key_size(cd);
+ r = sym_crypt_get_volume_key_size(setup->crypt_device);
if (r <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size");
vks = (size_t) r;
if (ret_found_uuid) {
const char *s;
- s = sym_crypt_get_uuid(cd);
+ s = sym_crypt_get_uuid(setup->crypt_device);
if (!s)
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "LUKS superblock has no UUID.");
FOREACH_POINTER(list,
cache ? cache->pkcs11_passwords : NULL,
cache ? cache->fido2_passwords : NULL,
- passwords) {
- r = luks_try_passwords(cd, list, vk, &vks);
+ h->password) {
+ r = luks_try_passwords(setup->crypt_device, list, vk, &vks);
if (r != -ENOKEY)
break;
}
if (r < 0)
return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
- log_info("Discovered used LUKS device /dev/mapper/%s, and validated password.", dm_name);
+ log_info("Discovered used LUKS device /dev/mapper/%s, and validated password.", setup->dm_name);
/* This is needed so that crypt_resize() can operate correctly for pre-existing LUKS devices. We need
* to tell libcryptsetup the volume key explicitly, so that it is in the kernel keyring. */
- r = sym_crypt_activate_by_volume_key(cd, NULL, vk, vks, CRYPT_ACTIVATE_KEYRING_KEY);
+ r = sym_crypt_activate_by_volume_key(setup->crypt_device, NULL, vk, vks, CRYPT_ACTIVATE_KEYRING_KEY);
if (r < 0)
return log_error_errno(r, "Failed to upload volume key again: %m");
log_info("Successfully re-activated LUKS device.");
- *ret = TAKE_PTR(cd);
-
if (ret_found_uuid)
*ret_found_uuid = p;
if (ret_volume_key)
return 0;
}
-static int make_dm_names(const char *user_name, char **ret_dm_name, char **ret_dm_node) {
- _cleanup_free_ char *name = NULL, *node = NULL;
-
- assert(user_name);
- assert(ret_dm_name);
- assert(ret_dm_node);
-
- name = strjoin("home-", user_name);
- if (!name)
- return log_oom();
-
- node = path_join("/dev/mapper/", name);
- if (!node)
- return log_oom();
-
- *ret_dm_name = TAKE_PTR(name);
- *ret_dm_node = TAKE_PTR(node);
- return 0;
-}
-
static int luks_validate(
int fd,
const char *label,
UserRecord *h,
HomeSetupFlags flags,
const char *force_image_path,
- PasswordCache *cache,
HomeSetup *setup,
+ PasswordCache *cache,
UserRecord **ret_luks_home) {
sd_id128_t found_partition_uuid, found_luks_uuid, found_fs_uuid;
_cleanup_(user_record_unrefp) UserRecord *luks_home = NULL;
- _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
- _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
_cleanup_(erase_and_freep) void *volume_key = NULL;
- _cleanup_close_ int opened_image_fd = -1, root_fd = -1;
- bool dm_activated = false, mounted = false;
size_t volume_key_size = 0;
- bool marked_dirty = false;
uint64_t offset, size;
- int r, image_fd = -1;
+ int r;
assert(h);
assert(setup);
assert(setup->dm_name);
assert(setup->dm_node);
+ assert(setup->root_fd < 0);
+ assert(!setup->crypt_device);
+ assert(!setup->loop);
assert(user_record_storage(h) == USER_LUKS);
struct loop_info64 info;
const char *n;
- r = luks_open(setup->dm_name,
- h->password,
+ r = luks_open(h,
+ setup,
cache,
- &cd,
&found_luks_uuid,
&volume_key,
&volume_key_size);
if (r < 0)
return r;
- r = luks_validate_home_record(cd, h, volume_key, cache, &luks_home);
+ r = luks_validate_home_record(setup->crypt_device, h, volume_key, cache, &luks_home);
if (r < 0)
return r;
- n = sym_crypt_get_device_name(cd);
+ n = sym_crypt_get_device_name(setup->crypt_device);
if (!n)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine backing device for DM %s.", setup->dm_name);
- r = loop_device_open(n, O_RDWR, &loop);
+ r = loop_device_open(n, O_RDWR, &setup->loop);
if (r < 0)
return log_error_errno(r, "Failed to open loopback device %s: %m", n);
- if (ioctl(loop->fd, LOOP_GET_STATUS64, &info) < 0) {
+ if (ioctl(setup->loop->fd, LOOP_GET_STATUS64, &info) < 0) {
_cleanup_free_ char *sysfs = NULL;
struct stat st;
if (!IN_SET(errno, ENOTTY, EINVAL))
return log_error_errno(errno, "Failed to get block device metrics of %s: %m", n);
- if (ioctl(loop->fd, BLKGETSIZE64, &size) < 0)
+ if (ioctl(setup->loop->fd, BLKGETSIZE64, &size) < 0)
return log_error_errno(r, "Failed to read block device size of %s: %m", n);
- if (fstat(loop->fd, &st) < 0)
+ if (fstat(setup->loop->fd, &st) < 0)
return log_error_errno(r, "Failed to stat block device %s: %m", n);
assert(S_ISBLK(st.st_mode));
found_partition_uuid = found_fs_uuid = SD_ID128_NULL;
- log_info("Discovered used loopback device %s.", loop->node);
+ log_info("Discovered used loopback device %s.", setup->loop->node);
- root_fd = open(user_record_home_directory(h), O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
- if (root_fd < 0) {
- r = log_error_errno(errno, "Failed to open home directory: %m");
- goto fail;
- }
+ setup->root_fd = open(user_record_home_directory(h), O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+ if (setup->root_fd < 0)
+ return log_error_errno(errno, "Failed to open home directory: %m");
} else {
_cleanup_free_ char *fstype = NULL, *subdir = NULL;
const char *ip;
/* Reuse the image fd if it has already been opened by an earlier step */
if (setup->image_fd < 0) {
- opened_image_fd = open_image_file(h, force_image_path, &st);
- if (opened_image_fd < 0)
- return opened_image_fd;
-
- image_fd = opened_image_fd;
- } else
- image_fd = setup->image_fd;
+ setup->image_fd = open_image_file(h, force_image_path, &st);
+ if (setup->image_fd < 0)
+ return setup->image_fd;
+ }
- r = luks_validate(image_fd, user_record_user_name_and_realm(h), h->partition_uuid, &found_partition_uuid, &offset, &size);
+ r = luks_validate(setup->image_fd, user_record_user_name_and_realm(h), h->partition_uuid, &found_partition_uuid, &offset, &size);
if (r < 0)
return log_error_errno(r, "Failed to validate disk label: %m");
/* Everything before this point left the image untouched. We are now starting to make
* changes, hence mark the image dirty */
- marked_dirty = run_mark_dirty(image_fd, true) > 0;
+ if (run_mark_dirty(setup->image_fd, true) > 0)
+ setup->do_mark_clean = true;
if (!user_record_luks_discard(h)) {
- r = run_fallocate(image_fd, &st);
+ r = run_fallocate(setup->image_fd, &st);
if (r < 0)
return r;
}
- r = loop_device_make(image_fd, O_RDWR, offset, size, 0, &loop);
+ r = loop_device_make(setup->image_fd, O_RDWR, offset, size, 0, &setup->loop);
if (r == -ENOENT) {
log_error_errno(r, "Loopback block device support is not available on this system.");
return -ENOLINK; /* make recognizable */
if (r < 0)
return log_error_errno(r, "Failed to allocate loopback context: %m");
- log_info("Setting up loopback device %s completed.", loop->node ?: ip);
+ log_info("Setting up loopback device %s completed.", setup->loop->node ?: ip);
- r = luks_setup(loop->node ?: ip,
+ r = luks_setup(setup->loop->node ?: ip,
setup->dm_name,
h->luks_uuid,
h->luks_cipher,
h->password,
cache,
user_record_luks_discard(h) || user_record_luks_offline_discard(h),
- &cd,
+ &setup->crypt_device,
&found_luks_uuid,
&volume_key,
&volume_key_size);
if (r < 0)
return r;
- dm_activated = true;
+ setup->undo_dm = true;
- r = luks_validate_home_record(cd, h, volume_key, cache, &luks_home);
+ r = luks_validate_home_record(setup->crypt_device, h, volume_key, cache, &luks_home);
if (r < 0)
- goto fail;
+ return r;
r = fs_validate(setup->dm_node, h->file_system_uuid, &fstype, &found_fs_uuid);
if (r < 0)
- goto fail;
+ return r;
r = run_fsck(setup->dm_node, fstype);
if (r < 0)
- goto fail;
+ return r;
r = home_unshare_and_mount(setup->dm_node, fstype, user_record_luks_discard(h), user_record_mount_flags(h));
if (r < 0)
- goto fail;
+ return r;
- mounted = true;
+ setup->undo_mount = true;
- root_fd = open(subdir, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
- if (root_fd < 0) {
- r = log_error_errno(errno, "Failed to open home directory: %m");
- goto fail;
- }
+ setup->root_fd = open(subdir, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+ if (setup->root_fd < 0)
+ return log_error_errno(errno, "Failed to open home directory: %m");
if (user_record_luks_discard(h))
- (void) run_fitrim(root_fd);
-
- /* And now, fill in everything */
- if (opened_image_fd >= 0) {
- safe_close(setup->image_fd);
- setup->image_fd = TAKE_FD(opened_image_fd);
- }
+ (void) run_fitrim(setup->root_fd);
setup->do_offline_fallocate = !(setup->do_offline_fitrim = user_record_luks_offline_discard(h));
- setup->do_mark_clean = marked_dirty;
}
- setup->loop = TAKE_PTR(loop);
- setup->crypt_device = TAKE_PTR(cd);
- setup->root_fd = TAKE_FD(root_fd);
setup->found_partition_uuid = found_partition_uuid;
setup->found_luks_uuid = found_luks_uuid;
setup->found_fs_uuid = found_fs_uuid;
setup->volume_key = TAKE_PTR(volume_key);
setup->volume_key_size = volume_key_size;
- setup->undo_mount = mounted;
- setup->undo_dm = dm_activated;
-
if (ret_luks_home)
*ret_luks_home = TAKE_PTR(luks_home);
return 0;
-
-fail:
- if (mounted)
- (void) umount_verbose(LOG_ERR, HOME_RUNTIME_WORK_DIR, UMOUNT_NOFOLLOW);
-
- if (dm_activated)
- (void) sym_crypt_deactivate_by_name(cd, setup->dm_name, 0);
-
- if (image_fd >= 0 && marked_dirty)
- (void) run_mark_dirty(image_fd, false);
-
- return r;
}
static void print_size_summary(uint64_t host_size, uint64_t encrypted_size, struct statfs *sfs) {
h,
0,
NULL,
- cache,
setup,
+ cache,
&luks_home_record);
if (r < 0)
return r;
return 1;
}
-int home_deactivate_luks(UserRecord *h) {
- _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
- _cleanup_free_ char *dm_name = NULL, *dm_node = NULL;
+int home_deactivate_luks(UserRecord *h, HomeSetup *setup) {
bool we_detached;
int r;
+ assert(h);
+ assert(setup);
+ assert(!setup->crypt_device);
+
/* Note that the DM device and loopback device are set to auto-detach, hence strictly speaking we
* don't have to explicitly have to detach them. However, we do that nonetheless (in case of the DM
* device), to avoid races: by explicitly detaching them we know when the detaching is complete. We
* don't bother about the loopback device because unlike the DM device it doesn't have a fixed
* name. */
- r = dlopen_cryptsetup();
- if (r < 0)
- return r;
-
- r = make_dm_names(h->user_name, &dm_name, &dm_node);
+ r = acquire_open_luks_device(h, setup, /* graceful= */ true);
if (r < 0)
- return r;
-
- r = sym_crypt_init_by_name(&cd, dm_name);
- if (IN_SET(r, -ENODEV, -EINVAL, -ENOENT)) {
- log_debug_errno(r, "LUKS device %s has already been detached.", dm_name);
+ return log_error_errno(r, "Failed to initialize cryptsetup context for %s: %m", setup->dm_name);
+ if (r == 0) {
+ log_debug("LUKS device %s has already been detached.", setup->dm_name);
we_detached = false;
- } else if (r < 0)
- return log_error_errno(r, "Failed to initialize cryptsetup context for %s: %m", dm_name);
- else {
- log_info("Discovered used LUKS device %s.", dm_node);
+ } else {
+ log_info("Discovered used LUKS device %s.", setup->dm_node);
- cryptsetup_enable_logging(cd);
+ cryptsetup_enable_logging(setup->crypt_device);
- r = sym_crypt_deactivate_by_name(cd, dm_name, 0);
+ r = sym_crypt_deactivate_by_name(setup->crypt_device, setup->dm_name, 0);
if (IN_SET(r, -ENODEV, -EINVAL, -ENOENT)) {
- log_debug_errno(r, "LUKS device %s is already detached.", dm_node);
+ log_debug_errno(r, "LUKS device %s is already detached.", setup->dm_node);
we_detached = false;
} else if (r < 0)
- return log_info_errno(r, "LUKS device %s couldn't be deactivated: %m", dm_node);
+ return log_info_errno(r, "LUKS device %s couldn't be deactivated: %m", setup->dm_node);
else {
log_info("LUKS device detaching completed.");
we_detached = true;
int home_create_luks(
UserRecord *h,
+ HomeSetup *setup,
const PasswordCache *cache,
char **effective_passwords,
UserRecord **ret_home) {
- _cleanup_free_ char *dm_name = NULL, *dm_node = NULL, *subdir = NULL, *disk_uuid_path = NULL, *temporary_image_path = NULL;
+ _cleanup_free_ char *subdir = NULL, *disk_uuid_path = NULL;
uint64_t encrypted_size,
host_size = 0, partition_offset = 0, partition_size = 0; /* Unnecessary initialization to appease gcc */
- bool image_created = false, dm_activated = false, mounted = false;
_cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
sd_id128_t partition_uuid, fs_uuid, luks_uuid, disk_uuid;
- _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
- _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
- _cleanup_close_ int image_fd = -1, root_fd = -1;
+ _cleanup_close_ int mount_fd = -1;
const char *fstype, *ip;
struct statfs sfs;
int r;
assert(h);
assert(h->storage < 0 || h->storage == USER_LUKS);
+ assert(setup);
+ assert(!setup->temporary_image_path);
+ assert(setup->image_fd < 0);
assert(ret_home);
r = dlopen_cryptsetup();
} else
fs_uuid = h->file_system_uuid;
- r = make_dm_names(h->user_name, &dm_name, &dm_node);
+ r = make_dm_names(h, setup);
if (r < 0)
return r;
- r = access(dm_node, F_OK);
+ r = access(setup->dm_node, F_OK);
if (r < 0) {
if (errno != ENOENT)
- return log_error_errno(errno, "Failed to determine whether %s exists: %m", dm_node);
+ return log_error_errno(errno, "Failed to determine whether %s exists: %m", setup->dm_node);
} else
- return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Device mapper device %s already exists, refusing.", dm_node);
+ return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Device mapper device %s already exists, refusing.", setup->dm_node);
if (path_startswith(ip, "/dev/")) {
_cleanup_free_ char *sysfs = NULL;
/* Let's place the home directory on a real device, i.e. an USB stick or such */
- image_fd = open(ip, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
- if (image_fd < 0)
+ setup->image_fd = open(ip, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+ if (setup->image_fd < 0)
return log_error_errno(errno, "Failed to open device %s: %m", ip);
- if (fstat(image_fd, &st) < 0)
+ if (fstat(setup->image_fd, &st) < 0)
return log_error_errno(errno, "Failed to stat device %s: %m", ip);
if (!S_ISBLK(st.st_mode))
return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "Device is not a block device, refusing.");
} else
return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "Operating on partitions is currently not supported, sorry. Please specify a top-level block device.");
- if (flock(image_fd, LOCK_EX) < 0) /* make sure udev doesn't read from it while we operate on the device */
+ if (flock(setup->image_fd, LOCK_EX) < 0) /* make sure udev doesn't read from it while we operate on the device */
return log_error_errno(errno, "Failed to lock block device %s: %m", ip);
- if (ioctl(image_fd, BLKGETSIZE64, &block_device_size) < 0)
+ if (ioctl(setup->image_fd, BLKGETSIZE64, &block_device_size) < 0)
return log_error_errno(errno, "Failed to read block device size: %m");
if (h->disk_size == UINT64_MAX) {
if (user_record_luks_discard(h) || user_record_luks_offline_discard(h)) {
/* If we want online or offline discard, discard once before we start using things. */
- if (ioctl(image_fd, BLKDISCARD, (uint64_t[]) { 0, block_device_size }) < 0)
+ if (ioctl(setup->image_fd, BLKDISCARD, (uint64_t[]) { 0, block_device_size }) < 0)
log_full_errno(errno == EOPNOTSUPP ? LOG_DEBUG : LOG_WARNING, errno,
"Failed to issue full-device BLKDISCARD on device, ignoring: %m");
else
log_info("Full device discard completed.");
}
} else {
- _cleanup_free_ char *parent = NULL;
+ _cleanup_free_ char *parent = NULL, *t = NULL;
parent = dirname_malloc(ip);
if (!parent)
if (!supported_fs_size(fstype, host_size))
return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Selected file system size too small for %s.", fstype);
- r = tempfn_random(ip, "homework", &temporary_image_path);
+ r = tempfn_random(ip, "homework", &t);
if (r < 0)
return log_error_errno(r, "Failed to derive temporary file name for %s: %m", ip);
- image_fd = open(temporary_image_path, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
- if (image_fd < 0)
- return log_error_errno(errno, "Failed to create home image %s: %m", temporary_image_path);
+ setup->image_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
+ if (setup->image_fd < 0)
+ return log_error_errno(errno, "Failed to create home image %s: %m", t);
- image_created = true;
+ setup->temporary_image_path = TAKE_PTR(t);
- r = chattr_fd(image_fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
+ r = chattr_fd(setup->image_fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
if (r < 0)
log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r,
- "Failed to set file attributes on %s, ignoring: %m", temporary_image_path);
+ "Failed to set file attributes on %s, ignoring: %m", setup->temporary_image_path);
- r = home_truncate(h, image_fd, temporary_image_path, host_size);
+ r = home_truncate(h, setup->image_fd, setup->temporary_image_path, host_size);
if (r < 0)
- goto fail;
+ return r;
log_info("Allocating image file completed.");
}
r = make_partition_table(
- image_fd,
+ setup->image_fd,
user_record_user_name_and_realm(h),
partition_uuid,
&partition_offset,
&partition_size,
&disk_uuid);
if (r < 0)
- goto fail;
+ return r;
log_info("Writing of partition table completed.");
- r = loop_device_make(image_fd, O_RDWR, partition_offset, partition_size, 0, &loop);
+ r = loop_device_make(setup->image_fd, O_RDWR, partition_offset, partition_size, 0, &setup->loop);
if (r < 0) {
if (r == -ENOENT) { /* this means /dev/loop-control doesn't exist, i.e. we are in a container
* or similar and loopback bock devices are not available, return a
* recognizable error in this case. */
log_error_errno(r, "Loopback block device support is not available on this system.");
- r = -ENOLINK;
- goto fail;
+ return -ENOLINK; /* Make recognizable */
}
- log_error_errno(r, "Failed to set up loopback device for %s: %m", temporary_image_path);
- goto fail;
+ return log_error_errno(r, "Failed to set up loopback device for %s: %m", setup->temporary_image_path);
}
- r = loop_device_flock(loop, LOCK_EX); /* make sure udev won't read before we are done */
- if (r < 0) {
- log_error_errno(r, "Failed to take lock on loop device: %m");
- goto fail;
- }
+ r = loop_device_flock(setup->loop, LOCK_EX); /* make sure udev won't read before we are done */
+ if (r < 0)
+ return log_error_errno(r, "Failed to take lock on loop device: %m");
- log_info("Setting up loopback device %s completed.", loop->node ?: ip);
+ log_info("Setting up loopback device %s completed.", setup->loop->node ?: ip);
- r = luks_format(loop->node,
- dm_name,
+ r = luks_format(setup->loop->node,
+ setup->dm_name,
luks_uuid,
user_record_user_name_and_realm(h),
cache,
effective_passwords,
user_record_luks_discard(h) || user_record_luks_offline_discard(h),
h,
- &cd);
+ &setup->crypt_device);
if (r < 0)
- goto fail;
+ return r;
- dm_activated = true;
+ setup->undo_dm = true;
- r = block_get_size_by_path(dm_node, &encrypted_size);
- if (r < 0) {
- log_error_errno(r, "Failed to get encrypted block device size: %m");
- goto fail;
- }
+ r = block_get_size_by_path(setup->dm_node, &encrypted_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get encrypted block device size: %m");
- log_info("Setting up LUKS device %s completed.", dm_node);
+ log_info("Setting up LUKS device %s completed.", setup->dm_node);
- r = make_filesystem(dm_node, fstype, user_record_user_name_and_realm(h), fs_uuid, user_record_luks_discard(h));
+ r = make_filesystem(setup->dm_node, fstype, user_record_user_name_and_realm(h), fs_uuid, user_record_luks_discard(h));
if (r < 0)
- goto fail;
+ return r;
log_info("Formatting file system completed.");
- r = home_unshare_and_mount(dm_node, fstype, user_record_luks_discard(h), user_record_mount_flags(h));
+ r = home_unshare_and_mount(setup->dm_node, fstype, user_record_luks_discard(h), user_record_mount_flags(h));
if (r < 0)
- goto fail;
+ return r;
- mounted = true;
+ setup->undo_mount = true;
subdir = path_join(HOME_RUNTIME_WORK_DIR, user_record_user_name_and_realm(h));
- if (!subdir) {
- r = log_oom();
- goto fail;
- }
+ if (!subdir)
+ return log_oom();
/* Prefer using a btrfs subvolume if we can, fall back to directory otherwise */
r = btrfs_subvol_make_fallback(subdir, 0700);
- if (r < 0) {
- log_error_errno(r, "Failed to create user directory in mounted image file: %m");
- goto fail;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to create user directory in mounted image file: %m");
+
+ setup->root_fd = open(subdir, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+ if (setup->root_fd < 0)
+ return log_error_errno(errno, "Failed to open user directory in mounted image file: %m");
- root_fd = open(subdir, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
- if (root_fd < 0) {
- r = log_error_errno(errno, "Failed to open user directory in mounted image file: %m");
- goto fail;
+ (void) home_shift_uid(setup->root_fd, NULL, UID_NOBODY, h->uid, &mount_fd);
+
+ if (mount_fd >= 0) {
+ /* If we have established a new mount, then we can use that as new root fd to our home directory. */
+ safe_close(setup->root_fd);
+
+ setup->root_fd = fd_reopen(mount_fd, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+ if (setup->root_fd < 0)
+ return log_error_errno(setup->root_fd, "Unable to convert mount fd into proper directory fd: %m");
+
+ mount_fd = safe_close(mount_fd);
}
- r = home_populate(h, root_fd);
+ r = home_populate(h, setup->root_fd);
if (r < 0)
- goto fail;
+ return r;
- r = home_sync_and_statfs(root_fd, &sfs);
+ r = home_sync_and_statfs(setup->root_fd, &sfs);
if (r < 0)
- goto fail;
+ return r;
r = user_record_clone(h, USER_RECORD_LOAD_MASK_SECRET|USER_RECORD_LOG|USER_RECORD_PERMISSIVE, &new_home);
- if (r < 0) {
- log_error_errno(r, "Failed to clone record: %m");
- goto fail;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to clone record: %m");
r = user_record_add_binding(
new_home,
partition_uuid,
luks_uuid,
fs_uuid,
- sym_crypt_get_cipher(cd),
- sym_crypt_get_cipher_mode(cd),
- luks_volume_key_size_convert(cd),
+ sym_crypt_get_cipher(setup->crypt_device),
+ sym_crypt_get_cipher_mode(setup->crypt_device),
+ luks_volume_key_size_convert(setup->crypt_device),
fstype,
NULL,
h->uid,
(gid_t) h->uid);
- if (r < 0) {
- log_error_errno(r, "Failed to add binding to record: %m");
- goto fail;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to add binding to record: %m");
if (user_record_luks_offline_discard(h)) {
- r = run_fitrim(root_fd);
+ r = run_fitrim(setup->root_fd);
if (r < 0)
- goto fail;
+ return r;
}
- root_fd = safe_close(root_fd);
+ setup->root_fd = safe_close(setup->root_fd);
- r = umount_verbose(LOG_ERR, HOME_RUNTIME_WORK_DIR, UMOUNT_NOFOLLOW);
+ r = home_setup_undo_mount(setup, LOG_ERR);
if (r < 0)
- goto fail;
-
- mounted = false;
-
- r = sym_crypt_deactivate_by_name(cd, dm_name, 0);
- if (r < 0) {
- log_error_errno(r, "Failed to deactivate LUKS device: %m");
- goto fail;
- }
-
- sym_crypt_free(cd);
- cd = NULL;
+ return r;
- dm_activated = false;
+ r = home_setup_undo_dm(setup, LOG_ERR);
+ if (r < 0)
+ return r;
- loop = loop_device_unref(loop);
+ setup->loop = loop_device_unref(setup->loop);
if (!user_record_luks_offline_discard(h)) {
- r = run_fallocate(image_fd, NULL /* refresh stat() data */);
+ r= run_fallocate(setup->image_fd, NULL /* refresh stat() data */);
if (r < 0)
- goto fail;
+ return r;
}
/* Sync everything to disk before we move things into place under the final name. */
- if (fsync(image_fd) < 0) {
- r = log_error_errno(r, "Failed to synchronize image to disk: %m");
- goto fail;
- }
+ if (fsync(setup->image_fd) < 0)
+ return log_error_errno(r, "Failed to synchronize image to disk: %m");
if (disk_uuid_path)
- (void) ioctl(image_fd, BLKRRPART, 0);
+ (void) ioctl(setup->image_fd, BLKRRPART, 0);
else {
/* If we operate on a file, sync the containing directory too. */
- r = fsync_directory_of_file(image_fd);
- if (r < 0) {
- log_error_errno(r, "Failed to synchronize directory of image file to disk: %m");
- goto fail;
- }
+ r = fsync_directory_of_file(setup->image_fd);
+ if (r < 0)
+ return log_error_errno(r, "Failed to synchronize directory of image file to disk: %m");
}
/* Let's close the image fd now. If we are operating on a real block device this will release the BSD
* lock that ensures udev doesn't interfere with what we are doing */
- image_fd = safe_close(image_fd);
+ setup->image_fd = safe_close(setup->image_fd);
- if (temporary_image_path) {
- if (rename(temporary_image_path, ip) < 0) {
- log_error_errno(errno, "Failed to rename image file: %m");
- goto fail;
- }
+ if (setup->temporary_image_path) {
+ if (rename(setup->temporary_image_path, ip) < 0)
+ return log_error_errno(errno, "Failed to rename image file: %m");
+ setup->temporary_image_path = mfree(setup->temporary_image_path);
log_info("Moved image file into place.");
}
*ret_home = TAKE_PTR(new_home);
return 0;
-
-fail:
- /* Let's close all files before we unmount the file system, to avoid EBUSY */
- root_fd = safe_close(root_fd);
-
- if (mounted)
- (void) umount_verbose(LOG_WARNING, HOME_RUNTIME_WORK_DIR, UMOUNT_NOFOLLOW);
-
- if (dm_activated)
- (void) sym_crypt_deactivate_by_name(cd, dm_name, 0);
-
- loop = loop_device_unref(loop);
-
- if (image_created)
- (void) unlink(temporary_image_path);
-
- return r;
}
int home_get_state_luks(UserRecord *h, HomeSetup *setup) {
- _cleanup_free_ char *dm_name = NULL, *dm_node = NULL;
int r;
assert(h);
assert(setup);
- r = make_dm_names(h->user_name, &dm_name, &dm_node);
+ r = make_dm_names(h, setup);
if (r < 0)
return r;
- r = access(dm_node, F_OK);
+ r = access(setup->dm_node, F_OK);
if (r < 0 && errno != ENOENT)
- return log_error_errno(errno, "Failed to determine whether %s exists: %m", dm_node);
-
- free_and_replace(setup->dm_name, dm_name);
- free_and_replace(setup->dm_node, dm_node);
+ return log_error_errno(errno, "Failed to determine whether %s exists: %m", setup->dm_node);
return r >= 0;
}
}
if (setup->undo_mount) {
- r = umount_verbose(LOG_ERR, HOME_RUNTIME_WORK_DIR, UMOUNT_NOFOLLOW);
+ r = home_setup_undo_mount(setup, LOG_ERR);
if (r < 0)
return r;
- setup->undo_mount = false;
re_mount = true;
}
int home_resize_luks(
UserRecord *h,
HomeSetupFlags flags,
- PasswordCache *cache,
HomeSetup *setup,
+ PasswordCache *cache,
UserRecord **ret_home) {
uint64_t old_image_size, new_image_size, old_fs_size, new_fs_size, crypto_offset, new_partition_size;
new_image_size = new_image_size_rounded;
}
- r = home_setup_luks(h, flags, whole_disk, cache, setup, &header_home);
+ r = home_setup_luks(h, flags, whole_disk, setup, cache, &header_home);
if (r < 0)
return r;
return 1;
}
-int home_lock_luks(UserRecord *h) {
- _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
- _cleanup_free_ char *dm_name = NULL, *dm_node = NULL;
- _cleanup_close_ int root_fd = -1;
+int home_lock_luks(UserRecord *h, HomeSetup *setup) {
const char *p;
int r;
assert(h);
+ assert(setup);
+ assert(setup->root_fd < 0);
+ assert(!setup->crypt_device);
- assert_se(p = user_record_home_directory(h));
- root_fd = open(p, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
- if (root_fd < 0)
- return log_error_errno(errno, "Failed to open home directory: %m");
-
- r = make_dm_names(h->user_name, &dm_name, &dm_node);
- if (r < 0)
- return r;
-
- r = dlopen_cryptsetup();
+ r = acquire_open_luks_device(h, setup, /* graceful= */ false);
if (r < 0)
return r;
- r = sym_crypt_init_by_name(&cd, dm_name);
- if (r < 0)
- return log_error_errno(r, "Failed to initialize cryptsetup context for %s: %m", dm_name);
-
- log_info("Discovered used LUKS device %s.", dm_node);
- cryptsetup_enable_logging(cd);
-
- if (syncfs(root_fd) < 0) /* Snake oil, but let's better be safe than sorry */
- return log_error_errno(errno, "Failed to synchronize file system %s: %m", p);
+ log_info("Discovered used LUKS device %s.", setup->dm_node);
- root_fd = safe_close(root_fd);
+ assert_se(p = user_record_home_directory(h));
+ r = syncfs_path(AT_FDCWD, p);
+ if (r < 0) /* Snake oil, but let's better be safe than sorry */
+ return log_error_errno(r, "Failed to synchronize file system %s: %m", p);
log_info("File system synchronized.");
/* Note that we don't invoke FIFREEZE here, it appears libcryptsetup/device-mapper already does that on its own for us */
- r = sym_crypt_suspend(cd, dm_name);
+ r = sym_crypt_suspend(setup->crypt_device, setup->dm_name);
if (r < 0)
- return log_error_errno(r, "Failed to suspend cryptsetup device: %s: %m", dm_node);
+ return log_error_errno(r, "Failed to suspend cryptsetup device: %s: %m", setup->dm_node);
log_info("LUKS device suspended.");
return 0;
return -ENOKEY;
}
-int home_unlock_luks(UserRecord *h, const PasswordCache *cache) {
- _cleanup_free_ char *dm_name = NULL, *dm_node = NULL;
- _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL;
+int home_unlock_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache) {
char **list;
int r;
assert(h);
+ assert(setup);
+ assert(!setup->crypt_device);
- r = make_dm_names(h->user_name, &dm_name, &dm_node);
- if (r < 0)
- return r;
-
- r = dlopen_cryptsetup();
+ r = acquire_open_luks_device(h, setup, /* graceful= */ false);
if (r < 0)
return r;
- r = sym_crypt_init_by_name(&cd, dm_name);
- if (r < 0)
- return log_error_errno(r, "Failed to initialize cryptsetup context for %s: %m", dm_name);
-
- log_info("Discovered used LUKS device %s.", dm_node);
- cryptsetup_enable_logging(cd);
+ log_info("Discovered used LUKS device %s.", setup->dm_node);
r = -ENOKEY;
FOREACH_POINTER(list,
cache ? cache->pkcs11_passwords : NULL,
cache ? cache->fido2_passwords : NULL,
h->password) {
- r = luks_try_resume(cd, dm_name, list);
+ r = luks_try_resume(setup->crypt_device, setup->dm_name, list);
if (r != -ENOKEY)
break;
}
#include "homework.h"
#include "user-record.h"
-int home_setup_luks(UserRecord *h, HomeSetupFlags flags, const char *force_image_path, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_luks_home);
+int home_setup_luks(UserRecord *h, HomeSetupFlags flags, const char *force_image_path, HomeSetup *setup, PasswordCache *cache, UserRecord **ret_luks_home);
int home_activate_luks(UserRecord *h, HomeSetup *setup, PasswordCache *cache, UserRecord **ret_home);
-int home_deactivate_luks(UserRecord *h);
+int home_deactivate_luks(UserRecord *h, HomeSetup *setup);
int home_trim_luks(UserRecord *h);
int home_store_header_identity_luks(UserRecord *h, HomeSetup *setup, UserRecord *old_home);
-int home_create_luks(UserRecord *h, const PasswordCache *cache, char **effective_passwords, UserRecord **ret_home);
+int home_create_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache, char **effective_passwords, UserRecord **ret_home);
int home_get_state_luks(UserRecord *h, HomeSetup *setup);
-int home_resize_luks(UserRecord *h, HomeSetupFlags flags, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_home);
+int home_resize_luks(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup, PasswordCache *cache, UserRecord **ret_home);
int home_passwd_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache, char **effective_passwords);
-int home_lock_luks(UserRecord *h);
-int home_unlock_luks(UserRecord *h, const PasswordCache *cache);
+int home_lock_luks(UserRecord *h, HomeSetup *setup);
+int home_unlock_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache);
static inline uint64_t luks_volume_key_size_convert(struct crypt_device *cd) {
int k;
if (r < 0)
return r;
- return home_mount_node(node, fstype, discard, flags);
+ r = home_mount_node(node, fstype, discard, flags);
+ if (r < 0)
+ return r;
+
+ r = mount_nofollow_verbose(LOG_ERR, NULL, HOME_RUNTIME_WORK_DIR, NULL, MS_PRIVATE, NULL);
+ if (r < 0) {
+ (void) umount_verbose(LOG_ERR, HOME_RUNTIME_WORK_DIR, UMOUNT_NOFOLLOW);
+ return r;
+ }
+
+ return 0;
}
int home_move_mount(const char *mount_suffix, const char *target) {
if (r < 0)
return r;
- r = umount_verbose(LOG_ERR, HOME_RUNTIME_WORK_DIR, UMOUNT_NOFOLLOW);
+ r = umount_recursive(HOME_RUNTIME_WORK_DIR, 0);
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to unmount %s: %m", HOME_RUNTIME_WORK_DIR);
log_info("Moving to final mount point %s completed.", target);
return 0;
if (!setup->undo_mount)
return 0;
- r = umount_verbose(level, HOME_RUNTIME_WORK_DIR, UMOUNT_NOFOLLOW);
- if (r < 0)
- return r;
+ r = umount_recursive(HOME_RUNTIME_WORK_DIR, 0);
+ if (r < 0) {
+ if (level >= LOG_DEBUG) /* umount_recursive() does debug level logging anyway, no need to
+ * repeat that here */
+ return r;
+
+ /* If a higher log level is requested, the generate a non-debug mesage here too. */
+ return log_full_errno(level, r, "Failed to unmount mount tree below %s: %m", HOME_RUNTIME_WORK_DIR);
+ }
setup->undo_mount = false;
return 1;
}
+int home_setup_undo_dm(HomeSetup *setup, int level) {
+ int r, ret;
+
+ assert(setup);
+
+ if (setup->undo_dm) {
+ assert(setup->crypt_device);
+ assert(setup->dm_name);
+
+ r = sym_crypt_deactivate_by_name(setup->crypt_device, setup->dm_name, 0);
+ if (r < 0)
+ return log_full_errno(level, r, "Failed to deactivate LUKS device: %m");
+
+ setup->undo_dm = false;
+ ret = 1;
+ } else
+ ret = 0;
+
+ if (setup->crypt_device) {
+ sym_crypt_free(setup->crypt_device);
+ setup->crypt_device = NULL;
+ }
+
+ return ret;
+}
+
int home_setup_done(HomeSetup *setup) {
int r = 0, q;
if (q < 0)
r = q;
- if (setup->undo_dm && setup->crypt_device && setup->dm_name) {
- q = sym_crypt_deactivate_by_name(setup->crypt_device, setup->dm_name, 0);
- if (q < 0)
- r = q;
- }
+ q = home_setup_undo_dm(setup, LOG_DEBUG);
+ if (q < 0)
+ r = q;
if (setup->image_fd >= 0) {
if (setup->do_offline_fallocate) {
setup->image_fd = safe_close(setup->image_fd);
}
+ if (setup->temporary_image_path) {
+ if (unlink(setup->temporary_image_path) < 0)
+ log_debug_errno(errno, "Failed to remove temporary image file '%s', ignoring: %m",
+ setup->temporary_image_path);
+
+ setup->temporary_image_path = mfree(setup->temporary_image_path);
+ }
+
setup->undo_mount = false;
setup->undo_dm = false;
setup->do_offline_fitrim = false;
setup->dm_node = mfree(setup->dm_node);
setup->loop = loop_device_unref(setup->loop);
- if (setup->crypt_device) {
- sym_crypt_free(setup->crypt_device);
- setup->crypt_device = NULL;
- }
setup->volume_key = erase_and_free(setup->volume_key);
setup->volume_key_size = 0;
int home_setup(
UserRecord *h,
HomeSetupFlags flags,
- PasswordCache *cache,
HomeSetup *setup,
+ PasswordCache *cache,
UserRecord **ret_header_home) {
int r;
switch (user_record_storage(h)) {
case USER_LUKS:
- return home_setup_luks(h, flags, NULL, cache, setup, ret_header_home);
+ return home_setup_luks(h, flags, NULL, setup, cache, ret_header_home);
case USER_SUBVOLUME:
case USER_DIRECTORY:
break;
case USER_FSCRYPT:
- r = home_setup_fscrypt(h, cache, setup);
+ r = home_setup_fscrypt(h, setup, cache);
break;
case USER_CIFS:
}
static int home_deactivate(UserRecord *h, bool force) {
+ _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
bool done = false;
int r;
log_info("Directory %s is already unmounted.", user_record_home_directory(h));
if (user_record_storage(h) == USER_LUKS) {
- r = home_deactivate_luks(h);
+ r = home_deactivate_luks(h, &setup);
if (r < 0)
return r;
if (r > 0)
switch (user_record_storage(h)) {
case USER_LUKS:
- r = home_create_luks(h, &cache, effective_passwords, &new_home);
+ r = home_create_luks(h, &setup, &cache, effective_passwords, &new_home);
break;
case USER_DIRECTORY:
if (r < 0)
return r;
- r = home_setup(h, flags, &cache, &setup, &header_home);
+ r = home_setup(h, flags, &setup, &cache, &header_home);
if (r < 0)
return r;
switch (user_record_storage(h)) {
case USER_LUKS:
- return home_resize_luks(h, flags, &cache, &setup, ret);
+ return home_resize_luks(h, flags, &setup, &cache, ret);
case USER_DIRECTORY:
case USER_SUBVOLUME:
case USER_FSCRYPT:
- return home_resize_directory(h, flags, &cache, &setup, ret);
+ return home_resize_directory(h, flags, &setup, &cache, ret);
default:
return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Resizing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
if (r < 0)
return r;
- r = home_setup(h, flags, &cache, &setup, &header_home);
+ r = home_setup(h, flags, &setup, &cache, &header_home);
if (r < 0)
return r;
if (r < 0)
return r;
- r = home_setup(h, flags, &cache, &setup, &header_home);
+ r = home_setup(h, flags, &setup, &cache, &header_home);
if (r < 0)
return r;
}
static int home_lock(UserRecord *h) {
+ _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
int r;
assert(h);
if (r != USER_TEST_MOUNTED)
return log_error_errno(SYNTHETIC_ERRNO(ENOEXEC), "Home directory of %s is not mounted, can't lock.", h->user_name);
- r = home_lock_luks(h);
+ r = home_lock_luks(h, &setup);
if (r < 0)
return r;
}
static int home_unlock(UserRecord *h) {
+ _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
_cleanup_(password_cache_free) PasswordCache cache = {};
int r;
if (r < 0)
return r;
- r = home_unlock_luks(h, &cache);
+ r = home_unlock_luks(h, &setup, &cache);
if (r < 0)
return r;
log_setup();
+ cryptsetup_enable_logging(NULL);
+
umask(0022);
if (argc < 2 || argc > 3)
uint64_t partition_size;
char *mount_suffix; /* The directory to use as home dir is this path below /run/systemd/user-home-mount */
+
+ char *temporary_image_path;
} HomeSetup;
typedef struct PasswordCache {
int home_setup_done(HomeSetup *setup);
int home_setup_undo_mount(HomeSetup *setup, int level);
+int home_setup_undo_dm(HomeSetup *setup, int level);
-int home_setup(UserRecord *h, HomeSetupFlags flags, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_header_home);
+int home_setup(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup, PasswordCache *cache, UserRecord **ret_header_home);
int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, PasswordCache *cache, struct statfs *ret_statfs, UserRecord **ret_new_home);
return 0;
}
-static int parse_mount_bind_options(const char *options, unsigned long *mount_flags, char **mount_opts) {
+static int parse_mount_bind_options(const char *options, unsigned long *mount_flags, char **mount_opts, bool *idmapped) {
unsigned long flags = *mount_flags;
char *opts = NULL;
+ bool flag_idmapped = *idmapped;
int r;
assert(options);
flags |= MS_REC;
else if (streq(word, "norbind"))
flags &= ~MS_REC;
+ else if (streq(word, "idmap"))
+ flag_idmapped = true;
+ else if (streq(word, "noidmap"))
+ flag_idmapped = false;
else
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Invalid bind mount option: %s", word);
}
*mount_flags = flags;
+ *idmapped = flag_idmapped;
/* in the future mount_opts will hold string options for mount(2) */
*mount_opts = opts;
return 0;
}
-static int mount_bind(const char *dest, CustomMount *m) {
+static int mount_bind(const char *dest, CustomMount *m, uid_t uid_shift, uid_t uid_range) {
_cleanup_free_ char *mount_opts = NULL, *where = NULL;
unsigned long mount_flags = MS_BIND | MS_REC;
struct stat source_st, dest_st;
int r;
+ bool idmapped = false;
assert(dest);
assert(m);
if (m->options) {
- r = parse_mount_bind_options(m->options, &mount_flags, &mount_opts);
+ r = parse_mount_bind_options(m->options, &mount_flags, &mount_opts, &idmapped);
if (r < 0)
return r;
}
+ /* If this is a bind mount from a temporary sources change ownership of the source to the container's
+ * root UID. Otherwise it would always show up as "nobody" if user namespacing is used. */
+ if (m->rm_rf_tmpdir && chown(m->source, uid_shift, uid_shift) < 0)
+ return log_error_errno(errno, "Failed to chown %s: %m", m->source);
+
if (stat(m->source, &source_st) < 0)
return log_error_errno(errno, "Failed to stat %s: %m", m->source);
return log_error_errno(r, "Read-only bind mount failed: %m");
}
+ if (idmapped) {
+ r = remount_idmap(where, uid_shift, uid_range);
+ if (r < 0)
+ return log_error_errno(r, "Failed to map ids for bind mount %s: %m", where);
+ }
+
return 0;
}
const char *dest,
CustomMount *mounts, size_t n,
uid_t uid_shift,
+ uid_t uid_range,
const char *selinux_apifs_context,
MountSettingsMask mount_settings) {
int r;
switch (m->type) {
case CUSTOM_MOUNT_BIND:
- r = mount_bind(dest, m);
+ r = mount_bind(dest, m, uid_shift, uid_range);
break;
case CUSTOM_MOUNT_TMPFS:
int mount_all(const char *dest, MountSettingsMask mount_settings, uid_t uid_shift, const char *selinux_apifs_context);
int mount_sysfs(const char *dest, MountSettingsMask mount_settings);
-int mount_custom(const char *dest, CustomMount *mounts, size_t n, uid_t uid_shift, const char *selinux_apifs_context, MountSettingsMask mount_settings);
+int mount_custom(const char *dest, CustomMount *mounts, size_t n, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context, MountSettingsMask mount_settings);
bool has_custom_root_mount(const CustomMount *mounts, size_t n);
int setup_volatile_mode(const char *directory, VolatileMode mode, uid_t uid_shift, const char *selinux_apifs_context);
arg_custom_mounts,
arg_n_custom_mounts,
0,
+ 0,
arg_selinux_apifs_context,
MOUNT_NON_ROOT_ONLY | MOUNT_IN_USERNS);
if (r < 0)
directory = "/run/systemd/nspawn-root";
}
- if (arg_userns_mode != USER_NAMESPACE_NO &&
- IN_SET(arg_userns_ownership, USER_NAMESPACE_OWNERSHIP_MAP, USER_NAMESPACE_OWNERSHIP_AUTO) &&
- arg_uid_shift != 0) {
- r = make_mount_point(directory);
- if (r < 0)
- return r;
-
- r = remount_idmap(directory, arg_uid_shift, arg_uid_range);
- if (r == -EINVAL || ERRNO_IS_NOT_SUPPORTED(r)) {
- /* This might fail because the kernel or file system doesn't support idmapping. We
- * can't really distinguish this nicely, nor do we have any guarantees about the
- * error codes we see, could be EOPNOTSUPP or EINVAL. */
- if (arg_userns_ownership != USER_NAMESPACE_OWNERSHIP_AUTO)
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
- "ID mapped mounts are apparently not available, sorry.");
-
- log_debug("ID mapped mounts are apparently not available on this kernel or for the selected file system, reverting to recursive chown()ing.");
- arg_userns_ownership = USER_NAMESPACE_OWNERSHIP_CHOWN;
- } else if (r < 0)
- return log_error_errno(r, "Failed to set up ID mapped mounts: %m");
- else {
- log_debug("ID mapped mounts available, making use of them.");
- idmap = true;
- }
- }
-
r = setup_pivot_root(
directory,
arg_pivot_root_new,
arg_custom_mounts,
arg_n_custom_mounts,
arg_uid_shift,
+ arg_uid_range,
arg_selinux_apifs_context,
MOUNT_ROOT_ONLY);
if (r < 0)
if (r < 0)
return r;
+ if (arg_userns_mode != USER_NAMESPACE_NO &&
+ IN_SET(arg_userns_ownership, USER_NAMESPACE_OWNERSHIP_MAP, USER_NAMESPACE_OWNERSHIP_AUTO) &&
+ arg_uid_shift != 0) {
+
+ r = remount_idmap(directory, arg_uid_shift, arg_uid_range);
+ if (r == -EINVAL || ERRNO_IS_NOT_SUPPORTED(r)) {
+ /* This might fail because the kernel or file system doesn't support idmapping. We
+ * can't really distinguish this nicely, nor do we have any guarantees about the
+ * error codes we see, could be EOPNOTSUPP or EINVAL. */
+ if (arg_userns_ownership != USER_NAMESPACE_OWNERSHIP_AUTO)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "ID mapped mounts are apparently not available, sorry.");
+
+ log_debug("ID mapped mounts are apparently not available on this kernel or for the selected file system, reverting to recursive chown()ing.");
+ arg_userns_ownership = USER_NAMESPACE_OWNERSHIP_CHOWN;
+ } else if (r < 0)
+ return log_error_errno(r, "Failed to set up ID mapped mounts: %m");
+ else {
+ log_debug("ID mapped mounts available, making use of them.");
+ idmap = true;
+ }
+ }
+
if (dissected_image) {
/* Now we know the uid shift, let's now mount everything else that might be in the image. */
r = dissected_image_mount(
arg_custom_mounts,
arg_n_custom_mounts,
arg_uid_shift,
+ arg_uid_range,
arg_selinux_apifs_context,
MOUNT_NON_ROOT_ONLY);
if (r < 0)
if (r < 0)
return r;
+#if HAVE_LIBCRYPTSETUP
+ cryptsetup_enable_logging(NULL);
+#endif
+
if (arg_image) {
assert(!arg_root);
#include "libmount-util.h"
#include "locale-util.h"
#include "log.h"
+#include "macro.h"
#include "missing_fs.h"
#include "mountpoint-util.h"
#include "nsflags.h"
"ExecPaths",
"NoExecPaths",
"ExecSearchPath",
- "RuntimeDirectory",
- "StateDirectory",
- "CacheDirectory",
- "LogsDirectory",
"ConfigurationDirectory",
"SupplementaryGroups",
"SystemCallArchitectures"))
return 1;
}
+ if (STR_IN_SET(field, "StateDirectory", "RuntimeDirectory", "CacheDirectory", "LogsDirectory")) {
+ _cleanup_strv_free_ char **symlinks = NULL, **sources = NULL;
+ const char *p = eq;
+
+ /* 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. */
+
+ for (;;) {
+ _cleanup_free_ char *tuple = NULL, *source = NULL, *destination = NULL;
+
+ r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse argument: %m");
+ if (r == 0)
+ break;
+
+ const char *t = tuple;
+ r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination, NULL);
+ if (r <= 0)
+ return log_error_errno(r ?: SYNTHETIC_ERRNO(EINVAL), "Failed to parse argument: %m");
+
+ path_simplify(source);
+
+ if (isempty(destination)) {
+ r = strv_extend(&sources, TAKE_PTR(source));
+ if (r < 0)
+ return bus_log_create_error(r);
+ } else {
+ path_simplify(destination);
+
+ r = strv_consume_pair(&symlinks, TAKE_PTR(source), TAKE_PTR(destination));
+ if (r < 0)
+ return log_oom();
+ }
+ }
+
+ if (!strv_isempty(sources)) {
+ r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'v', "as");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append_strv(m, sources);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ 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)) {
+ const char *symlink_field;
+
+ 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);
+
+ r = sd_bus_message_open_container(m, 'v', "a(sst)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'a', "(sst)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ char **source, **destination;
+ STRV_FOREACH_PAIR(source, destination, symlinks) {
+ r = sd_bus_message_append(m, "(sst)", *source, *destination, 0);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
+ return 1;
+ }
+
return 0;
}
if (r < 0)
return bus_log_parse_error(r);
+ return 1;
+ } else if (STR_IN_SET(name, "StateDirectorySymlink", "RuntimeDirectorySymlink", "CacheDirectorySymlink", "LogsDirectorySymlink")) {
+ const char *a, *p;
+ uint64_t symlink_flags;
+
+ r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sst)");
+ 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);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
return 1;
}
}
+static void test_contains_instance_specifier_superset(void) {
+ assert_se(contains_instance_specifier_superset("foobar@a%i"));
+ assert_se(contains_instance_specifier_superset("foobar@%ia"));
+ assert_se(contains_instance_specifier_superset("foobar@%n"));
+ assert_se(contains_instance_specifier_superset("foobar@%n.service"));
+ assert_se(contains_instance_specifier_superset("foobar@%N"));
+ assert_se(contains_instance_specifier_superset("foobar@%N.service"));
+ assert_se(contains_instance_specifier_superset("foobar@baz.%N.service"));
+ assert_se(contains_instance_specifier_superset("@%N.service"));
+ assert_se(contains_instance_specifier_superset("@%N"));
+ assert_se(contains_instance_specifier_superset("@%a%N"));
+
+ assert_se(!contains_instance_specifier_superset("foobar@%i.service"));
+ assert_se(!contains_instance_specifier_superset("foobar%ia.service"));
+ assert_se(!contains_instance_specifier_superset("foobar@%%n.service"));
+ assert_se(!contains_instance_specifier_superset("foobar@baz.service"));
+ assert_se(!contains_instance_specifier_superset("%N.service"));
+ assert_se(!contains_instance_specifier_superset("%N"));
+ assert_se(!contains_instance_specifier_superset("@%aN"));
+ assert_se(!contains_instance_specifier_superset("@%a%b"));
+}
+
+static void test_unit_is_recursive_template_dependency(void) {
+ _cleanup_(manager_freep) Manager *m = NULL;
+ Unit *u;
+ int r;
+
+ r = manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_MINIMAL, &m);
+ if (manager_errno_skip_test(r)) {
+ log_notice_errno(r, "Skipping test: manager_new: %m");
+ return;
+ }
+
+ assert_se(r >= 0);
+ assert_se(manager_startup(m, NULL, NULL, NULL) >= 0);
+
+ assert_se(u = unit_new(m, sizeof(Service)));
+ assert_se(unit_add_name(u, "foobar@1.service") == 0);
+ u->fragment_path = strdup("/foobar@.service");
+
+ assert_se(hashmap_put_strdup(&m->unit_id_map, "foobar@foobar@123.service", "/foobar@.service"));
+ assert_se(hashmap_put_strdup(&m->unit_id_map, "foobar@foobar@456.service", "/custom.service"));
+
+ /* Test that %n, %N and any extension of %i specifiers in the instance are detected as recursive. */
+ assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%N.service") == 1);
+ assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%n.service") == 1);
+ assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@a%i.service") == 1);
+ assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%ia.service") == 1);
+ assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%x%n.service") == 1);
+ /* Test that %i on its own is not detected as recursive. */
+ assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%i.service") == 0);
+ /* Test that a specifier other than %i, %n and %N is not detected as recursive. */
+ assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@%xn.service") == 0);
+ /* Test that an expanded specifier is not detected as recursive. */
+ assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.service", "foobar@foobar@123.service") == 0);
+ /* Test that a dependency with a custom fragment path is not detected as recursive. */
+ assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@456.service", "foobar@%n.service") == 0);
+ /* Test that a dependency without a fragment path is not detected as recursive. */
+ assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@789.service", "foobar@%n.service") == 0);
+ /* Test that a dependency with a different prefix is not detected as recursive. */
+ assert_se(unit_is_likely_recursive_template_dependency(u, "quux@foobar@123.service", "quux@%n.service") == 0);
+ /* Test that a dependency of a different type is not detected as recursive. */
+ assert_se(unit_is_likely_recursive_template_dependency(u, "foobar@foobar@123.mount", "foobar@%n.mount") == 0);
+}
+
int main(int argc, char *argv[]) {
_cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
int r;
TEST_REQ_RUNNING_SYSTEMD(test_install_printf());
test_unit_dump_config_items();
test_config_parse_memory_limit();
+ test_contains_instance_specifier_superset();
+ test_unit_is_recursive_template_dependency();
return r;
}
NULL,
NULL,
NULL,
+ NULL,
NULL, 0,
NULL, 0,
NULL, 0,
(char **) writable,
(char **) readonly,
(char **) inaccessible,
+ NULL,
(char **) exec,
(char **) no_exec,
NULL,
SET_FOREACH(name, names)
log_info(" %s", name);
}
+
+ /* Make sure everything still works if we don't collect names. */
+ STRV_FOREACH(id, ids) {
+ const char *fragment;
+ log_info("*** %s ***", *id);
+ r = unit_file_find_fragment(unit_ids,
+ unit_names,
+ *id,
+ &fragment,
+ NULL);
+ assert_se(r == 0);
+ log_info("fragment: %s", fragment);
+ }
}
static void test_runlevel_to_target(void) {
test_unit_name_from_dbus_path_one("/org/freedesktop/systemd1/unit/wpa_5fsupplicant_2eservice", 0, "wpa_supplicant.service");
}
+static void test_unit_name_prefix_equal(void) {
+ log_info("/* %s */", __func__);
+
+ assert_se(unit_name_prefix_equal("a.service", "a.service"));
+ assert_se(unit_name_prefix_equal("a.service", "a.mount"));
+ assert_se(unit_name_prefix_equal("a@b.service", "a.service"));
+ assert_se(unit_name_prefix_equal("a@b.service", "a@c.service"));
+
+ assert_se(!unit_name_prefix_equal("a.service", "b.service"));
+ assert_se(!unit_name_prefix_equal("a.service", "b.mount"));
+ assert_se(!unit_name_prefix_equal("a@a.service", "b.service"));
+ assert_se(!unit_name_prefix_equal("a@a.service", "b@a.service"));
+ assert_se(!unit_name_prefix_equal("a", "b"));
+ assert_se(!unit_name_prefix_equal("a", "a"));
+}
+
int main(int argc, char* argv[]) {
_cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL;
int r, rc = 0;
test_unit_name_path_unescape();
test_unit_name_to_prefix();
test_unit_name_from_dbus_path();
+ test_unit_name_prefix_equal();
return rc;
}
systemd-analyze log-level debug
systemd-analyze log-target console
-# Set everything up without DynamicUser=1
+function test_directory() {
+ local directory="$1"
+ local path="$2"
-systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz touch /var/lib/zzz/test
-systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz test -f /var/lib/zzz/test
-systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz test -f /var/lib/zzz/test-missing \
- && { echo 'unexpected success'; exit 1; }
+ # Set everything up without DynamicUser=1
-test -d /var/lib/zzz
-test ! -L /var/lib/zzz
-test ! -e /var/lib/private/zzz
-test -f /var/lib/zzz/test
-test ! -f /var/lib/zzz/test-missing
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz touch "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz -p TemporaryFileSystem="${path}" test -f "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}" test -f "${path}"/xxx/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing \
+ && { echo 'unexpected success'; exit 1; }
-# Convert to DynamicUser=1
+ test -d "${path}"/zzz
+ test ! -L "${path}"/zzz
+ test ! -e "${path}"/private/zzz
+ test -f "${path}"/zzz/test
+ test ! -f "${path}"/zzz/test-missing
-systemd-run --wait -p DynamicUser=1 -p StateDirectory=zzz test -f /var/lib/zzz/test
-systemd-run --wait -p DynamicUser=1 -p StateDirectory=zzz test -f /var/lib/zzz/test-missing \
- && { echo 'unexpected success'; exit 1; }
+ # Convert to DynamicUser=1
-test -L /var/lib/zzz
-test -d /var/lib/private/zzz
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz -p TemporaryFileSystem="${path}" test -f "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}" test -f "${path}"/xxx/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=1 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing \
+ && { echo 'unexpected success'; exit 1; }
-test -f /var/lib/zzz/test
-test ! -f /var/lib/zzz/test-missing
+ test -L "${path}"/zzz
+ test -L "${path}"/yyy
+ test -d "${path}"/private/zzz
+ test ! -L "${path}"/private/xxx
+ test ! -L "${path}"/xxx
-# Convert back
+ test -f "${path}"/zzz/test
+ test ! -f "${path}"/zzz/test-missing
-systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz test -f /var/lib/zzz/test
-systemd-run --wait -p DynamicUser=0 -p StateDirectory=zzz test -f /var/lib/zzz/test-missing \
- && { echo 'unexpected success'; exit 1; }
+ # Convert back
-test -d /var/lib/zzz
-test ! -L /var/lib/zzz
-test ! -e /var/lib/private/zzz
-test -f /var/lib/zzz/test
-test ! -f /var/lib/zzz/test-missing
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz -p TemporaryFileSystem="${path}" test -f "${path}"/zzz/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:yyy test -f "${path}"/yyy/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}" test -f "${path}"/xxx/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}=zzz:xxx zzz:xxx2" -p TemporaryFileSystem="${path}" bash -c "test -f ${path}/xxx/test && test -f ${path}/xxx2/test"
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz:xxx -p TemporaryFileSystem="${path}":ro test -f "${path}"/xxx/test
+ systemd-run --wait -p RuntimeDirectoryPreserve=yes -p DynamicUser=0 -p "${directory}"=zzz test -f "${path}"/zzz/test-missing \
+ && { echo 'unexpected success'; exit 1; }
+
+ # Exercise the unit parsing paths too
+ cat >/run/systemd/system/testservice-34.service <<EOF
+[Service]
+Type=oneshot
+TemporaryFileSystem=${path}
+RuntimeDirectoryPreserve=yes
+${directory}=zzz:x\:yz zzz:x\:yz2
+ExecStart=test -f ${path}/x:yz2/test
+ExecStart=test -f ${path}/x:yz/test
+ExecStart=test -f ${path}/zzz/test
+EOF
+ systemctl daemon-reload
+ systemctl start --wait testservice-34.service
+
+ test -d "${path}"/zzz
+ test ! -L "${path}"/xxx
+ test ! -L "${path}"/xxx2
+ test ! -L "${path}"/private/xxx
+ test ! -L "${path}"/private/xxx2
+ test -L "${path}"/yyy
+ test ! -L "${path}"/zzz
+ test ! -e "${path}"/private/zzz
+ test -f "${path}"/zzz/test
+ test ! -f "${path}"/zzz/test-missing
+ test ! -L "${path}"/x:yz
+ test ! -L "${path}"/x:yz2
+}
+
+test_directory "StateDirectory" "/var/lib"
+test_directory "RuntimeDirectory" "/run"
+test_directory "CacheDirectory" "/var/cache"
+test_directory "LogsDirectory" "/var/log"
systemd-analyze log-level info