From 907952bbc92dd6656807d9b2eb0d0c94a4c9e865 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 23 Jun 2020 13:09:42 +0100 Subject: [PATCH] portabled: add --extension parameter for layered images support Add an --extension parameter to portablectl, and new DBUS methods to attach/detach/reattach/inspect. Allows to append separate images on top of the root directory (os-release will be searched in there) and mount the images using an overlay-like setup (unit files will be searched in there) using the new ExtensionImages service option. --- man/org.freedesktop.portable1.xml | 119 +++++++++++ man/portablectl.xml | 13 ++ src/portable/portable.c | 323 ++++++++++++++++++++++++----- src/portable/portable.h | 16 +- src/portable/portablectl.c | 181 ++++++++++++---- src/portable/portabled-bus.c | 74 ++++++- src/portable/portabled-image-bus.c | 198 +++++++++++++++--- test/TEST-29-PORTABLE/test.sh | 1 + test/units/testsuite-29.sh | 30 +++ 9 files changed, 823 insertions(+), 132 deletions(-) diff --git a/man/org.freedesktop.portable1.xml b/man/org.freedesktop.portable1.xml index e6d2f4f536d..46dca55745c 100644 --- a/man/org.freedesktop.portable1.xml +++ b/man/org.freedesktop.portable1.xml @@ -48,6 +48,13 @@ node /org/freedesktop/portable1 { out s image, out ay os_release, out a{say} units); + GetImageMetadataWithExtensions(in s image, + in as extensions, + in as matches, + in t flags, + out s image, + out ay os_release, + out a{say} units); GetImageState(in s image, out s state); AttachImage(in s image, @@ -56,9 +63,20 @@ node /org/freedesktop/portable1 { in b runtime, in s copy_mode, out a(sss) changes); + AttachImageWithExtensions(in s image, + in as extensions, + in as matches, + in s profile, + in s copy_mode, + in t flags, + out a(sss) changes); DetachImage(in s image, in b runtime, out a(sss) changes); + DetachImageWithExtensions(in s image, + in as extensions, + in t flags, + out a(sss) changes); ReattachImage(in s image, in as matches, in s profile, @@ -66,6 +84,14 @@ node /org/freedesktop/portable1 { in s copy_mode, out a(sss) changes_removed, out a(sss) changes_updated); + ReattachImageWithExtensions(in s image, + in as extensions, + in as matches, + in s profile, + in s copy_mode, + in t flags, + out a(sss) changes_removed, + out a(sss) changes_updated); RemoveImage(in s image); MarkImageReadOnly(in s image, in b read_only); @@ -102,14 +128,22 @@ node /org/freedesktop/portable1 { + + + + + + + + @@ -149,6 +183,12 @@ node /org/freedesktop/portable1 { and a list of portable units contained in the image, in the form of a string (unit name) and an array of bytes with the content. + GetImageMetadataWithExtensions() retrieves metadata associated with an image. + This method is a superset of GetImageMetadata() with the addition of + a list of extensions as input parameter, which were overlayed on top of the main + image via AttachImageWithExtensions(). + The flag parameter is currently unused and reserved for future purposes. + GetImageState() retrieves the image state as one of the following strings: @@ -197,6 +237,16 @@ node /org/freedesktop/portable1 { Note that an image cannot be attached if a unit that it contains is already present on the system. + AttachImageWithExtensions() attaches a portable image to the system. + This method is a superset of AttachImage() with the addition of + a list of extensions as input parameter, which will be overlayed on top of the main + image. When this method is used, detaching must be done by passing the same arguments via the + DetachImageWithExtensions() method. For more details on this functionality, + see the MountImages= entry on + systemd.exec5 + and systemd-sysext8. + The flag parameter is currently unused and reserved for future purposes. + DetachImage() detaches a portable image from the system. This method takes an image path or name, and a boolean indicating whether the image to detach was attached only for the current boot session or persistently. This method @@ -209,6 +259,12 @@ node /org/freedesktop/portable1 { Note that an image cannot be detached if a unit that it contains is running. + DetachImageWithExtensions() detaches a portable image from the system. + This method is a superset of DetachImage() with the addition of + a list of extensions as input parameter, which were overlayed on top of the main + image via AttachImageWithExtensions(). + The flag parameter is currently unused and reserved for future purposes. + ReattachImage() combines the effects of the AttachImage() method and the DetachImage() method. The difference is that it is allowed to reattach an image while one or more of its units @@ -218,6 +274,14 @@ node /org/freedesktop/portable1 { DetachImage() method (first array, units that were removed) and the AttachImage() method (second array, units that were updated or added). + ReattachImageWithExtensions() reattaches a portable image to the system. + This method is a superset of ReattachImage() with the addition of + a list of extensions as input parameter, which will be overlayed on top of the main + image. For more details on this functionality, see the MountImages= entry on + systemd.exec5 + and systemd-sysext8. + The flag parameter is currently unused and reserved for future purposes + RemoveImage() removes the image with the specified name. MarkImageReadOnly() toggles the read-only flag of an image. @@ -225,6 +289,15 @@ node /org/freedesktop/portable1 { SetPoolLimit() sets an overall quota limit on the pool of images. SetImageLimit() sets a per-image quota limit. + + The AttachImageWithExtensions(), + DetachImageWithExtensions() and + ReattachImageWithExtensions() methods take in options as flags instead of + booleans to allow for extendability, defined as follows: + + +#define SD_SYSTEMD_PORTABLE_RUNTIME (UINT64_C(1) << 0) + @@ -254,20 +327,42 @@ node /org/freedesktop/portable1 { out s image, out ay os_release, out a{say} units); + GetMetadataWithExtensions(in as extensions, + in as matches, + in t flags, + out s image, + out ay os_release, + out a{say} units); GetState(out s UNNAMED); Attach(in as matches, in s profile, in b runtime, in s copy_mode, out a(sss) changes); + AttachWithExtensions(in as extensions, + in as matches, + in s profile, + in s copy_mode, + in t flags, + out a(sss) changes); Detach(in b runtime, out a(sss) changes); + DetachWithExtensions(in as extensions, + in t flags, + out a(sss) changes); Reattach(in as matches, in s profile, in b runtime, in s copy_mode, out a(sss) changes_removed, out a(sss) changes_updated); + ReattacheWithExtensions(in as extensions, + in as matches, + in s profile, + in s copy_mode, + in t flags, + out a(sss) changes_removed, + out a(sss) changes_updated); Remove(); MarkReadOnly(in b read_only); SetLimit(in t limit); @@ -303,14 +398,22 @@ node /org/freedesktop/portable1 { + + + + + + + + @@ -327,14 +430,22 @@ node /org/freedesktop/portable1 { + + + + + + + + @@ -377,14 +488,22 @@ node /org/freedesktop/portable1 { GetMetadata() + GetMetadataWithExtensions() + GetState() Attach() + AttachWithExtensions() + Detach() + DetachWithExtensions() + Reattach() + ReattacheWithExtensions() + Remove() MarkReadOnly() diff --git a/man/portablectl.xml b/man/portablectl.xml index 2dae537a40a..d798219d459 100644 --- a/man/portablectl.xml +++ b/man/portablectl.xml @@ -352,6 +352,19 @@ Don't block waiting for attach --now to complete. + + PATH + + Add an additional image PATH as an overlay on + top of IMAGE when attaching/detaching. This argument can be specified + multiple times, in which case the order in which images are laid down follows the rules specified in + systemd.exec5 + for the ExtensionImages= directive. + + Note that the same extensions have to be specified, in the same order, when attaching + and detaching. + + diff --git a/src/portable/portable.c b/src/portable/portable.c index 5651db67227..02d1d641950 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -11,6 +11,7 @@ #include "discover-image.h" #include "dissect-image.h" #include "errno-list.h" +#include "escape.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" @@ -74,17 +75,26 @@ static bool unit_match(const char *unit, char **matches) { return false; } -static PortableMetadata *portable_metadata_new(const char *name, int fd) { +static PortableMetadata *portable_metadata_new(const char *name, const char *path, int fd) { PortableMetadata *m; m = malloc0(offsetof(PortableMetadata, name) + strlen(name) + 1); if (!m) return NULL; + /* In case of a layered attach, we want to remember which image the unit came from */ + if (path) { + m->image_path = strdup(path); + if (!m->image_path) { + free(m); + return NULL; + } + } + strcpy(m->name, name); m->fd = fd; - return m; + return TAKE_PTR(m); } PortableMetadata *portable_metadata_unref(PortableMetadata *i) { @@ -93,6 +103,7 @@ PortableMetadata *portable_metadata_unref(PortableMetadata *i) { safe_close(i->fd); free(i->source); + free(i->image_path); return mfree(i); } @@ -255,7 +266,7 @@ static int extract_now( } if (ret_os_release) { - os_release = portable_metadata_new("/etc/os-release", os_release_fd); + os_release = portable_metadata_new("/etc/os-release", NULL, os_release_fd); if (!os_release) return -ENOMEM; @@ -316,7 +327,7 @@ static int extract_now( return log_debug_errno(r, "Failed to send unit metadata to parent: %m"); } - m = portable_metadata_new(de->d_name, fd); + m = portable_metadata_new(de->d_name, NULL, fd); if (!m) return -ENOMEM; fd = -1; @@ -342,6 +353,7 @@ static int extract_now( static int portable_extract_by_path( const char *path, + bool extract_os_release, char **matches, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, @@ -412,7 +424,7 @@ static int portable_extract_by_path( if (r == 0) { seq[0] = safe_close(seq[0]); - r = dissected_image_mount(m, tmpdir, UID_INVALID, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_VALIDATE_OS); + r = dissected_image_mount(m, tmpdir, UID_INVALID, DISSECT_IMAGE_READ_ONLY); if (r < 0) { log_debug_errno(r, "Failed to mount dissected image: %m"); goto child_finish; @@ -448,7 +460,7 @@ static int portable_extract_by_path( return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid item sent from child."); - add = portable_metadata_new(name, fd); + add = portable_metadata_new(name, path, fd); if (!add) return -ENOMEM; fd = -1; @@ -478,10 +490,12 @@ static int portable_extract_by_path( child = 0; } - if (!os_release) + /* When the portable image is layered, the image with units will not + * have a full filesystem, so no os-release - it will be in the root layer */ + if (extract_os_release && !os_release) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image '%s' lacks os-release data, refusing.", path); - if (hashmap_isempty(unit_files)) + if (!extract_os_release && hashmap_isempty(unit_files)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Couldn't find any matching unit files in image '%s', refusing.", path); if (ret_unit_files) @@ -496,11 +510,16 @@ static int portable_extract_by_path( int portable_extract( const char *name_or_path, char **matches, + char **extension_image_paths, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, sd_bus_error *error) { + _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL; + _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL; + _cleanup_hashmap_free_ Hashmap *unit_files = NULL; _cleanup_(image_unrefp) Image *image = NULL; + Image *ext; int r; assert(name_or_path); @@ -509,7 +528,46 @@ int portable_extract( if (r < 0) return r; - return portable_extract_by_path(image->path, matches, ret_os_release, ret_unit_files, error); + if (!strv_isempty(extension_image_paths)) { + char **p; + + extension_images = ordered_hashmap_new(&image_hash_ops); + if (!extension_images) + return -ENOMEM; + + STRV_FOREACH(p, extension_image_paths) { + _cleanup_(image_unrefp) Image *new = NULL; + + r = image_find_harder(IMAGE_PORTABLE, *p, NULL, &new); + if (r < 0) + return r; + + r = ordered_hashmap_put(extension_images, new->name, new); + if (r < 0) + return r; + TAKE_PTR(new); + } + } + + r = portable_extract_by_path(image->path, true, matches, &os_release, &unit_files, error); + if (r < 0) + return r; + + ORDERED_HASHMAP_FOREACH(ext, extension_images) { + _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL; + + r = portable_extract_by_path(ext->path, false, matches, NULL, &extra_unit_files, error); + if (r < 0) + return r; + r = hashmap_move(unit_files, extra_unit_files); + if (r < 0) + return r; + } + + *ret_os_release = TAKE_PTR(os_release); + *ret_unit_files = TAKE_PTR(unit_files); + + return 0; } static int unit_file_is_active( @@ -684,9 +742,49 @@ void portable_changes_free(PortableChange *changes, size_t n_changes) { free(changes); } +static const char *root_setting_from_image(ImageType type) { + return IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) ? "RootDirectory=" : "RootImage="; +} + +static int make_marker_text(const char *image_path, OrderedHashmap *extension_images, char **ret_text) { + _cleanup_free_ char *text = NULL, *escaped_image_path = NULL; + Image *ext; + + assert(image_path); + assert(ret_text); + + escaped_image_path = xescape(image_path, ":"); + if (!escaped_image_path) + return -ENOMEM; + + /* If the image is layered, include all layers in the marker as a colon-separated + * list of paths, so that we can do exact matches on removal. */ + text = strjoin(PORTABLE_DROPIN_MARKER_BEGIN, escaped_image_path); + if (!text) + return -ENOMEM; + + ORDERED_HASHMAP_FOREACH(ext, extension_images) { + _cleanup_free_ char *escaped = NULL; + + escaped = xescape(ext->path, ":"); + if (!escaped) + return -ENOMEM; + + if (!strextend(&text, ":", escaped)) + return -ENOMEM; + } + + if (!strextend(&text, PORTABLE_DROPIN_MARKER_END "\n")) + return -ENOMEM; + + *ret_text = TAKE_PTR(text); + return 0; +} + static int install_chroot_dropin( const char *image_path, ImageType type, + OrderedHashmap *extension_images, const PortableMetadata *m, const char *dropin_dir, char **ret_dropin, @@ -694,6 +792,7 @@ static int install_chroot_dropin( size_t *n_changes) { _cleanup_free_ char *text = NULL, *dropin = NULL; + Image *ext; int r; assert(image_path); @@ -704,12 +803,15 @@ static int install_chroot_dropin( if (!dropin) return -ENOMEM; - text = strjoin(PORTABLE_DROPIN_MARKER_BEGIN, image_path, PORTABLE_DROPIN_MARKER_END "\n"); - if (!text) - return -ENOMEM; + r = make_marker_text(image_path, extension_images, &text); + if (r < 0) + return log_debug_errno(r, "Failed to generate marker string for portable drop-in: %m"); if (endswith(m->name, ".service")) { - const char *os_release_source; + const char *os_release_source, *root_type; + _cleanup_free_ char *base_name = NULL; + + root_type = root_setting_from_image(type); if (access("/etc/os-release", F_OK) < 0) { if (errno != ENOENT) @@ -719,14 +821,23 @@ static int install_chroot_dropin( } else os_release_source = "/etc/os-release"; + r = path_extract_filename(m->image_path ?: image_path, &base_name); + if (r < 0) + return log_debug_errno(r, "Failed to extract basename from '%s': %m", m->image_path ?: image_path); + if (!strextend(&text, "\n" "[Service]\n", - IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) ? "RootDirectory=" : "RootImage=", image_path, "\n" - "Environment=PORTABLE=", basename(image_path), "\n" + root_type, image_path, "\n" + "Environment=PORTABLE=", base_name, "\n" "BindReadOnlyPaths=", os_release_source, ":/run/host/os-release\n" - "LogExtraFields=PORTABLE=", basename(image_path), "\n")) + "LogExtraFields=PORTABLE=", base_name, "\n")) return -ENOMEM; + + if (m->image_path && !path_equal(m->image_path, image_path)) + ORDERED_HASHMAP_FOREACH(ext, extension_images) + if (!strextend(&text, "ExtensionImages=", ext->path, "\n")) + return -ENOMEM; } r = write_string_file(dropin, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); @@ -841,6 +952,7 @@ static int attach_unit_file( const LookupPaths *paths, const char *image_path, ImageType type, + OrderedHashmap *extension_images, const PortableMetadata *m, const char *profile, PortableFlags flags, @@ -881,7 +993,7 @@ static int attach_unit_file( * is reloaded while we are creating things here: as long as only the drop-ins exist the unit doesn't exist at * all for PID 1. */ - r = install_chroot_dropin(image_path, type, m, dropin_dir, &chroot_dropin, changes, n_changes); + r = install_chroot_dropin(image_path, type, extension_images, m, dropin_dir, &chroot_dropin, changes, n_changes); if (r < 0) return r; @@ -984,20 +1096,49 @@ static int install_image_symlink( return 0; } +static int install_image_and_extensions_symlinks( + const Image *image, + OrderedHashmap *extension_images, + PortableFlags flags, + PortableChange **changes, + size_t *n_changes) { + + Image *ext; + int r; + + assert(image); + + ORDERED_HASHMAP_FOREACH(ext, extension_images) { + r = install_image_symlink(ext->path, flags, changes, n_changes); + if (r < 0) + return r; + } + + r = install_image_symlink(image->path, flags, changes, n_changes); + if (r < 0) + return r; + + return 0; +} + int portable_attach( sd_bus *bus, const char *name_or_path, char **matches, const char *profile, + char **extension_image_paths, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error) { + _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL; _cleanup_hashmap_free_ Hashmap *unit_files = NULL; _cleanup_(lookup_paths_free) LookupPaths paths = {}; _cleanup_(image_unrefp) Image *image = NULL; PortableMetadata *item; + Image *ext; + char **p; int r; assert(name_or_path); @@ -1005,11 +1146,40 @@ int portable_attach( r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image); if (r < 0) return r; + if (!strv_isempty(extension_image_paths)) { + extension_images = ordered_hashmap_new(&image_hash_ops); + if (!extension_images) + return -ENOMEM; - r = portable_extract_by_path(image->path, matches, NULL, &unit_files, error); + STRV_FOREACH(p, extension_image_paths) { + _cleanup_(image_unrefp) Image *new = NULL; + + r = image_find_harder(IMAGE_PORTABLE, *p, NULL, &new); + if (r < 0) + return r; + + r = ordered_hashmap_put(extension_images, new->name, new); + if (r < 0) + return r; + TAKE_PTR(new); + } + } + + r = portable_extract_by_path(image->path, true, matches, NULL, &unit_files, error); if (r < 0) return r; + ORDERED_HASHMAP_FOREACH(ext, extension_images) { + _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL; + + r = portable_extract_by_path(ext->path, false, matches, NULL, &extra_unit_files, error); + if (r < 0) + return r; + r = hashmap_move(unit_files, extra_unit_files); + if (r < 0) + return r; + } + r = lookup_paths_init(&paths, UNIT_FILE_SYSTEM, LOOKUP_PATHS_SPLIT_USR, NULL); if (r < 0) return r; @@ -1029,62 +1199,95 @@ int portable_attach( } HASHMAP_FOREACH(item, unit_files) { - r = attach_unit_file(&paths, image->path, image->type, item, profile, flags, changes, n_changes); + r = attach_unit_file(&paths, image->path, image->type, extension_images, + item, profile, flags, changes, n_changes); if (r < 0) return r; } /* 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_symlink(image->path, flags, changes, n_changes); + (void) install_image_and_extensions_symlinks(image, extension_images, flags, changes, n_changes); return 0; } -static bool marker_matches_image(const char *marker, const char *name_or_path) { +static bool marker_matches_images(const char *marker, const char *name_or_path, char **extension_image_paths) { + _cleanup_strv_free_ char **root_and_extensions = NULL; + char **image_name_or_path; const char *a; + int r; assert(marker); assert(name_or_path); - a = last_path_component(marker); + /* If extensions were used when attaching, the marker will be a colon-separated + * list of images/paths. We enforce strict 1:1 matching, so that we are sure + * we are detaching exactly what was attached. + * For each image, starting with the root, we look for a token in the marker, + * and return a negative answer on any non-matching combination. */ - if (image_name_is_valid(name_or_path)) { - const char *e, *underscore; + root_and_extensions = strv_new(name_or_path); + if (!root_and_extensions) + return -ENOMEM; - /* We shall match against an image name. In that case let's compare the last component, and optionally - * allow either a suffix of ".raw" or a series of "/". - * But allow matching on a different version of the same image, when a "_" is used as a separator. */ - underscore = strchr(name_or_path, '_'); - if (underscore) - return strneq(a, name_or_path, underscore - name_or_path); + r = strv_extend_strv(&root_and_extensions, extension_image_paths, false); + if (r < 0) + return r; - e = startswith(a, name_or_path); - if (!e) + STRV_FOREACH(image_name_or_path, root_and_extensions) { + _cleanup_free_ char *image = NULL; + + r = extract_first_word(&marker, &image, ":", EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return log_debug_errno(r, "Failed to parse marker: %s", marker); + if (r == 0) return false; - return - e[strspn(e, "/")] == 0 || - streq(e, ".raw"); - } else { - const char *b, *underscore; - size_t l; + a = last_path_component(image); - /* We shall match against a path. Let's ignore any prefix here though, as often there are many ways to - * reach the same file. However, in this mode, let's validate any file suffix. */ + if (image_name_is_valid(*image_name_or_path)) { + const char *e, *underscore; - l = strcspn(a, "/"); - b = last_path_component(name_or_path); + /* We shall match against an image name. In that case let's compare the last component, and optionally + * allow either a suffix of ".raw" or a series of "/". + * But allow matching on a different version of the same image, when a "_" is used as a separator. */ + underscore = strchr(*image_name_or_path, '_'); + if (underscore) { + if (strneq(a, *image_name_or_path, underscore - *image_name_or_path)) + continue; + return false; + } - if (strcspn(b, "/") != l) - return false; + e = startswith(a, *image_name_or_path); + if (!e) + return false; - underscore = strchr(b, '_'); - if (underscore) - l = underscore - b; + if(!(e[strspn(e, "/")] == 0 || streq(e, ".raw"))) + return false; + } else { + const char *b, *underscore; + size_t l; - return strneq(a, b, l); + /* We shall match against a path. Let's ignore any prefix here though, as often there are many ways to + * reach the same file. However, in this mode, let's validate any file suffix. */ + + l = strcspn(a, "/"); + b = last_path_component(*image_name_or_path); + + if (strcspn(b, "/") != l) + return false; + + underscore = strchr(b, '_'); + if (underscore) + l = underscore - b; + + if (!strneq(a, b, l)) + return false; + } } + + return true; } static int test_chroot_dropin( @@ -1092,6 +1295,7 @@ static int test_chroot_dropin( const char *where, const char *fname, const char *name_or_path, + char **extension_image_paths, char **ret_marker) { _cleanup_free_ char *line = NULL, *marker = NULL; @@ -1138,7 +1342,7 @@ static int test_chroot_dropin( if (!name_or_path) r = true; else - r = marker_matches_image(marker, name_or_path); + r = marker_matches_images(marker, name_or_path, extension_image_paths); if (ret_marker) *ret_marker = TAKE_PTR(marker); @@ -1149,6 +1353,7 @@ static int test_chroot_dropin( int portable_detach( sd_bus *bus, const char *name_or_path, + char **extension_image_paths, PortableFlags flags, PortableChange **changes, size_t *n_changes, @@ -1193,7 +1398,7 @@ int portable_detach( if (!IN_SET(de->d_type, DT_LNK, DT_REG)) continue; - r = test_chroot_dropin(d, where, de->d_name, name_or_path, &marker); + r = test_chroot_dropin(d, where, de->d_name, name_or_path, extension_image_paths, &marker); if (r < 0) return r; if (r == 0) @@ -1215,12 +1420,20 @@ int portable_detach( if (r < 0) return log_debug_errno(r, "Failed to add unit name '%s' to set: %m", de->d_name); - if (path_is_absolute(marker) && - !image_in_search_path(IMAGE_PORTABLE, NULL, marker)) { + for (const char *p = marker;;) { + _cleanup_free_ char *image = NULL; - r = set_ensure_consume(&markers, &path_hash_ops_free, TAKE_PTR(marker)); + r = extract_first_word(&p, &image, ":", EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_RETAIN_ESCAPE); if (r < 0) - return r; + return log_debug_errno(r, "Failed to parse marker: %s", p); + if (r == 0) + break; + + if (path_is_absolute(image) && !image_in_search_path(IMAGE_PORTABLE, NULL, image)) { + r = set_ensure_consume(&markers, &path_hash_ops_free, TAKE_PTR(image)); + if (r < 0) + return r; + } } } @@ -1358,7 +1571,7 @@ static int portable_get_state_internal( if (!IN_SET(de->d_type, DT_LNK, DT_REG)) continue; - r = test_chroot_dropin(d, where, de->d_name, name_or_path, NULL); + r = test_chroot_dropin(d, where, de->d_name, name_or_path, NULL, NULL); if (r < 0) return r; if (r == 0) diff --git a/src/portable/portable.h b/src/portable/portable.h index 5694bd2b623..dd080edf4e0 100644 --- a/src/portable/portable.h +++ b/src/portable/portable.h @@ -11,6 +11,7 @@ typedef struct PortableMetadata { int fd; char *source; + char *image_path; char name[]; } PortableMetadata; @@ -18,10 +19,13 @@ typedef struct PortableMetadata { #define PORTABLE_METADATA_IS_UNIT(m) (!IN_SET((m)->name[0], 0, '/')) typedef enum PortableFlags { - PORTABLE_PREFER_COPY = 1 << 0, - PORTABLE_PREFER_SYMLINK = 1 << 1, - PORTABLE_RUNTIME = 1 << 2, + PORTABLE_RUNTIME = 1 << 0, /* Public API via DBUS, do not change */ + PORTABLE_PREFER_COPY = 1 << 1, + PORTABLE_PREFER_SYMLINK = 1 << 2, PORTABLE_REATTACH = 1 << 3, + _PORTABLE_MASK_PUBLIC = PORTABLE_RUNTIME, + _PORTABLE_TYPE_MAX, + _PORTABLE_TYPE_INVALID = -EINVAL, } PortableFlags; /* This enum is anonymous, since we usually store it in an 'int', as we overload it with negative errno @@ -59,10 +63,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(PortableMetadata*, portable_metadata_unref); int portable_metadata_hashmap_to_sorted_array(Hashmap *unit_files, PortableMetadata ***ret); -int portable_extract(const char *image, char **matches, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, sd_bus_error *error); +int portable_extract(const char *image, char **matches, char **extension_image_paths, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, sd_bus_error *error); -int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error); -int portable_detach(sd_bus *bus, const char *name_or_path, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error); +int portable_attach(sd_bus *bus, const char *name_or_path, char **matches, const char *profile, char **extension_images, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error); +int portable_detach(sd_bus *bus, const char *name_or_path, char **extension_image_paths, PortableFlags flags, PortableChange **changes, size_t *n_changes, sd_bus_error *error); int portable_get_state(sd_bus *bus, const char *name_or_path, PortableFlags flags, PortableState *ret, sd_bus_error *error); diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 77fcd4fe6fc..fa6df9054ae 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -21,9 +21,11 @@ #include "main-func.h" #include "os-util.h" #include "pager.h" +#include "parse-argument.h" #include "parse-util.h" #include "path-util.h" #include "pretty-print.h" +#include "portable.h" #include "spawn-polkit-agent.h" #include "string-util.h" #include "strv.h" @@ -44,6 +46,9 @@ static const char *arg_host = NULL; static bool arg_enable = false; static bool arg_now = false; static bool arg_no_block = false; +static char **arg_extension_images = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_extension_images, strv_freep); static bool is_portable_managed(const char *unit) { return ENDSWITH_SET(unit, ".service", ".target", ".socket", ".path", ".timer"); @@ -83,6 +88,38 @@ static int determine_image(const char *image, bool permit_non_existing, char **r return 0; } +static int attach_extensions_to_message(sd_bus_message *m, char **extensions) { + char **p; + int r; + + assert(m); + + if (strv_isempty(extensions)) + return 0; + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + STRV_FOREACH(p, extensions) { + _cleanup_free_ char *resolved_extension_image = NULL; + + r = determine_image(*p, false, &resolved_extension_image); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", resolved_extension_image); + 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 0; +} + static int extract_prefix(const char *path, char **ret) { _cleanup_free_ char *name = NULL; const char *bn, *underscore; @@ -219,15 +256,55 @@ static int maybe_reload(sd_bus **bus) { return 0; } -static int inspect_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; +static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd_bus_message **reply) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *method; + uint64_t flags = 0; + int r; + + assert(bus); + assert(reply); + + method = strv_isempty(arg_extension_images) ? "GetImageMetadata" : "GetImageMetadataWithExtensions"; + + r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", image); + if (r < 0) + return bus_log_create_error(r); + + r = attach_extensions_to_message(m, arg_extension_images); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(m, matches); + if (r < 0) + return bus_log_create_error(r); + + if (!strv_isempty(arg_extension_images)) { + r = sd_bus_message_append(m, "t", flags); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_call(bus, m, 0, &error, reply); + if (r < 0) + return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r)); + + return 0; +} + +static int inspect_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_strv_free_ char **matches = NULL; _cleanup_free_ char *image = NULL; bool nl = false, header = false; - const void *data; const char *path; + const void *data; size_t sz; int r; @@ -243,21 +320,9 @@ static int inspect_image(int argc, char *argv[], void *userdata) { if (r < 0) return r; - r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "GetImageMetadata"); + r = get_image_metadata(bus, image, matches, &reply); if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", image); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, matches); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r)); + return r; r = sd_bus_message_read(reply, "s", &path); if (r < 0) @@ -607,8 +672,7 @@ static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) { static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) { _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_strv_free_ char **matches = NULL; int r; @@ -623,21 +687,9 @@ static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) { if (r < 0) return log_error_errno(r, "Could not watch jobs: %m"); - r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "GetImageMetadata"); + r = get_image_metadata(bus, image, matches, &reply); if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", image); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, matches); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r)); + return r; r = sd_bus_message_skip(reply, "say"); if (r < 0) @@ -693,7 +745,7 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) { int r; assert(method); - assert(STR_IN_SET(method, "AttachImage", "ReattachImage")); + assert(STR_IN_SET(method, "AttachImage", "ReattachImage", "AttachImageWithExtensions", "ReattachImageWithExtensions")); r = determine_image(argv[1], false, &image); if (r < 0) @@ -717,11 +769,24 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) { if (r < 0) return bus_log_create_error(r); + r = attach_extensions_to_message(m, arg_extension_images); + if (r < 0) + return r; + r = sd_bus_message_append_strv(m, matches); if (r < 0) return bus_log_create_error(r); - r = sd_bus_message_append(m, "sbs", arg_profile, arg_runtime, arg_copy_mode); + r = sd_bus_message_append(m, "s", arg_profile); + if (r < 0) + return bus_log_create_error(r); + + if (STR_IN_SET(method, "AttachImageWithExtensions", "ReattachImageWithExtensions")) { + uint64_t flags = arg_runtime ? PORTABLE_RUNTIME : 0; + + r = sd_bus_message_append(m, "st", arg_copy_mode, flags); + } else + r = sd_bus_message_append(m, "bs", arg_runtime, arg_copy_mode); if (r < 0) return bus_log_create_error(r); @@ -733,7 +798,7 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) { print_changes(reply); - if (streq(method, "AttachImage")) + if (STR_IN_SET(method, "AttachImage", "AttachImageWithExtensions")) (void) maybe_enable_start(bus, reply); else { /* ReattachImage returns 2 lists - removed units first, and changed/added second */ @@ -745,18 +810,19 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) { } static int attach_image(int argc, char *argv[], void *userdata) { - return attach_reattach_image(argc, argv, "AttachImage"); + return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) ? "AttachImage" : "AttachImageWithExtensions"); } static int reattach_image(int argc, char *argv[], void *userdata) { - return attach_reattach_image(argc, argv, "ReattachImage"); + return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) ? "ReattachImage" : "ReattachImageWithExtensions"); } static int detach_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *image = NULL; + const char *method; int r; r = determine_image(argv[1], true, &image); @@ -771,9 +837,32 @@ static int detach_image(int argc, char *argv[], void *userdata) { (void) maybe_stop_disable(bus, image, argv); - r = bus_call_method(bus, bus_portable_mgr, "DetachImage", &error, &reply, "sb", image, arg_runtime); + method = strv_isempty(arg_extension_images) ? "DetachImage" : "DetachImageWithExtensions"; + + r = bus_message_new_method_call(bus, &m, bus_portable_mgr, method); if (r < 0) - return log_error_errno(r, "Failed to detach image: %s", bus_error_message(&error, r)); + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", image); + if (r < 0) + return bus_log_create_error(r); + + r = attach_extensions_to_message(m, arg_extension_images); + if (r < 0) + return r; + + if (!strv_isempty(arg_extension_images)) { + uint64_t flags = arg_runtime ? PORTABLE_RUNTIME : 0; + + r = sd_bus_message_append(m, "t", flags); + } else + r = sd_bus_message_append(m, "b", arg_runtime); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "%s failed: %s", method, bus_error_message(&error, r)); (void) maybe_reload(&bus); @@ -1045,6 +1134,7 @@ static int help(int argc, char *argv[], void *userdata) { " --now Immediately start/stop the portable service after\n" " attach/before detach\n" " --no-block Don't block waiting for attach --now to complete\n" + " --extension=PATH Extend the image with an overlay\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -1055,6 +1145,7 @@ static int help(int argc, char *argv[], void *userdata) { } static int parse_argv(int argc, char *argv[]) { + int r; enum { ARG_VERSION = 0x100, @@ -1068,6 +1159,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_ENABLE, ARG_NOW, ARG_NO_BLOCK, + ARG_EXTENSION, }; static const struct option options[] = { @@ -1087,6 +1179,7 @@ static int parse_argv(int argc, char *argv[]) { { "enable", no_argument, NULL, ARG_ENABLE }, { "now", no_argument, NULL, ARG_NOW }, { "no-block", no_argument, NULL, ARG_NO_BLOCK }, + { "extension", required_argument, NULL, ARG_EXTENSION }, {} }; @@ -1185,6 +1278,12 @@ static int parse_argv(int argc, char *argv[]) { arg_no_block = true; break; + case ARG_EXTENSION: + r = strv_extend(&arg_extension_images, optarg); + if (r < 0) + return log_oom(); + break; + case '?': return -EINVAL; diff --git a/src/portable/portabled-bus.c b/src/portable/portabled-bus.c index 6d0dee99c3f..72f685f76db 100644 --- a/src/portable/portabled-bus.c +++ b/src/portable/portabled-bus.c @@ -252,11 +252,13 @@ static int method_attach_image(sd_bus_message *message, void *userdata, sd_bus_e } static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **extension_images = NULL; PortableChange *changes = NULL; + PortableFlags flags = 0; Manager *m = userdata; size_t n_changes = 0; const char *name_or_path; - int r, runtime; + int r; assert(message); assert(m); @@ -265,10 +267,37 @@ static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_e * detach already deleted images too, in case the user already deleted an image before properly detaching * it. */ - r = sd_bus_message_read(message, "sb", &name_or_path, &runtime); + r = sd_bus_message_read(message, "s", &name_or_path); if (r < 0) return r; + if (sd_bus_message_is_method_call(message, NULL, "DetachImageWithExtensions")) { + uint64_t input_flags = 0; + + r = sd_bus_message_read_strv(message, &extension_images); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "t", &input_flags); + if (r < 0) + return r; + + if ((input_flags & ~_PORTABLE_MASK_PUBLIC) != 0) + return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, + "Invalid 'flags' parameter '%" PRIu64 "'", + input_flags); + flags |= input_flags; + } else { + int runtime; + + r = sd_bus_message_read(message, "b", &runtime); + if (r < 0) + return r; + + if (runtime) + flags |= PORTABLE_RUNTIME; + } + r = bus_verify_polkit_async( message, CAP_SYS_ADMIN, @@ -286,7 +315,8 @@ static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_e r = portable_detach( sd_bus_message_get_bus(message), name_or_path, - runtime ? PORTABLE_RUNTIME : 0, + extension_images, + flags, &changes, &n_changes, error); @@ -383,6 +413,16 @@ const sd_bus_vtable manager_vtable[] = { "a{say}", units), method_get_image_metadata, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("GetImageMetadataWithExtensions", + SD_BUS_ARGS("s", image, + "as", extensions, + "as", matches, + "t", flags), + SD_BUS_RESULT("s", image, + "ay", os_release, + "a{say}", units), + method_get_image_metadata, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("GetImageState", SD_BUS_ARGS("s", image), SD_BUS_RESULT("s", state), @@ -397,12 +437,29 @@ const sd_bus_vtable manager_vtable[] = { SD_BUS_RESULT("a(sss)", changes), method_attach_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("AttachImageWithExtensions", + SD_BUS_ARGS("s", image, + "as", extensions, + "as", matches, + "s", profile, + "s", copy_mode, + "t", flags), + SD_BUS_RESULT("a(sss)", changes), + method_attach_image, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("DetachImage", SD_BUS_ARGS("s", image, "b", runtime), SD_BUS_RESULT("a(sss)", changes), method_detach_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("DetachImageWithExtensions", + SD_BUS_ARGS("s", image, + "as", extensions, + "t", flags), + SD_BUS_RESULT("a(sss)", changes), + method_detach_image, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("ReattachImage", SD_BUS_ARGS("s", image, "as", matches, @@ -413,6 +470,17 @@ const sd_bus_vtable manager_vtable[] = { "a(sss)", changes_updated), method_reattach_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("ReattachImageWithExtensions", + SD_BUS_ARGS("s", image, + "as", extensions, + "as", matches, + "s", profile, + "s", copy_mode, + "t", flags), + SD_BUS_RESULT("a(sss)", changes_removed, + "a(sss)", changes_updated), + method_reattach_image, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("RemoveImage", SD_BUS_ARGS("s", image), SD_BUS_NO_RESULT, diff --git a/src/portable/portabled-image-bus.c b/src/portable/portabled-image-bus.c index 8332332c915..88d8f914e03 100644 --- a/src/portable/portabled-image-bus.c +++ b/src/portable/portabled-image-bus.c @@ -75,20 +75,22 @@ static int bus_image_method_get_os_release(sd_bus_message *message, void *userda static int append_fd(sd_bus_message *m, PortableMetadata *d) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *buf = NULL; - size_t n; + size_t n = 0; int r; assert(m); - assert(d); - assert(d->fd >= 0); - f = take_fdopen(&d->fd, "r"); - if (!f) - return -errno; + if (d) { + assert(d->fd >= 0); - r = read_full_stream(f, &buf, &n); - if (r < 0) - return r; + f = take_fdopen(&d->fd, "r"); + if (!f) + return -errno; + + r = read_full_stream(f, &buf, &n); + if (r < 0) + return r; + } return sd_bus_message_append_array(m, 'y', buf, n); } @@ -101,10 +103,12 @@ int bus_image_common_get_metadata( sd_bus_error *error) { _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL; + _cleanup_strv_free_ char **matches = NULL, **extension_images = NULL; _cleanup_hashmap_free_ Hashmap *unit_files = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ PortableMetadata **sorted = NULL; - _cleanup_strv_free_ char **matches = NULL; + /* Unused for now, but added to the DBUS methods for future-proofing */ + uint64_t input_flags = 0; size_t i; int r; @@ -116,10 +120,29 @@ int bus_image_common_get_metadata( m = image->userdata; } + if (sd_bus_message_is_method_call(message, NULL, "GetImageMetadataWithExtensions") || + sd_bus_message_is_method_call(message, NULL, "GetMetadataWithExtensions")) { + r = sd_bus_message_read_strv(message, &extension_images); + if (r < 0) + return r; + } + r = sd_bus_message_read_strv(message, &matches); if (r < 0) return r; + if (sd_bus_message_is_method_call(message, NULL, "GetImageMetadataWithExtensions") || + sd_bus_message_is_method_call(message, NULL, "GetMetadataWithExtensions")) { + r = sd_bus_message_read(message, "t", &input_flags); + if (r < 0) + return r; + /* Let clients know that this version doesn't support any flags */ + if (input_flags != 0) + return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, + "Invalid 'flags' parameter '%" PRIu64 "'", + input_flags); + } + r = bus_image_acquire(m, message, name_or_path, @@ -136,6 +159,7 @@ int bus_image_common_get_metadata( r = portable_extract( image->path, matches, + extension_images, &os_release, &unit_files, error); @@ -223,12 +247,12 @@ int bus_image_common_attach( Image *image, sd_bus_error *error) { - _cleanup_strv_free_ char **matches = NULL; + _cleanup_strv_free_ char **matches = NULL, **extension_images = NULL; PortableChange *changes = NULL; PortableFlags flags = 0; const char *profile, *copy_mode; size_t n_changes = 0; - int runtime, r; + int r; assert(message); assert(name_or_path || image); @@ -238,14 +262,44 @@ int bus_image_common_attach( m = image->userdata; } + if (sd_bus_message_is_method_call(message, NULL, "AttachImageWithExtensions") || + sd_bus_message_is_method_call(message, NULL, "AttachWithExtensions")) { + r = sd_bus_message_read_strv(message, &extension_images); + if (r < 0) + return r; + } + r = sd_bus_message_read_strv(message, &matches); if (r < 0) return r; - r = sd_bus_message_read(message, "sbs", &profile, &runtime, ©_mode); + r = sd_bus_message_read(message, "s", &profile); if (r < 0) return r; + if (sd_bus_message_is_method_call(message, NULL, "AttachImageWithExtensions") || + sd_bus_message_is_method_call(message, NULL, "AttachWithExtensions")) { + uint64_t input_flags = 0; + + r = sd_bus_message_read(message, "st", ©_mode, &input_flags); + if (r < 0) + return r; + if ((input_flags & ~_PORTABLE_MASK_PUBLIC) != 0) + return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, + "Invalid 'flags' parameter '%" PRIu64 "'", + input_flags); + flags |= input_flags; + } else { + int runtime; + + r = sd_bus_message_read(message, "bs", &runtime, ©_mode); + if (r < 0) + return r; + + if (runtime) + flags |= PORTABLE_RUNTIME; + } + if (streq(copy_mode, "symlink")) flags |= PORTABLE_PREFER_SYMLINK; else if (streq(copy_mode, "copy")) @@ -253,9 +307,6 @@ int bus_image_common_attach( else if (!isempty(copy_mode)) return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode); - if (runtime) - flags |= PORTABLE_RUNTIME; - r = bus_image_acquire(m, message, name_or_path, @@ -274,6 +325,7 @@ int bus_image_common_attach( image->path, matches, profile, + extension_images, flags, &changes, &n_changes, @@ -297,19 +349,46 @@ static int bus_image_method_detach( void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **extension_images = NULL; PortableChange *changes = NULL; Image *image = userdata; Manager *m = image->userdata; + PortableFlags flags = 0; size_t n_changes = 0; - int r, runtime; + int r; assert(message); assert(image); assert(m); - r = sd_bus_message_read(message, "b", &runtime); - if (r < 0) - return r; + if (sd_bus_message_is_method_call(message, NULL, "DetachWithExtensions")) { + r = sd_bus_message_read_strv(message, &extension_images); + if (r < 0) + return r; + } + + if (sd_bus_message_is_method_call(message, NULL, "DetachWithExtensions")) { + uint64_t input_flags = 0; + + r = sd_bus_message_read(message, "t", &input_flags); + if (r < 0) + return r; + + if ((input_flags & ~_PORTABLE_MASK_PUBLIC) != 0) + return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, + "Invalid 'flags' parameter '%" PRIu64 "'", + input_flags); + flags |= input_flags; + } else { + int runtime; + + r = sd_bus_message_read(message, "b", &runtime); + if (r < 0) + return r; + + if (runtime) + flags |= PORTABLE_RUNTIME; + } r = bus_verify_polkit_async( message, @@ -328,7 +407,8 @@ static int bus_image_method_detach( r = portable_detach( sd_bus_message_get_bus(message), image->path, - runtime ? PORTABLE_RUNTIME : 0, + extension_images, + flags, &changes, &n_changes, error); @@ -510,10 +590,10 @@ int bus_image_common_reattach( PortableChange *changes_detached = NULL, *changes_attached = NULL, *changes_gone = NULL; size_t n_changes_detached = 0, n_changes_attached = 0, n_changes_gone = 0; - _cleanup_strv_free_ char **matches = NULL; + _cleanup_strv_free_ char **matches = NULL, **extension_images = NULL; PortableFlags flags = PORTABLE_REATTACH; const char *profile, *copy_mode; - int runtime, r; + int r; assert(message); assert(name_or_path || image); @@ -523,14 +603,45 @@ int bus_image_common_reattach( m = image->userdata; } + if (sd_bus_message_is_method_call(message, NULL, "ReattachImageWithExtensions") || + sd_bus_message_is_method_call(message, NULL, "ReattachWithExtensions")) { + r = sd_bus_message_read_strv(message, &extension_images); + if (r < 0) + return r; + } + r = sd_bus_message_read_strv(message, &matches); if (r < 0) return r; - r = sd_bus_message_read(message, "sbs", &profile, &runtime, ©_mode); + r = sd_bus_message_read(message, "s", &profile); if (r < 0) return r; + if (sd_bus_message_is_method_call(message, NULL, "ReattachImageWithExtensions") || + sd_bus_message_is_method_call(message, NULL, "ReattachWithExtensions")) { + uint64_t input_flags = 0; + + r = sd_bus_message_read(message, "st", ©_mode, &input_flags); + if (r < 0) + return r; + + if ((input_flags & ~_PORTABLE_MASK_PUBLIC) != 0) + return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, + "Invalid 'flags' parameter '%" PRIu64 "'", + input_flags); + flags |= input_flags; + } else { + int runtime; + + r = sd_bus_message_read(message, "bs", &runtime, ©_mode); + if (r < 0) + return r; + + if (runtime) + flags |= PORTABLE_RUNTIME; + } + if (streq(copy_mode, "symlink")) flags |= PORTABLE_PREFER_SYMLINK; else if (streq(copy_mode, "copy")) @@ -538,9 +649,6 @@ int bus_image_common_reattach( else if (!isempty(copy_mode)) return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode); - if (runtime) - flags |= PORTABLE_RUNTIME; - r = bus_image_acquire(m, message, name_or_path, @@ -557,6 +665,7 @@ int bus_image_common_reattach( r = portable_detach( sd_bus_message_get_bus(message), image->path, + extension_images, flags, &changes_detached, &n_changes_detached, @@ -569,6 +678,7 @@ int bus_image_common_reattach( image->path, matches, profile, + extension_images, flags, &changes_attached, &n_changes_attached, @@ -721,6 +831,15 @@ const sd_bus_vtable image_vtable[] = { "a{say}", units), bus_image_method_get_metadata, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("GetMetadataWithExtensions", + SD_BUS_ARGS("as", extensions, + "as", matches, + "t", flags), + SD_BUS_RESULT("s", image, + "ay", os_release, + "a{say}", units), + bus_image_method_get_metadata, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("GetState", SD_BUS_NO_ARGS, SD_BUS_RESULT("s", state), @@ -734,11 +853,26 @@ const sd_bus_vtable image_vtable[] = { SD_BUS_RESULT("a(sss)", changes), bus_image_method_attach, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("AttachWithExtensions", + SD_BUS_ARGS("as", extensions, + "as", matches, + "s", profile, + "s", copy_mode, + "t", flags), + SD_BUS_RESULT("a(sss)", changes), + bus_image_method_attach, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("Detach", SD_BUS_ARGS("b", runtime), SD_BUS_RESULT("a(sss)", changes), bus_image_method_detach, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("DetachWithExtensions", + SD_BUS_ARGS("as", extensions, + "t", flags), + SD_BUS_RESULT("a(sss)", changes), + bus_image_method_detach, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("Reattach", SD_BUS_ARGS("as", matches, "s", profile, @@ -748,6 +882,16 @@ const sd_bus_vtable image_vtable[] = { "a(sss)", changes_updated), bus_image_method_reattach, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("ReattacheWithExtensions", + SD_BUS_ARGS("as", extensions, + "as", matches, + "s", profile, + "s", copy_mode, + "t", flags), + SD_BUS_RESULT("a(sss)", changes_removed, + "a(sss)", changes_updated), + bus_image_method_reattach, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("Remove", SD_BUS_NO_ARGS, SD_BUS_NO_RESULT, diff --git a/test/TEST-29-PORTABLE/test.sh b/test/TEST-29-PORTABLE/test.sh index 801e74c13d8..cd421efdaea 100755 --- a/test/TEST-29-PORTABLE/test.sh +++ b/test/TEST-29-PORTABLE/test.sh @@ -15,6 +15,7 @@ test_append_files() { instmods loop =block instmods squashfs =squashfs instmods dm_verity =md + instmods overlay =overlayfs install_dmevent generate_module_dependencies inst_binary losetup diff --git a/test/units/testsuite-29.sh b/test/units/testsuite-29.sh index b5b05b42d9b..2e55c275bf1 100755 --- a/test/units/testsuite-29.sh +++ b/test/units/testsuite-29.sh @@ -63,6 +63,36 @@ portablectl detach --now --enable --runtime /tmp/minimal_1 app0 portablectl list | grep -q -F "No images." +root="/usr/share/minimal_0.raw" +app1="/usr/share/app1.raw" + +portablectl attach --now --runtime --extension ${app1} ${root} app1 + +systemctl is-active app1.service + +portablectl reattach --now --runtime --extension ${app1} ${root} app1 + +systemctl is-active app1.service + +portablectl detach --now --runtime --extension ${app1} ${root} app1 + +# portablectl also works with directory paths rather than images + +mkdir /tmp/rootdir /tmp/app1 /tmp/overlay +mount ${app1} /tmp/app1 +mount ${root} /tmp/rootdir +mount -t overlay overlay -o lowerdir=/tmp/app1:/tmp/rootdir /tmp/overlay + +portablectl attach --copy=symlink --now --runtime /tmp/overlay app1 + +systemctl is-active app1.service + +portablectl detach --now --runtime overlay app1 + +umount /tmp/overlay +umount /tmp/rootdir +umount /tmp/app1 + echo OK > /testok exit 0