]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #21172 from poettering/fix-systemctl-cgroup-tree
authorLennart Poettering <lennart@poettering.net>
Fri, 29 Oct 2021 07:17:43 +0000 (09:17 +0200)
committerGitHub <noreply@github.com>
Fri, 29 Oct 2021 07:17:43 +0000 (09:17 +0200)
fix "sytemctl status" cgroup tree output

41 files changed:
TODO
man/org.freedesktop.systemd1.xml
man/systemd-nspawn.xml
man/systemd.exec.xml
man/systemd.unit.xml
src/basic/unit-file.c
src/basic/unit-name.c
src/basic/unit-name.h
src/core/cgroup.c
src/core/dbus-execute.c
src/core/execute.c
src/core/execute.h
src/core/load-fragment-gperf.gperf.in
src/core/load-fragment.c
src/core/load-fragment.h
src/core/namespace.c
src/core/namespace.h
src/core/scope.c
src/core/unit.c
src/home/homework-cifs.c
src/home/homework-directory.c
src/home/homework-directory.h
src/home/homework-fscrypt.c
src/home/homework-fscrypt.h
src/home/homework-luks.c
src/home/homework-luks.h
src/home/homework-mount.c
src/home/homework.c
src/home/homework.h
src/nspawn/nspawn-mount.c
src/nspawn/nspawn-mount.h
src/nspawn/nspawn.c
src/partition/repart.c
src/shared/bus-unit-util.c
src/systemctl/systemctl-show.c
src/test/test-load-fragment.c
src/test/test-namespace.c
src/test/test-ns.c
src/test/test-unit-file.c
src/test/test-unit-name.c
test/units/testsuite-34.sh

diff --git a/TODO b/TODO
index d19808ec2fe02678dcdfe8890dd787148d1fd315..9c8bdfdaa532aa2e1b751f26b6660950ab5ee004 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1279,8 +1279,6 @@ Features:
   - 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
index 0e1b5da333da713d63069d7b8d4dfad1e7a6c5f9..c9f71a6dca84638f3345b9e81260ef20ad0c23f9 100644 (file)
@@ -2805,20 +2805,28 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       @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 = ['...', ...];
@@ -3336,20 +3344,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <!--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!-->
@@ -3938,20 +3938,28 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
 
     <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"/>
@@ -4120,6 +4128,13 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
       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>
 
@@ -4651,20 +4666,28 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
       @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 = ['...', ...];
@@ -5208,20 +5231,12 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
 
     <!--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!-->
