]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
portable: add --copy=mixed to copy images and link profiles
authorLuca Boccassi <bluca@debian.org>
Wed, 7 Feb 2024 00:36:39 +0000 (00:36 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Thu, 8 Feb 2024 21:11:26 +0000 (21:11 +0000)
This new mode copies resources provided by the client, so that they
remain available for inspect/detach even if the original images are
deleted, but symlinks the profile as that is owned by the OS, so that
updates are automatically applied.

man/org.freedesktop.portable1.xml
man/portablectl.xml
src/portable/portable.c
src/portable/portable.h
src/portable/portablectl.c
src/portable/portabled-image-bus.c
test/units/testsuite-29.sh

index a41da4f5c3cd20a7da3f5c6da1b0e0776f2b11de..9b49c610d55c6223dfe29aa79cad2bf6282fbf5a 100644 (file)
@@ -229,16 +229,23 @@ node /org/freedesktop/portable1 {
       for the current boot session, and a string representing the preferred copy mode
       (whether to copy the image or to just symlink it) with the following possible values:
       <itemizedlist>
-        <listitem><para>(null)</para></listitem>
+        <listitem><para>(empty)</para></listitem>
 
         <listitem><para>copy</para></listitem>
 
         <listitem><para>symlink</para></listitem>
+
+        <listitem><para>mixed</para></listitem>
       </itemizedlist>
-      This method returns the list of changes applied to the system (for example, which unit was
-      added and is now available as a system service). Each change is represented as a triplet of
-      strings: the type of change applied, the path on which it was applied, and the source
-      (if any). The type of change applied will be one of the following possible values:
+      If an empty string is passed the security profile drop-ins and images will be symlinked while unit
+      files will be copied, <varname>copy</varname> will copy, <varname>symlink</varname> will prefer
+      linking if possible (e.g.: a unit has to be copied out of an image), and <varname>mixed</varname> will
+      prefer linking the resources owned by the OS (e.g.: the portable profile located within the host's
+      /usr/ tree) but will copy the resources owned by the portable image (e.g.: the unit files and the
+      images). This method returns the list of changes applied to the system (for example, which unit was
+      added and is now available as a system service). Each change is represented as a triplet of strings:
+      the type of change applied, the path on which it was applied, and the source (if any). The type of
+      change applied will be one of the following possible values:
       <itemizedlist>
         <listitem><para>copy</para></listitem>
 
index d0d00cf5d221ea509e752b894b73a3f7b9427351..d241893d4de3ce779105d4a7929bac645519728d 100644 (file)
       <varlistentry>
         <term><option>--copy=</option></term>
 
-        <listitem><para>When attaching an image, select whether to prefer copying or symlinking of files installed into
-        the host system. Takes one of <literal>copy</literal> (to prefer copying of files), <literal>symlink</literal>
-        (to prefer creation of symbolic links) or <literal>auto</literal> for an intermediary mode where security
-        profile drop-ins are symlinked while unit files are copied. Note that this option expresses a preference only,
-        in cases where symbolic links cannot be created — for example when the image operated on is a raw disk image,
-        and hence not directly referentiable from the host file system — copying of files is used
+        <listitem><para>When attaching an image, select whether to prefer copying or symlinking of files
+        installed into the host system. Takes one of <literal>copy</literal> (files will be copied),
+        <literal>symlink</literal> (to prefer creation of symbolic links), <literal>auto</literal> for an
+        intermediary mode where security profile drop-ins and images are symlinked while unit files are
+        copied, or <literal>mixed</literal> (since v256) where security profile drop-ins are symlinked while
+        unit files and images are copied. Note that this option expresses a preference only, in cases where
+        symbolic links cannot be created — for example when the image operated on is a raw disk image, and
+        hence not directly referentiable from the host file system — copying of files is used
         unconditionally.</para>
 
         <xi:include href="version-info.xml" xpointer="v239"/></listitem>
index 6054f0f17f8de20f2e8c09795309166169e1c0a7..27c18b117f2cbc963f35ebddc9b784194d420e06 100644 (file)
@@ -33,6 +33,7 @@
 #include "path-lookup.h"
 #include "portable.h"
 #include "process-util.h"
+#include "rm-rf.h"
 #include "selinux-util.h"
 #include "set.h"
 #include "signal-util.h"
@@ -1341,7 +1342,7 @@ static int attach_unit_file(
         return 0;
 }
 
-static int image_symlink(
+static int image_target_path(
                 const char *image_path,
                 PortableFlags flags,
                 char **ret) {
@@ -1367,37 +1368,66 @@ static int image_symlink(
         return 0;
 }
 
-static int install_image_symlink(
+static int install_image(
                 const char *image_path,
                 PortableFlags flags,
                 PortableChange **changes,
                 size_t *n_changes) {
 
-        _cleanup_free_ char *sl = NULL;
+        _cleanup_free_ char *target = NULL;
         int r;
 
         assert(image_path);
 
-        /* If the image is outside of the image search also link it into it, so that it can be found with short image
-         * names and is listed among the images. */
+        /* If the image is outside of the image search also link it into it, so that it can be found with
+         * short image names and is listed among the images. If we are operating in mixed mode, the image is
+         * copied instead. */
 
         if (image_in_search_path(IMAGE_PORTABLE, NULL, image_path))
                 return 0;
 
-        r = image_symlink(image_path, flags, &sl);
+        r = image_target_path(image_path, flags, &target);
         if (r < 0)
                 return log_debug_errno(r, "Failed to generate image symlink path: %m");
 
-        (void) mkdir_parents(sl, 0755);
+        (void) mkdir_parents(target, 0755);
 
-        if (symlink(image_path, sl) < 0)
-                return log_debug_errno(errno, "Failed to link %s %s %s: %m", image_path, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), sl);
+        if (flags & PORTABLE_MIXED_COPY_LINK) {
+                r = copy_tree(image_path,
+                              target,
+                              UID_INVALID,
+                              GID_INVALID,
+                              COPY_REFLINK | COPY_FSYNC | COPY_FSYNC_FULL | COPY_SYNCFS,
+                              /* denylist= */ NULL,
+                              /* subvolumes= */ NULL);
+                if (r < 0)
+                        return log_debug_errno(
+                                        r,
+                                        "Failed to copy %s %s %s: %m",
+                                        image_path,
+                                        special_glyph(SPECIAL_GLYPH_ARROW_RIGHT),
+                                        target);
+
+        } else {
+                if (symlink(image_path, target) < 0)
+                        return log_debug_errno(
+                                        errno,
+                                        "Failed to link %s %s %s: %m",
+                                        image_path,
+                                        special_glyph(SPECIAL_GLYPH_ARROW_RIGHT),
+                                        target);
+        }
 
-        (void) portable_changes_add(changes, n_changes, PORTABLE_SYMLINK, sl, image_path);
+        (void) portable_changes_add(
+                        changes,
+                        n_changes,
+                        (flags & PORTABLE_MIXED_COPY_LINK) ? PORTABLE_COPY : PORTABLE_SYMLINK,
+                        target,
+                        image_path);
         return 0;
 }
 
-static int install_image_and_extensions_symlinks(
+static int install_image_and_extensions(
                 const Image *image,
                 OrderedHashmap *extension_images,
                 PortableFlags flags,
@@ -1410,12 +1440,12 @@ static int install_image_and_extensions_symlinks(
         assert(image);
 
         ORDERED_HASHMAP_FOREACH(ext, extension_images) {
-                r = install_image_symlink(ext->path, flags, changes, n_changes);
+                r = install_image(ext->path, flags, changes, n_changes);
                 if (r < 0)
                         return r;
         }
 
-        r = install_image_symlink(image->path, flags, changes, n_changes);
+        r = install_image(image->path, flags, changes, n_changes);
         if (r < 0)
                 return r;
 
@@ -1608,9 +1638,9 @@ int portable_attach(
                         return sd_bus_error_set_errnof(error, r, "Failed to attach unit '%s': %m", item->name);
         }
 
-        /* We don't care too much for the image symlink, it's just a convenience thing, it's not necessary for proper
-         * operation otherwise. */
-        (void) install_image_and_extensions_symlinks(image, extension_images, flags, changes, n_changes);
+        /* We don't care too much for the image symlink/copy, it's just a convenience thing, it's not necessary for
+         * proper operation otherwise. */
+        (void) install_image_and_extensions(image, extension_images, flags, changes, n_changes);
 
         log_portable_verb(
                         "attached",
@@ -1909,34 +1939,24 @@ int portable_detach(
                         portable_changes_add_with_prefix(changes, n_changes, PORTABLE_UNLINK, where, md, NULL);
         }
 
-        /* Now, also drop any image symlink, for images outside of the sarch path */
+        /* Now, also drop any image symlink or copy, for images outside of the sarch path */
         SET_FOREACH(item, markers) {
-                _cleanup_free_ char *sl = NULL;
-                struct stat st;
+                _cleanup_free_ char *target = NULL;
 
-                r = image_symlink(item, flags, &sl);
+                r = image_target_path(item, flags, &target);
                 if (r < 0) {
-                        log_debug_errno(r, "Failed to determine image symlink for '%s', ignoring: %m", item);
-                        continue;
-                }
-
-                if (lstat(sl, &st) < 0) {
-                        log_debug_errno(errno, "Failed to stat '%s', ignoring: %m", sl);
+                        log_debug_errno(r, "Failed to determine image path for '%s', ignoring: %m", item);
                         continue;
                 }
 
-                if (!S_ISLNK(st.st_mode)) {
-                        log_debug("Image '%s' is not a symlink, ignoring.", sl);
-                        continue;
-                }
-
-                if (unlink(sl) < 0) {
-                        log_debug_errno(errno, "Can't remove image symlink '%s': %m", sl);
+                r = rm_rf(target, REMOVE_ROOT | REMOVE_PHYSICAL | REMOVE_MISSING_OK | REMOVE_SYNCFS);
+                if (r < 0) {
+                        log_debug_errno(r, "Can't remove image '%s': %m", target);
 
-                        if (errno != ENOENT && ret >= 0)
-                                ret = -errno;
+                        if (r != -ENOENT)
+                                RET_GATHER(ret, r);
                 } else
-                        portable_changes_add(changes, n_changes, PORTABLE_UNLINK, sl, NULL);
+                        portable_changes_add(changes, n_changes, PORTABLE_UNLINK, target, NULL);
         }
 
         /* Try to remove the unit file directory, if we can */
index c4a9d5103e6355d2ed3f73989b611aff2ba9a7ff..f5bb1902bd3f9a98b44ed608d8297b8025ed46a0 100644 (file)
@@ -27,7 +27,8 @@ typedef enum PortableFlags {
         PORTABLE_FORCE_EXTENSION = 1 << 2, /* Public API via DBUS, do not change */
         PORTABLE_PREFER_COPY     = 1 << 3,
         PORTABLE_PREFER_SYMLINK  = 1 << 4,
-        PORTABLE_REATTACH        = 1 << 5,
+        PORTABLE_MIXED_COPY_LINK = 1 << 5,
+        PORTABLE_REATTACH        = 1 << 6,
         _PORTABLE_MASK_PUBLIC    = PORTABLE_RUNTIME | PORTABLE_FORCE_ATTACH | PORTABLE_FORCE_EXTENSION,
         _PORTABLE_TYPE_MAX,
         _PORTABLE_TYPE_INVALID   = -EINVAL,
index cf3122e29c56bede8c3df1d68bc71edfc79ed273..1867fc8a1d03bcc21403e2d578f4f67f10c0c049 100644 (file)
@@ -1372,12 +1372,13 @@ static int parse_argv(int argc, char *argv[]) {
                 case ARG_COPY:
                         if (streq(optarg, "auto"))
                                 arg_copy_mode = NULL;
-                        else if (STR_IN_SET(optarg, "copy", "symlink"))
+                        else if (STR_IN_SET(optarg, "copy", "symlink", "mixed"))
                                 arg_copy_mode = optarg;
                         else if (streq(optarg, "help")) {
                                 puts("auto\n"
                                      "copy\n"
-                                     "symlink");
+                                     "symlink\n"
+                                     "mixed\n");
                                 return 0;
                         } else
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
index 63f177eb74b28f58685a6c7103c75e0e81a0f616..8db55742425aee5402a096a98f072e745ea1c9a6 100644 (file)
@@ -366,6 +366,8 @@ int bus_image_common_attach(
                 flags |= PORTABLE_PREFER_SYMLINK;
         else if (streq(copy_mode, "copy"))
                 flags |= PORTABLE_PREFER_COPY;
+        else if (streq(copy_mode, "mixed"))
+                flags |= PORTABLE_MIXED_COPY_LINK;
         else if (!isempty(copy_mode))
                 return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode);
 
@@ -695,6 +697,8 @@ int bus_image_common_reattach(
                 flags |= PORTABLE_PREFER_SYMLINK;
         else if (streq(copy_mode, "copy"))
                 flags |= PORTABLE_PREFER_COPY;
+        else if (streq(copy_mode, "mixed"))
+                flags |= PORTABLE_MIXED_COPY_LINK;
         else if (!isempty(copy_mode))
                 return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode);
 
index 536827311b92c662d0f30b88199b83f64104fe4b..d40cef2551a2407396fb02a22e33bcb617a3558d 100755 (executable)
@@ -203,6 +203,14 @@ portablectl inspect --force --cat --extension /usr/share/app0.raw --extension /u
 
 portablectl detach --now --runtime --extension /usr/share/app0.raw --extension /usr/share/conf0.raw /usr/share/minimal_0.raw app0
 
+# Ensure that mixed mode copies the images and units (client-owned) but symlinks the profile (OS owned)
+portablectl "${ARGS[@]}" attach --copy=mixed --runtime --extension /usr/share/app0.raw /usr/share/minimal_0.raw app0
+test -f /run/portables/app0.raw
+test -f /run/portables/minimal_0.raw
+test -f /run/systemd/system.attached/app0.service
+test -L /run/systemd/system.attached/app0.service.d/10-profile.conf
+portablectl detach --runtime --extension /usr/share/app0.raw /usr/share/minimal_0.raw app0
+
 # portablectl also works with directory paths rather than images
 
 mkdir /tmp/rootdir /tmp/app0 /tmp/app1 /tmp/overlay /tmp/os-release-fix /tmp/os-release-fix/etc
@@ -255,6 +263,17 @@ grep -q -F "LogExtraFields=PORTABLE_EXTENSION_NAME_AND_VERSION=app_1" /run/syste
 
 portablectl detach --now --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1
 
+# Ensure that mixed mode copies the images and units (client-owned) but symlinks the profile (OS owned)
+portablectl "${ARGS[@]}" attach --copy=mixed --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1
+test -d /run/portables/app0
+test -d /run/portables/app1
+test -d /run/portables/rootdir
+test -f /run/systemd/system.attached/app0.service
+test -f /run/systemd/system.attached/app1.service
+test -L /run/systemd/system.attached/app0.service.d/10-profile.conf
+test -L /run/systemd/system.attached/app1.service.d/10-profile.conf
+portablectl detach --runtime --extension /tmp/app0 --extension /tmp/app1 /tmp/rootdir app0 app1
+
 # Attempt to disable the app unit during detaching. Requires --copy=symlink to reproduce.
 # Provides coverage for https://github.com/systemd/systemd/issues/23481
 portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/rootdir minimal-app0