mirror of
https://github.com/systemd/systemd.git
synced 2025-01-09 01:18:19 +03:00
portable: add --copy=mixed to copy images and link profiles
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.
This commit is contained in:
parent
eb47031694
commit
82047a6aa7
@ -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>
|
||||
|
||||
|
@ -321,12 +321,14 @@
|
||||
<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>
|
||||
|
@ -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);
|
||||
|
||||
(void) portable_changes_add(changes, n_changes, PORTABLE_SYMLINK, sl, image_path);
|
||||
} 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,
|
||||
(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);
|
||||
log_debug_errno(r, "Failed to determine image path for '%s', ignoring: %m", item);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lstat(sl, &st) < 0) {
|
||||
log_debug_errno(errno, "Failed to stat '%s', ignoring: %m", sl);
|
||||
continue;
|
||||
}
|
||||
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 (!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);
|
||||
|
||||
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 */
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user