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>
<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>
#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"
return 0;
}
-static int image_symlink(
+static int image_target_path(
const char *image_path,
PortableFlags flags,
char **ret) {
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,
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;
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",
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 */
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
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