@@ -5806,20 +5821,28 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2esocket {
 
     <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"/>
@@ -6416,20 +6439,28 @@ node /org/freedesktop/systemd1/unit/home_2emount {
       @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 = ['...', ...];
@@ -6901,20 +6932,12 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <!--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!-->
@@ -7417,20 +7440,28 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <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"/>
@@ -8148,20 +8179,28 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
       @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 = ['...', ...];
@@ -8619,20 +8658,12 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
 
     <!--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!-->
@@ -9121,20 +9152,28 @@ node /org/freedesktop/systemd1/unit/dev_2dsda3_2eswap {
 
     <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"/>
index aec0b0e1299661560a5d17b5abd04ea17410a8b2..9c1cb33c0147291ac16a8ce70feee810676d484f 100644 (file)
@@ -1357,17 +1357,21 @@ After=sys-subsystem-net-devices-ens1.device</programlisting>
         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>
index 2b7129eb4065e2e0b260562854b4eb959a142ee9..83a23ca9a5db3480b6772051dda20162e0143f2a 100644 (file)
@@ -1327,6 +1327,13 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
         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>
@@ -1355,7 +1362,13 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
         <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>
index b6e4a2233c4552888f9ff4dba169999293c98513..dbf56c26a55b0087a317b8add5cfb021ec25df25 100644 (file)
@@ -2256,8 +2256,12 @@ OnFailure=failure-handler@%N.service
     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>
index bbe439da05ac047c16da0b402ab0afba15e302d4..89bfeceae4f52bed47891cbff23e00a35704ca9b 100644 (file)
@@ -598,9 +598,11 @@ int unit_file_find_fragment(
         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);
@@ -619,7 +621,7 @@ int unit_file_find_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)) {
@@ -631,7 +633,8 @@ int unit_file_find_fragment(
         }
 
         *ret_fragment_path = fragment;
-        *ret_names = TAKE_PTR(names);
+        if (ret_names)
+                *ret_names = TAKE_PTR(names);
 
         return 0;
 }
index 1deead74588b0a9ab4033931426fa30622f98b22..671e30a53fbc5f02e25c909516b54231cdaa6cb3 100644 (file)
@@ -8,6 +8,7 @@
 #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"
@@ -797,3 +798,26 @@ bool slice_name_is_valid(const char *name) {
 
         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;
+}
index f6af01c9bd8e8a3a1b4088d1c14a0c0efb685eea..b62b3e034e7cacfe0a79f3ccfafeff94b74a1700 100644 (file)
@@ -62,3 +62,5 @@ static inline int unit_name_mangle(const char *name, UnitNameMangle flags, char
 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);
index 3c4602d0962ff4f6c52de72d89e17396a3755cf9..abc30e3990c4827d9ad4aa13d12055d3a79b0adc 100644 (file)
@@ -2232,7 +2232,7 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) {
         CGroupMask delegated_mask;
         const char *p;
         void *pidp;
-        int r, q;
+        int ret, r;
 
         assert(u);
 
@@ -2259,16 +2259,16 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) {
 
         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));
 
@@ -2287,16 +2287,17 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) {
                                         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
@@ -2311,11 +2312,11 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) {
 
                         /* 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));
                         }
 
@@ -2326,14 +2327,14 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) {
                         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(
index 1c82c7d90db81cbb45251bb36596213eb3732dca..3645e680fe52ed84280e92a4ef79366fc1116a9a 100644 (file)
@@ -1091,6 +1091,70 @@ static int property_get_extension_images(
         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),
@@ -1224,17 +1288,21 @@ const sd_bus_vtable bus_exec_vtable[] = {
         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),
@@ -3294,14 +3362,17 @@ int bus_exec_context_set_transient_property(
                         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)
@@ -3711,6 +3782,79 @@ int bus_exec_context_set_transient_property(
                 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;
index 0c1e2e0563ba487024ee82dfca268d9c0ac5df69..d5882999f69938f6ab342e8b2a2bb78379f52088 100644 (file)
@@ -1943,26 +1943,29 @@ static int build_environment(
         }
 
         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)
@@ -2078,15 +2081,15 @@ bool exec_needs_mount_namespace(
                         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)
@@ -2268,12 +2271,43 @@ static bool exec_directory_is_private(const ExecContext *context, ExecDirectoryT
         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] = {
@@ -2283,7 +2317,6 @@ static int setup_exec_directory(
                 [EXEC_DIRECTORY_LOGS] = EXIT_LOGS_DIRECTORY,
                 [EXEC_DIRECTORY_CONFIGURATION] = EXIT_CONFIGURATION_DIRECTORY,
         };
-        char **rt;
         int r;
 
         assert(context);
@@ -2301,10 +2334,10 @@ static int setup_exec_directory(
                         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;
@@ -2351,7 +2384,7 @@ static int setup_exec_directory(
                         if (r < 0)
                                 goto fail;
 
-                        if (!path_extend(&pp, *rt)) {
+                        if (!path_extend(&pp, context->directories[type].items[i].path)) {
                                 r = -ENOMEM;
                                 goto fail;
                         }
@@ -2384,7 +2417,9 @@ static int setup_exec_directory(
                                         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;
@@ -2407,7 +2442,7 @@ static int setup_exec_directory(
                                 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;
@@ -2460,7 +2495,7 @@ static int setup_exec_directory(
                                         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;
@@ -2483,6 +2518,17 @@ static int setup_exec_directory(
                         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:
@@ -3032,7 +3078,7 @@ static int compile_bind_mounts(
                 if (!params->prefix[t])
                         continue;
 
-                n += strv_length(context->directories[t].paths);
+                n += context->directories[t].n_items;
         }
 
         if (n <= 0) {
@@ -3073,12 +3119,10 @@ static int compile_bind_mounts(
         }
 
         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) &&
@@ -3100,13 +3144,13 @@ static int compile_bind_mounts(
                                 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;
@@ -3117,7 +3161,7 @@ static int compile_bind_mounts(
                                 /* 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) {
@@ -3150,6 +3194,61 @@ finish:
         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,
@@ -3196,7 +3295,7 @@ static int apply_mount_namespace(
                 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;
@@ -3219,6 +3318,11 @@ static int apply_mount_namespace(
         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,
@@ -3302,6 +3406,7 @@ static int apply_mount_namespace(
                             needs_sandboxing ? context->exec_paths : NULL,
                             needs_sandboxing ? context->no_exec_paths : NULL,
                             empty_directories,
+                            symlinks,
                             bind_mounts,
                             n_bind_mounts,
                             context->temporary_filesystems,
@@ -3636,21 +3741,19 @@ static int compile_suggested_paths(const ExecContext *c, const ExecParameters *p
          * 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;
 
@@ -4170,8 +4273,10 @@ static int exec_child(
                 }
         }
 
+        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]);
         }
@@ -4358,7 +4463,6 @@ static int exec_child(
                         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;
 
@@ -5031,7 +5135,7 @@ void exec_context_done(ExecContext *c) {
         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;
 
@@ -5053,26 +5157,39 @@ void exec_context_done(ExecContext *c) {
 }
 
 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;
@@ -5484,8 +5601,12 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
         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));
@@ -5959,18 +6080,16 @@ int exec_context_get_clean_directories(
         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;
 
@@ -5980,7 +6099,18 @@ int exec_context_get_clean_directories(
 
                         /* 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;
 
@@ -6002,7 +6132,7 @@ int exec_context_get_clean_mask(ExecContext *c, ExecCleanMask *ret) {
         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;
@@ -6680,6 +6810,49 @@ ExecLoadCredential *exec_load_credential_free(ExecLoadCredential *lc) {
         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);
 
@@ -6740,6 +6913,17 @@ static const char* const exec_directory_type_table[_EXEC_DIRECTORY_TYPE_MAX] = {
 
 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. */
index 560dcbcc5eb57b6ee675852699be4bc2061d4afa..18a4316d01c924baf7f5c141f7fda0b5bc3fc385 100644 (file)
@@ -132,9 +132,15 @@ typedef enum ExecDirectoryType {
         _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 {
@@ -479,6 +485,9 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(ExecSetCredential*, exec_set_credential_free);
 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;
 
@@ -500,6 +509,9 @@ ExecKeyringMode exec_keyring_mode_from_string(const char *s) _pure_;
 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_;
 
index 480ec66bf9e59d372ebd58e74445ff0f5f0d3ca6..5ecba47e311044eb811a32b35f391ff4700c74ea 100644 (file)
 {{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)
index 18d9bb377c7a469494d8aa9623d60a72615b8da9..62cadaf2286ff2510cc4e60d8913e34f872839b0 100644 (file)
@@ -150,6 +150,84 @@ DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_numa_policy, mpol, int, -1, "
 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,
@@ -189,6 +267,18 @@ int config_parse_unit_deps(
                         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);
@@ -4511,7 +4601,7 @@ int config_parse_exec_directories(
                 void *data,
                 void *userdata) {
 
-        char***rt = data;
+        ExecDirectory *ed = data;
         const Unit *u = userdata;
         int r;
 
@@ -4522,45 +4612,84 @@ int config_parse_exec_directories(
 
         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;
         }
 }
 
index 96ccb426e6457a5baf0954f6d74c3f9204a00c99..9ff550410f67acc9e39b7e05e17364606f529131 100644 (file)
@@ -4,6 +4,10 @@
 #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);
index 48f11f99066fa2996fda9d6d846da1ba720669ee..68704dff063dba27013ae19b7ba5a577c0f4b635 100644 (file)
@@ -1586,11 +1586,36 @@ static void normalize_mounts(const char *root_directory, MountEntry *mounts, siz
         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;
@@ -1657,6 +1682,14 @@ static int apply_mounts(
                 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)
@@ -1820,6 +1853,7 @@ int setup_namespace(
                 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,
@@ -2261,7 +2295,7 @@ int setup_namespace(
                 (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;
 
index c9373a4adb1057ae63692d33242a1664f4e5a33f..62f05d7585fb9b508b16fc170a6467c73843a2d6 100644 (file)
@@ -121,6 +121,7 @@ int setup_namespace(
                 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,
index 74f16233c5894a3b7c2f3ed7a50e40ee01eba6f7..63d3288caf16fc8bb3960b8d94d9498f84a6b480 100644 (file)
@@ -391,6 +391,12 @@ static int scope_start(Unit *u) {
                 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;
 
index d8b4179239a244b8b99b1364ef190a15858ad209..4c55827a65119e8c01d97d7e97ec23304479b718 100644 (file)
@@ -1253,11 +1253,10 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) {
                 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;
 
@@ -1272,9 +1271,9 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) {
 
         /* 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;
index 6a4431c2299bb159d154148f1cc9d59a5ab26830..c76d6a6b13edcdb281993769badf3fcbb29d153a 100644 (file)
@@ -159,7 +159,7 @@ int home_activate_cifs(
         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;
 
index 3418034bd1ab222c520ed61a6589e349ef8e7841..af13fa026a9502a5a0b1ec5ca77e0a5ef1338512 100644 (file)
@@ -74,7 +74,7 @@ int home_activate_directory(
         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;
 
@@ -259,8 +259,8 @@ int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserReco
 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;
@@ -271,7 +271,7 @@ int home_resize_directory(
         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;
 
index 92cc755546c2754e6e4cc6ff437a663343e030fb..ecbb2f143cb807dbae50ee3f801aad27a339cfe6 100644 (file)
@@ -7,4 +7,4 @@
 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);
index 057bf0d45ed386c3bb8de709e3653837a77f34a2..f9fef73f7580af80021545739d89777b39c0a123 100644 (file)
@@ -282,8 +282,8 @@ static int fscrypt_setup(
 
 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 = {};
index e9262c6349a5ab0fbf04359b56190d5518f420b1..7c2d7aace416b6c3399cfe94e6fc051bf0d4b083 100644 (file)
@@ -4,7 +4,7 @@
 #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);
 
index e09d40f66b6ca8b88b356e1b76b2b001796e7575..d1208e4678784cd753ac37d03b5aa2610f7b54f7 100644 (file)
@@ -45,6 +45,7 @@
 #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
@@ -384,39 +385,88 @@ static int luks_setup(
         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;
@@ -424,7 +474,7 @@ static int luks_open(
         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.");
 
@@ -441,8 +491,8 @@ static int luks_open(
         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;
         }
@@ -451,18 +501,16 @@ static int luks_open(
         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)
@@ -509,26 +557,6 @@ static int fs_validate(
         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,
@@ -1141,26 +1169,24 @@ int home_setup_luks(
                 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);
 
@@ -1172,39 +1198,38 @@ int home_setup_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));
 
@@ -1246,13 +1271,11 @@ int home_setup_luks(
 
                 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;
@@ -1266,29 +1289,27 @@ int home_setup_luks(
 
                 /* 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 */
@@ -1296,9 +1317,9 @@ int home_setup_luks(
                 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,
@@ -1307,55 +1328,43 @@ int home_setup_luks(
                                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;
@@ -1364,25 +1373,10 @@ int home_setup_luks(
         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) {
@@ -1429,8 +1423,8 @@ int home_activate_luks(
                         h,
                         0,
                         NULL,
-                        cache,
                         setup,
+                        cache,
                         &luks_home_record);
         if (r < 0)
                 return r;
@@ -1485,43 +1479,37 @@ int home_activate_luks(
         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;
@@ -1992,25 +1980,26 @@ static int home_truncate(
 
 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();
@@ -2061,16 +2050,16 @@ int home_create_luks(
         } 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;
@@ -2079,11 +2068,11 @@ int home_create_luks(
 
                 /* 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.");
@@ -2096,10 +2085,10 @@ int home_create_luks(
                 } 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) {
@@ -2129,14 +2118,14 @@ int home_create_luks(
                 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)
@@ -2153,128 +2142,127 @@ int home_create_luks(
                 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,
@@ -2283,78 +2271,62 @@ int home_create_luks(
                         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.");
         }
 
@@ -2367,42 +2339,21 @@ int home_create_luks(
 
         *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;
 }
@@ -2475,11 +2426,10 @@ static int ext4_offline_resize_fs(HomeSetup *setup, uint64_t new_size, bool disc
         }
 
         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;
         }
 
@@ -2742,8 +2692,8 @@ static int apply_resize_partition(int fd, sd_id128_t disk_uuids, struct fdisk_ta
 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;
@@ -2842,7 +2792,7 @@ int home_resize_luks(
                 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;
 
@@ -3152,47 +3102,33 @@ int home_passwd_luks(
         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;
@@ -3227,35 +3163,26 @@ static int luks_try_resume(
         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;
         }
index f8d22bb647a0ae44bcf1266d92703b8f7b66b151..f8af66e083edb4da51034bfd6bffec44973cc7f6 100644 (file)
@@ -5,24 +5,24 @@
 #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;
index 5758e85839d7b713902eea556b6ff88010d179d1..82b461a9873616aed05e7ec71a7a78d6f54edbec 100644 (file)
@@ -84,7 +84,17 @@ int home_unshare_and_mount(const char *node, const char *fstype, bool discard, u
         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) {
@@ -111,9 +121,9 @@ 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;
index 6b60fddc2cf66dcf0deb1fdf35a36dd2766ca01b..c9b1734492c2a6fca0f1dd33516332507f7b99ba 100644 (file)
@@ -306,14 +306,46 @@ int home_setup_undo_mount(HomeSetup *setup, int level) {
         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;
 
@@ -336,11 +368,9 @@ int home_setup_done(HomeSetup *setup) {
         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) {
@@ -358,6 +388,14 @@ int home_setup_done(HomeSetup *setup) {
                 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;
@@ -368,10 +406,6 @@ int home_setup_done(HomeSetup *setup) {
         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;
@@ -387,8 +421,8 @@ int home_setup_done(HomeSetup *setup) {
 int home_setup(
                 UserRecord *h,
                 HomeSetupFlags flags,
-                PasswordCache *cache,
                 HomeSetup *setup,
+                PasswordCache *cache,
                 UserRecord **ret_header_home) {
 
         int r;
@@ -409,7 +443,7 @@ int home_setup(
         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:
@@ -417,7 +451,7 @@ int home_setup(
                 break;
 
         case USER_FSCRYPT:
-                r = home_setup_fscrypt(h, cache, setup);
+                r = home_setup_fscrypt(h, setup, cache);
                 break;
 
         case USER_CIFS:
@@ -883,6 +917,7 @@ static int home_activate(UserRecord *h, UserRecord **ret_home) {
 }
 
 static int home_deactivate(UserRecord *h, bool force) {
+        _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
         bool done = false;
         int r;
 
@@ -919,7 +954,7 @@ static int home_deactivate(UserRecord *h, bool force) {
                 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)
@@ -1279,7 +1314,7 @@ static int home_create(UserRecord *h, UserRecord **ret_home) {
         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:
@@ -1500,7 +1535,7 @@ static int home_update(UserRecord *h, UserRecord **ret) {
         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;
 
@@ -1558,12 +1593,12 @@ static int home_resize(UserRecord *h, UserRecord **ret) {
         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)));
@@ -1592,7 +1627,7 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) {
         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;
 
@@ -1662,7 +1697,7 @@ static int home_inspect(UserRecord *h, UserRecord **ret_home) {
         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;
 
@@ -1685,6 +1720,7 @@ static int home_inspect(UserRecord *h, UserRecord **ret_home) {
 }
 
 static int home_lock(UserRecord *h) {
+        _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
         int r;
 
         assert(h);
@@ -1700,7 +1736,7 @@ static int home_lock(UserRecord *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;
 
@@ -1709,6 +1745,7 @@ static int home_lock(UserRecord *h) {
 }
 
 static int home_unlock(UserRecord *h) {
+        _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT;
         _cleanup_(password_cache_free) PasswordCache cache = {};
         int r;
 
@@ -1726,7 +1763,7 @@ static int home_unlock(UserRecord *h) {
         if (r < 0)
                 return r;
 
-        r = home_unlock_luks(h, &cache);
+        r = home_unlock_luks(h, &setup, &cache);
         if (r < 0)
                 return r;
 
@@ -1748,6 +1785,8 @@ static int run(int argc, char *argv[]) {
 
         log_setup();
 
+        cryptsetup_enable_logging(NULL);
+
         umask(0022);
 
         if (argc < 2 || argc > 3)
index 1b56fbbd8f1e91912d8a9dfdd853c738d2c3d892..d7ad6fbdfaa88c81d0f723c24d45c371c8d12ed6 100644 (file)
@@ -39,6 +39,8 @@ typedef struct HomeSetup {
         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 {
@@ -76,8 +78,9 @@ typedef enum HomeSetupFlags {
 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);
 
index 035fe287eecb16f72511cb55a7a6e7ed16a08bbb..2bfff79cde573b4a505ad84c4273f4c81fadfbf5 100644 (file)
@@ -672,9 +672,10 @@ int mount_all(const char *dest,
         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);
@@ -692,33 +693,44 @@ static int parse_mount_bind_options(const char *options, unsigned long *mount_fl
                         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);
 
@@ -767,6 +779,12 @@ static int mount_bind(const char *dest, CustomMount *m) {
                         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;
 }
 
@@ -906,6 +924,7 @@ 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) {
         int r;
@@ -927,7 +946,7 @@ int mount_custom(
                 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:
index e19b2cc4e108b087cdbbce4e672141d08f3e3251..6bedbf9b3faec6d2615cc635d681c31ef2f16a92 100644 (file)
@@ -58,7 +58,7 @@ int inaccessible_mount_parse(CustomMount **l, size_t *n, const char *s);
 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);
index ea68dc248aa2c72c24ac9dc6d7d4bccf1966dbec..8c0bc99d727205d7a80ff3e6be59e4ccf76b40af 100644 (file)
@@ -3314,6 +3314,7 @@ static int inner_child(
                         arg_custom_mounts,
                         arg_n_custom_mounts,
                         0,
+                        0,
                         arg_selinux_apifs_context,
                         MOUNT_NON_ROOT_ONLY | MOUNT_IN_USERNS);
         if (r < 0)
@@ -3719,32 +3720,6 @@ static int outer_child(
                 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,
@@ -3795,6 +3770,7 @@ static int outer_child(
                         arg_custom_mounts,
                         arg_n_custom_mounts,
                         arg_uid_shift,
+                        arg_uid_range,
                         arg_selinux_apifs_context,
                         MOUNT_ROOT_ONLY);
         if (r < 0)
@@ -3805,6 +3781,29 @@ static int outer_child(
         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(
@@ -3915,6 +3914,7 @@ static int outer_child(
                         arg_custom_mounts,
                         arg_n_custom_mounts,
                         arg_uid_shift,
+                        arg_uid_range,
                         arg_selinux_apifs_context,
                         MOUNT_NON_ROOT_ONLY);
         if (r < 0)
index f50a459c624af824f1023093a595cc844cd0b5f3..62cc8ff7ae04627c32f83cb02298d8484d866c5f 100644 (file)
@@ -4827,6 +4827,10 @@ static int run(int argc, char *argv[]) {
         if (r < 0)
                 return r;
 
+#if HAVE_LIBCRYPTSETUP
+        cryptsetup_enable_logging(NULL);
+#endif
+
         if (arg_image) {
                 assert(!arg_root);
 
index 7df1e0b3108b0be36d9ea306bdeb2bb5dd2dcc7f..dbd33420be3f19a94f84b9a7ef8061632ae97c91 100644 (file)
@@ -23,6 +23,7 @@
 #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"
@@ -970,10 +971,6 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                               "ExecPaths",
                               "NoExecPaths",
                               "ExecSearchPath",
-                              "RuntimeDirectory",
-                              "StateDirectory",
-                              "CacheDirectory",
-                              "LogsDirectory",
                               "ConfigurationDirectory",
                               "SupplementaryGroups",
                               "SystemCallArchitectures"))
@@ -1926,6 +1923,125 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                 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;
 }
 
index 86803c5fc7bff0bde09e36cea63bc82a491a7534..6166260d106167f545bcf4790a2274df5bb61b4c 100644 (file)
@@ -1770,6 +1770,24 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
                         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;
                 }
 
index e0ba99199c9dca5b78e9df93067de97baeb23d72..124832cdc98a2dd5716410da0667419d46cb3ab1 100644 (file)
@@ -829,6 +829,71 @@ static void test_config_parse_memory_limit(void) {
 
 }
 
+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;
@@ -850,6 +915,8 @@ int main(int argc, char *argv[]) {
         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;
 }
index 796382f3cf0713f2ebe3b7363aa552132aa47174..dd66379b5c9d9f085c04d1511d6984365d3f51e7 100644 (file)
@@ -166,6 +166,7 @@ static void test_protect_kernel_logs(void) {
                                     NULL,
                                     NULL,
                                     NULL,
+                                    NULL,
                                     NULL, 0,
                                     NULL, 0,
                                     NULL, 0,
index ae666a3019a9aedd4dff7336719257b62211b75a..b03eabb59bbc60adb9beafd26e39ca0c1ec8e485 100644 (file)
@@ -83,6 +83,7 @@ int main(int argc, char *argv[]) {
                             (char **) writable,
                             (char **) readonly,
                             (char **) inaccessible,
+                            NULL,
                             (char **) exec,
                             (char **) no_exec,
                             NULL,
index 4fcd2e0efd0fbd7b0f0519d6717cddffc493af21..7900c1f460e76e84e7d6100ad3ebb14e529ab3bd 100644 (file)
@@ -71,6 +71,19 @@ static void test_unit_file_build_name_map(char **ids) {
                  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) {
index 0077c4c5e3bb9c39a2e9d5664f40b3da081b6931..665e7b7933625e0a0b0f3fe3abf30b938ce74286 100644 (file)
@@ -870,6 +870,22 @@ static void test_unit_name_from_dbus_path(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;
@@ -902,6 +918,7 @@ int main(int argc, char* argv[]) {
         test_unit_name_path_unescape();
         test_unit_name_to_prefix();
         test_unit_name_from_dbus_path();
+        test_unit_name_prefix_equal();
 
         return rc;
 }
index 97244ac4571f2edd535f479e87cc05ea9ab394ae..57a7b950a01cfff1a6c53bbad0886663a19c6ea6 100755 (executable)
@@ -6,42 +6,89 @@ set -o pipefail
 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