From a39cba252ede11013b600dd858c9459bb707d663 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Fri, 3 Nov 2023 16:59:32 +0000 Subject: [PATCH 1/3] strv: add strv_free_many() to be used with CLEANUP_ARRAY() --- src/basic/strv.c | 9 +++++++++ src/basic/strv.h | 2 ++ 2 files changed, 11 insertions(+) diff --git a/src/basic/strv.c b/src/basic/strv.c index 37c8f553eb..c2109d35bc 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -88,6 +88,15 @@ char** strv_free_erase(char **l) { return mfree(l); } +void strv_free_many(char ***strvs, size_t n) { + assert(strvs || n == 0); + + FOREACH_ARRAY (i, strvs, n) + strv_free(*i); + + free(strvs); +} + char** strv_copy_n(char * const *l, size_t m) { _cleanup_strv_free_ char **result = NULL; char **k; diff --git a/src/basic/strv.h b/src/basic/strv.h index 544d46a3f8..fec2616733 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -32,6 +32,8 @@ char** strv_free_erase(char **l); DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase); #define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep) +void strv_free_many(char ***strvs, size_t n); + char** strv_copy_n(char * const *l, size_t n); static inline char** strv_copy(char * const *l) { return strv_copy_n(l, SIZE_MAX); From f0304df6e4595e7b780c2772881aaacfac40c9f5 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 1 Nov 2023 19:39:37 +0000 Subject: [PATCH 2/3] core: check that extensions have the hierarchies before overlaying Before confext was added, hierarchies always existed in extensions. Now they are optional - i.e., a sysext will not contain /etc/. So mixing a confext and a sysext fails, as we'll try to create an overlay with /etc/ from the base, the confext and the sysext, but the latter doesn't have the directory. After the source images are mounted, check that each hierarchy exists in each source image before creating the overlay, and drop them if they don't. Follow-up for 55ea4ef096543 --- src/core/namespace.c | 109 +++++++++++++++++++++---------------- test/units/testsuite-50.sh | 9 +++ 2 files changed, 71 insertions(+), 47 deletions(-) diff --git a/src/core/namespace.c b/src/core/namespace.c index 0ef84794c9..1a4d15a800 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -107,6 +107,7 @@ typedef struct MountEntry { unsigned long flags; /* Mount flags used by EMPTY_DIR and TMPFS. Do not include MS_RDONLY here, but please use read_only. */ unsigned n_followed; LIST_HEAD(MountOptions, image_options_const); + char **overlay_layers; } MountEntry; typedef struct MountList { @@ -336,6 +337,7 @@ static void mount_entry_done(MountEntry *p) { p->unprefixed_path_malloc = mfree(p->unprefixed_path_malloc); p->source_malloc = mfree(p->source_malloc); p->options_malloc = mfree(p->options_malloc); + p->overlay_layers = strv_free(p->overlay_layers); } static void mount_list_done(MountList *ml) { @@ -470,7 +472,8 @@ static int append_extensions( size_t n, char **extension_directories) { - _cleanup_strv_free_ char **overlays = NULL; + char ***overlays = NULL; + size_t n_overlays = 0; int r; assert(ml); @@ -480,21 +483,19 @@ static int append_extensions( assert(extension_dir); - /* Prepare a list of overlays, that will have as each element a string suitable for being - * passed as a lowerdir= parameter, so start with the hierarchy on the root. + n_overlays = strv_length(hierarchies); + if (n_overlays == 0) + return 0; + + /* Prepare a list of overlays, that will have as each element a strv containing all the layers that + * will later be concatenated as a lowerdir= parameter for the mount operation. * The overlays vector will have the same number of elements and will correspond to the * hierarchies vector, so they can be iterated upon together. */ - STRV_FOREACH(hierarchy, hierarchies) { - _cleanup_free_ char *prefixed_hierarchy = NULL; + overlays = new0(char**, n_overlays); + if (!overlays) + return -ENOMEM; - prefixed_hierarchy = path_join(root, *hierarchy); - if (!prefixed_hierarchy) - return -ENOMEM; - - r = strv_consume(&overlays, TAKE_PTR(prefixed_hierarchy)); - if (r < 0) - return r; - } + CLEANUP_ARRAY(overlays, n_overlays, strv_free_many); /* First, prepare a mount for each image, but these won't be visible to the unit, instead * they will be mounted in our propagate directory, and used as a source for the overlay. */ @@ -506,28 +507,18 @@ static int append_extensions( return -ENOMEM; for (size_t j = 0; hierarchies && hierarchies[j]; ++j) { - _cleanup_free_ char *prefixed_hierarchy = NULL, *escaped = NULL, *lowerdir = NULL; - - prefixed_hierarchy = path_join(mount_point, hierarchies[j]); + char *prefixed_hierarchy = path_join(mount_point, hierarchies[j]); if (!prefixed_hierarchy) return -ENOMEM; - escaped = shell_escape(prefixed_hierarchy, ",:"); - if (!escaped) - return -ENOMEM; - - /* Note that lowerdir= parameters are in 'reverse' order, so the - * top-most directory in the overlay comes first in the list. */ - lowerdir = strjoin(escaped, ":", overlays[j]); - if (!lowerdir) - return -ENOMEM; - - free_and_replace(overlays[j], lowerdir); + r = strv_consume(&overlays[j], TAKE_PTR(prefixed_hierarchy)); + if (r < 0) + return r; } MountEntry *me = mount_list_extend(ml); if (!me) - return log_oom_debug(); + return -ENOMEM; *me = (MountEntry) { .path_malloc = TAKE_PTR(mount_point), @@ -565,28 +556,18 @@ static int append_extensions( return -ENOMEM; for (size_t j = 0; hierarchies && hierarchies[j]; ++j) { - _cleanup_free_ char *prefixed_hierarchy = NULL, *escaped = NULL, *lowerdir = NULL; - - prefixed_hierarchy = path_join(mount_point, hierarchies[j]); + char *prefixed_hierarchy = path_join(mount_point, hierarchies[j]); if (!prefixed_hierarchy) return -ENOMEM; - escaped = shell_escape(prefixed_hierarchy, ",:"); - if (!escaped) - return -ENOMEM; - - /* Note that lowerdir= parameters are in 'reverse' order, so the - * top-most directory in the overlay comes first in the list. */ - lowerdir = strjoin(escaped, ":", overlays[j]); - if (!lowerdir) - return -ENOMEM; - - free_and_replace(overlays[j], lowerdir); + r = strv_consume(&overlays[j], TAKE_PTR(prefixed_hierarchy)); + if (r < 0) + return r; } MountEntry *me = mount_list_extend(ml); if (!me) - return log_oom_debug(); + return -ENOMEM; *me = (MountEntry) { .path_malloc = TAKE_PTR(mount_point), @@ -609,11 +590,11 @@ static int append_extensions( MountEntry *me = mount_list_extend(ml); if (!me) - return log_oom_debug(); + return -ENOMEM; *me = (MountEntry) { .path_malloc = TAKE_PTR(prefixed_hierarchy), - .options_malloc = TAKE_PTR(overlays[i]), + .overlay_layers = TAKE_PTR(overlays[i]), .mode = MOUNT_OVERLAY, .has_prefix = true, .ignore = true, /* If the source image doesn't set the ignore bit it will fail earlier. */ @@ -1413,12 +1394,46 @@ static int mount_image( } static int mount_overlay(const MountEntry *m) { - const char *options; + _cleanup_free_ char *options = NULL, *layers = NULL; int r; assert(m); - options = strjoina("lowerdir=", mount_entry_options(m)); + /* Extension hierarchies are optional (e.g.: confext might not have /opt) so check if they actually + * exist in an image before attempting to create an overlay with them, otherwise the mount will + * fail. We can't check before this, as the images will not be mounted until now. */ + + /* Note that lowerdir= parameters are in 'reverse' order, so the top-most directory in the overlay + * comes first in the list. */ + STRV_FOREACH_BACKWARDS(o, m->overlay_layers) { + _cleanup_free_ char *escaped = NULL; + + r = is_dir(*o, /* follow= */ false); + if (r <= 0) { + if (r != -ENOENT) + log_debug_errno(r, + "Failed to check whether overlay layer source path '%s' exists, ignoring: %m", + *o); + continue; + } + + escaped = shell_escape(*o, ",:"); + if (!escaped) + return log_oom_debug(); + + if (!strextend_with_separator(&layers, ":", escaped)) + return log_oom_debug(); + } + + if (!layers) { + log_debug("None of the overlays specified in '%s' exist at the source, skipping.", + mount_entry_options(m)); + return 0; /* Only the root is set? Then there's nothing to overlay */ + } + + options = strjoin("lowerdir=", layers, ":", mount_entry_path(m)); /* The root goes in last */ + if (!options) + return log_oom_debug(); (void) mkdir_p_label(mount_entry_path(m), 0755); diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index d12323607d..169aa1b864 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -397,6 +397,15 @@ mkdir -p /etc/symlink-test/ cp /etc/service-scoped-test.raw /etc/symlink-test/service-scoped-test-v1.raw ln -fs /etc/symlink-test/service-scoped-test-v1.raw /etc/symlink-test/service-scoped-test.raw systemd-run -P --property ExtensionImages=/etc/symlink-test/service-scoped-test.raw --property RootImage="${image}.raw" cat /etc/systemd/system/some_file | grep -q -F "MARKER_CONFEXT_123" +# And again mixing sysext and confext +systemd-run -P \ + --property ExtensionImages=/usr/share/symlink-test/app-nodistro.raw \ + --property ExtensionImages=/etc/symlink-test/service-scoped-test.raw \ + --property RootImage="${image}.raw" cat /etc/systemd/system/some_file | grep -q -F "MARKER_CONFEXT_123" +systemd-run -P \ + --property ExtensionImages=/usr/share/symlink-test/app-nodistro.raw \ + --property ExtensionImages=/etc/symlink-test/service-scoped-test.raw \ + --property RootImage="${image}.raw" cat /usr/lib/systemd/system/some_file | grep -q -F "MARKER=1" cat >/run/systemd/system/testservice-50e.service < Date: Wed, 16 Aug 2023 18:43:06 +0000 Subject: [PATCH 3/3] portable: add support for confext Support confexts for portable services --- docs/PORTABLE_SERVICES.md | 16 +++++----- man/org.freedesktop.portable1.xml | 8 ++--- man/portablectl.xml | 3 +- src/portable/portable.c | 47 ++++++++++++++++++++-------- src/portable/portable.h | 18 +++++------ src/portable/portablectl.c | 51 ++++++++++++++++++++----------- test/test-functions | 9 ++++++ test/units/testsuite-29.sh | 12 ++++++++ 8 files changed, 113 insertions(+), 51 deletions(-) diff --git a/docs/PORTABLE_SERVICES.md b/docs/PORTABLE_SERVICES.md index 8d65c9002d..7f07f231da 100644 --- a/docs/PORTABLE_SERVICES.md +++ b/docs/PORTABLE_SERVICES.md @@ -277,12 +277,14 @@ following must be also be observed: 2. The upper extension images must contain an extension-release file in `/usr/lib/extension-release.d/`, with an `ID=` and `SYSEXT_LEVEL=`/`VERSION_ID=` - matching the base image. + matching the base image for sysexts, or `/etc/extension-release.d/`, with an + `ID=` and `CONFEXT_LEVEL=`/`VERSION_ID=` matching the base image for confexts. 3. The base/OS image does not need to have any unit files. -4. The upper extension images must contain at least one matching unit file - each, with the right name prefix and suffix (see above). +4. The upper sysext images must contain at least one matching unit file each, + with the right name prefix and suffix (see above). Confext images do not have + to contain units. 5. As with the base/OS image, each upper extension image must be a plain sub-directory, btrfs subvolume, or a raw disk image. @@ -354,10 +356,10 @@ underscore (`_`) as separator. If only either one is found, it will be used by i The field will be named `PORTABLE_NAME_AND_VERSION=`. In case extensions are used, the same fields in the same order are, but prefixed by -`SYSEXT_`, are parsed from each `extension-release` file, and are appended to the -journal as log entries, using `PORTABLE_EXTENSION_NAME_AND_VERSION=` as the field -name. The base layer's field will be named `PORTABLE_ROOT_NAME_AND_VERSION=` instead -of `PORTABLE_NAME_AND_VERSION=` in this case. +`SYSEXT_`/`CONFEXT_`, are parsed from each `extension-release` file, and are appended +to the journal as log entries, using `PORTABLE_EXTENSION_NAME_AND_VERSION=` as the +field name. The base layer's field will be named `PORTABLE_ROOT_NAME_AND_VERSION=` +instead of `PORTABLE_NAME_AND_VERSION=` in this case. For example, a portable service `app0` using two extensions `app0.raw` and `app1.raw` (with `SYSEXT_ID=app`, and `SYSEXT_VERSION_ID=` `0` and `1` in their diff --git a/man/org.freedesktop.portable1.xml b/man/org.freedesktop.portable1.xml index e5902a0328..118d86d727 100644 --- a/man/org.freedesktop.portable1.xml +++ b/man/org.freedesktop.portable1.xml @@ -309,14 +309,14 @@ node /org/freedesktop/portable1 { ReattachImageWithExtensions() methods take in options as flags instead of booleans to allow for extendability. SD_SYSTEMD_PORTABLE_FORCE_ATTACH will cause safety checks that ensure the units are not running while the new image is attached or detached - to be skipped. SD_SYSTEMD_PORTABLE_FORCE_SYSEXT will cause the check that the + to be skipped. SD_SYSTEMD_PORTABLE_FORCE_EXTENSION will cause the check that the extension-release.NAME file in the extension image matches the image name to be skipped. They are defined as follows: -#define SD_SYSTEMD_PORTABLE_RUNTIME (UINT64_C(1) << 0) -#define SD_SYSTEMD_PORTABLE_FORCE_ATTACH (UINT64_C(1) << 1) -#define SD_SYSTEMD_PORTABLE_FORCE_SYSEXT (UINT64_C(1) << 2) +#define SD_SYSTEMD_PORTABLE_RUNTIME (UINT64_C(1) << 0) +#define SD_SYSTEMD_PORTABLE_FORCE_ATTACH (UINT64_C(1) << 1) +#define SD_SYSTEMD_PORTABLE_FORCE_EXTENSION (UINT64_C(1) << 2) diff --git a/man/portablectl.xml b/man/portablectl.xml index c7962f2349..03ca65e0cb 100644 --- a/man/portablectl.xml +++ b/man/portablectl.xml @@ -397,7 +397,8 @@ multiple times, in which case the order in which images are laid down follows the rules specified in systemd.exec5 for the ExtensionImages= directive and for the - systemd-sysext8 tool. + systemd-sysext8 and. + systemd-confext8 tools. The images must contain an extension-release file with metadata that matches what is defined in the os-release of IMAGE. See: os-release5. diff --git a/src/portable/portable.c b/src/portable/portable.c index d72f3a02db..d4b448a627 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -198,8 +198,18 @@ static int extract_now( /* First, find os-release/extension-release and send it upstream (or just save it). */ if (path_is_extension) { - os_release_id = strjoina("/usr/lib/extension-release.d/extension-release.", image_name); + ImageClass class = IMAGE_SYSEXT; + r = open_extension_release(where, IMAGE_SYSEXT, image_name, relax_extension_release_check, &os_release_path, &os_release_fd); + if (r == -ENOENT) { + r = open_extension_release(where, IMAGE_CONFEXT, image_name, relax_extension_release_check, &os_release_path, &os_release_fd); + if (r >= 0) + class = IMAGE_CONFEXT; + } + if (r < 0) + return log_error_errno(r, "Failed to open extension release from '%s': %m", image_name); + + os_release_id = strjoina((class == IMAGE_SYSEXT) ? "/usr/lib" : "/etc", "/extension-release.d/extension-release.", image_name); } else { os_release_id = "/etc/os-release"; r = open_os_release(where, &os_release_path, &os_release_fd); @@ -530,7 +540,7 @@ static int extract_image_and_extensions( const char *name_or_path, char **matches, char **extension_image_paths, - bool validate_sysext, + bool validate_extension, bool relax_extension_release_check, const ImagePolicy *image_policy, Image **ret_image, @@ -541,7 +551,7 @@ static int extract_image_and_extensions( char ***ret_valid_prefixes, sd_bus_error *error) { - _cleanup_free_ char *id = NULL, *version_id = NULL, *sysext_level = NULL; + _cleanup_free_ char *id = NULL, *version_id = NULL, *sysext_level = NULL, *confext_level = NULL; _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL; _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL; _cleanup_hashmap_free_ Hashmap *unit_files = NULL; @@ -596,13 +606,14 @@ static int extract_image_and_extensions( /* If we are layering extension images on top of a runtime image, check that the os-release and * extension-release metadata match, otherwise reject it immediately as invalid, or it will fail when * the units are started. Also, collect valid portable prefixes if caller requested that. */ - if (validate_sysext || ret_valid_prefixes) { + if (validate_extension || ret_valid_prefixes) { _cleanup_free_ char *prefixes = NULL; r = parse_env_file_fd(os_release->fd, os_release->name, "ID", &id, "VERSION_ID", &version_id, "SYSEXT_LEVEL", &sysext_level, + "CONFEXT_LEVEL", &confext_level, "PORTABLE_PREFIXES", &prefixes); if (r < 0) return r; @@ -638,15 +649,18 @@ static int extract_image_and_extensions( if (r < 0) return r; - if (!validate_sysext && !ret_valid_prefixes && !ret_extension_releases) + if (!validate_extension && !ret_valid_prefixes && !ret_extension_releases) continue; r = load_env_file_pairs_fd(extension_release_meta->fd, extension_release_meta->name, &extension_release); if (r < 0) return r; - if (validate_sysext) { + if (validate_extension) { r = extension_release_validate(ext->path, id, version_id, sysext_level, "portable", extension_release, IMAGE_SYSEXT); + if (r < 0) + r = extension_release_validate(ext->path, id, version_id, confext_level, "portable", extension_release, IMAGE_CONFEXT); + if (r == 0) return sd_bus_error_set_errnof(error, SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", ext->path); if (r < 0) @@ -717,8 +731,8 @@ int portable_extract( name_or_path, matches, extension_image_paths, - /* validate_sysext= */ false, - /* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT), + /* validate_extension= */ false, + /* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_EXTENSION), image_policy, &image, &extension_images, @@ -979,16 +993,18 @@ static int append_release_log_fields( static const char *const field_versions[_IMAGE_CLASS_MAX][4]= { [IMAGE_PORTABLE] = { "IMAGE_VERSION", "VERSION_ID", "BUILD_ID", NULL }, [IMAGE_SYSEXT] = { "SYSEXT_IMAGE_VERSION", "SYSEXT_VERSION_ID", "SYSEXT_BUILD_ID", NULL }, + [IMAGE_CONFEXT] = { "CONFEXT_IMAGE_VERSION", "CONFEXT_VERSION_ID", "CONFEXT_BUILD_ID", NULL }, }; static const char *const field_ids[_IMAGE_CLASS_MAX][3]= { [IMAGE_PORTABLE] = { "IMAGE_ID", "ID", NULL }, [IMAGE_SYSEXT] = { "SYSEXT_IMAGE_ID", "SYSEXT_ID", NULL }, + [IMAGE_CONFEXT] = { "CONFEXT_IMAGE_ID", "CONFEXT_ID", NULL }, }; _cleanup_strv_free_ char **fields = NULL; const char *id = NULL, *version = NULL; int r; - assert(IN_SET(type, IMAGE_PORTABLE, IMAGE_SYSEXT)); + assert(IN_SET(type, IMAGE_PORTABLE, IMAGE_SYSEXT, IMAGE_CONFEXT)); assert(!strv_isempty((char *const *)field_ids[type])); assert(!strv_isempty((char *const *)field_versions[type])); assert(field_name); @@ -1111,7 +1127,7 @@ static int install_chroot_dropin( /* With --force tell PID1 to avoid enforcing that the image and * extension-release. have to match. */ !IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) && - FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT) ? + FLAGS_SET(flags, PORTABLE_FORCE_EXTENSION) ? ":x-systemd.relax-extension-release-check\n" : "\n", /* In PORTABLE= we list the 'main' image name for this unit @@ -1131,6 +1147,13 @@ static int install_chroot_dropin( "PORTABLE_EXTENSION_NAME_AND_VERSION"); if (r < 0) return r; + + r = append_release_log_fields(&text, + ordered_hashmap_get(extension_releases, ext->name), + IMAGE_CONFEXT, + "PORTABLE_EXTENSION_NAME_AND_VERSION"); + if (r < 0) + return r; } } @@ -1432,8 +1455,8 @@ int portable_attach( name_or_path, matches, extension_image_paths, - /* validate_sysext= */ true, - /* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_SYSEXT), + /* validate_extension= */ true, + /* relax_extension_release_check= */ FLAGS_SET(flags, PORTABLE_FORCE_EXTENSION), image_policy, &image, &extension_images, diff --git a/src/portable/portable.h b/src/portable/portable.h index c61d65fed3..c4a9d5103e 100644 --- a/src/portable/portable.h +++ b/src/portable/portable.h @@ -18,19 +18,19 @@ typedef struct PortableMetadata { } PortableMetadata; #define PORTABLE_METADATA_IS_OS_RELEASE(m) (streq((m)->name, "/etc/os-release")) -#define PORTABLE_METADATA_IS_EXTENSION_RELEASE(m) (startswith((m)->name, "/usr/lib/extension-release.d/extension-release.")) +#define PORTABLE_METADATA_IS_EXTENSION_RELEASE(m) (startswith_strv((m)->name, STRV_MAKE("/usr/lib/extension-release.d/extension-release.", "/etc/extension-release.d/extension-release."))) #define PORTABLE_METADATA_IS_UNIT(m) (!IN_SET((m)->name[0], 0, '/')) typedef enum PortableFlags { - PORTABLE_RUNTIME = 1 << 0, /* Public API via DBUS, do not change */ - PORTABLE_FORCE_ATTACH = 1 << 1, /* Public API via DBUS, do not change */ - PORTABLE_FORCE_SYSEXT = 1 << 2, /* Public API via DBUS, do not change */ - PORTABLE_PREFER_COPY = 1 << 3, - PORTABLE_PREFER_SYMLINK = 1 << 4, - PORTABLE_REATTACH = 1 << 5, - _PORTABLE_MASK_PUBLIC = PORTABLE_RUNTIME | PORTABLE_FORCE_ATTACH | PORTABLE_FORCE_SYSEXT, + PORTABLE_RUNTIME = 1 << 0, /* Public API via DBUS, do not change */ + PORTABLE_FORCE_ATTACH = 1 << 1, /* Public API via DBUS, do not change */ + 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_MASK_PUBLIC = PORTABLE_RUNTIME | PORTABLE_FORCE_ATTACH | PORTABLE_FORCE_EXTENSION, _PORTABLE_TYPE_MAX, - _PORTABLE_TYPE_INVALID = -EINVAL, + _PORTABLE_TYPE_INVALID = -EINVAL, } PortableFlags; /* This enum is anonymous, since we usually store it in an 'int', as we overload it with negative errno diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 3a25624b08..6f804e65ee 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -248,7 +248,7 @@ static int maybe_reload(sd_bus **bus) { 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; - uint64_t flags = arg_force ? PORTABLE_FORCE_SYSEXT : 0; + uint64_t flags = arg_force ? PORTABLE_FORCE_EXTENSION : 0; const char *method; int r; @@ -384,9 +384,16 @@ static int inspect_image(int argc, char *argv[], void *userdata) { fflush(stdout); nl = true; } else { - _cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL, *sysext_level = NULL, - *sysext_id = NULL, *sysext_version_id = NULL, *sysext_scope = NULL, *portable_prefixes = NULL, - *id = NULL, *version_id = NULL, *image_id = NULL, *image_version = NULL, *build_id = NULL; + _cleanup_free_ char *pretty_portable = NULL, *sysext_pretty_os = NULL, + *sysext_level = NULL, *sysext_id = NULL, + *sysext_version_id = NULL, *sysext_scope = NULL, + *portable_prefixes = NULL, *id = NULL, *version_id = NULL, + *sysext_image_id = NULL, *sysext_image_version = NULL, + *sysext_build_id = NULL, *confext_pretty_os = NULL, + *confext_level = NULL, *confext_id = NULL, + *confext_version_id = NULL, *confext_scope = NULL, + *confext_image_id = NULL, *confext_image_version = NULL, + *confext_build_id = NULL; _cleanup_fclose_ FILE *f = NULL; f = fmemopen_unlocked((void*) data, sz, "r"); @@ -396,12 +403,20 @@ static int inspect_image(int argc, char *argv[], void *userdata) { r = parse_env_file(f, name, "SYSEXT_ID", &sysext_id, "SYSEXT_VERSION_ID", &sysext_version_id, - "SYSEXT_BUILD_ID", &build_id, - "SYSEXT_IMAGE_ID", &image_id, - "SYSEXT_IMAGE_VERSION", &image_version, - "SYSEXT_PRETTY_NAME", &pretty_os, + "SYSEXT_BUILD_ID", &sysext_build_id, + "SYSEXT_IMAGE_ID", &sysext_image_id, + "SYSEXT_IMAGE_VERSION", &sysext_image_version, "SYSEXT_SCOPE", &sysext_scope, "SYSEXT_LEVEL", &sysext_level, + "SYSEXT_PRETTY_NAME", &sysext_pretty_os, + "CONFEXT_ID", &confext_id, + "CONFEXT_VERSION_ID", &confext_version_id, + "CONFEXT_BUILD_ID", &confext_build_id, + "CONFEXT_IMAGE_ID", &confext_image_id, + "CONFEXT_IMAGE_VERSION", &confext_image_version, + "CONFEXT_SCOPE", &confext_scope, + "CONFEXT_LEVEL", &confext_level, + "CONFEXT_PRETTY_NAME", &confext_pretty_os, "ID", &id, "VERSION_ID", &version_id, "PORTABLE_PRETTY_NAME", &pretty_portable, @@ -418,18 +433,18 @@ static int inspect_image(int argc, char *argv[], void *userdata) { "\tPortable Prefixes:\n\t\t%s\n" "\tExtension Image:\n\t\t%s%s%s %s%s%s\n", name, - strna(sysext_scope), - strna(sysext_level), + strna(sysext_scope ?: confext_scope), + strna(sysext_level ?: confext_level), strna(id), strna(version_id), strna(pretty_portable), strna(portable_prefixes), - strempty(pretty_os), - pretty_os ? " (" : "ID: ", - strna(sysext_id ?: image_id), - pretty_os ? "" : "Version: ", - strna(sysext_version_id ?: image_version ?: build_id), - pretty_os ? ")" : ""); + strempty(sysext_pretty_os ?: confext_pretty_os), + (sysext_pretty_os ?: confext_pretty_os) ? " (" : "ID: ", + strna(sysext_id ?: sysext_image_id ?: confext_id ?: confext_image_id), + (sysext_pretty_os ?: confext_pretty_os) ? "" : "Version: ", + strna(sysext_version_id ?: sysext_image_version ?: sysext_build_id ?: confext_version_id ?: confext_image_version ?: confext_build_id), + (sysext_pretty_os ?: confext_pretty_os) ? ")" : ""); } r = sd_bus_message_exit_container(reply); @@ -871,7 +886,7 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) { return bus_log_create_error(r); if (STR_IN_SET(method, "AttachImageWithExtensions", "ReattachImageWithExtensions")) { - uint64_t flags = (arg_runtime ? PORTABLE_RUNTIME : 0) | (arg_force ? PORTABLE_FORCE_ATTACH | PORTABLE_FORCE_SYSEXT : 0); + uint64_t flags = (arg_runtime ? PORTABLE_RUNTIME : 0) | (arg_force ? PORTABLE_FORCE_ATTACH | PORTABLE_FORCE_EXTENSION : 0); r = sd_bus_message_append(m, "st", arg_copy_mode, flags); } else @@ -943,7 +958,7 @@ static int detach_image(int argc, char *argv[], void *userdata) { if (streq(method, "DetachImage")) r = sd_bus_message_append(m, "b", arg_runtime); else { - uint64_t flags = (arg_runtime ? PORTABLE_RUNTIME : 0) | (arg_force ? PORTABLE_FORCE_ATTACH | PORTABLE_FORCE_SYSEXT : 0); + uint64_t flags = (arg_runtime ? PORTABLE_RUNTIME : 0) | (arg_force ? PORTABLE_FORCE_ATTACH | PORTABLE_FORCE_EXTENSION : 0); r = sd_bus_message_append(m, "t", flags); } diff --git a/test/test-functions b/test/test-functions index b659c98529..e72ed57ca9 100644 --- a/test/test-functions +++ b/test/test-functions @@ -814,6 +814,15 @@ EOF echo MARKER=1 >"$initdir/usr/lib/systemd/system/some_file" mksquashfs "$initdir" "$oldinitdir/usr/share/app0.raw" -noappend + export initdir="$TESTDIR/conf0" + mkdir -p "$initdir/etc/extension-release.d" "$initdir/etc/systemd/system" "$initdir/opt" + grep "^ID=" "$os_release" >"$initdir/etc/extension-release.d/extension-release.conf0" + echo "${version_id}" >>"$initdir/etc/extension-release.d/extension-release.conf0" + ( echo "${version_id}" + echo "CONFEXT_IMAGE_ID=app" ) >>"$initdir/etc/extension-release.d/extension-release.conf0" + echo MARKER_1 >"$initdir/etc/systemd/system/some_file" + mksquashfs "$initdir" "$oldinitdir/usr/share/conf0.raw" -noappend + export initdir="$TESTDIR/app1" mkdir -p "$initdir/usr/lib/extension-release.d" "$initdir/usr/lib/systemd/system" "$initdir/opt" grep "^ID=" "$os_release" >"$initdir/usr/lib/extension-release.d/extension-release.app2" diff --git a/test/units/testsuite-29.sh b/test/units/testsuite-29.sh index 4bbbd38bee..66256a40c8 100755 --- a/test/units/testsuite-29.sh +++ b/test/units/testsuite-29.sh @@ -33,6 +33,7 @@ systemd-dissect --no-pager /usr/share/minimal_0.raw | grep -q '✓ portable serv systemd-dissect --no-pager /usr/share/minimal_1.raw | grep -q '✓ portable service' systemd-dissect --no-pager /usr/share/app0.raw | grep -q '✓ sysext for portable service' systemd-dissect --no-pager /usr/share/app1.raw | grep -q '✓ sysext for portable service' +systemd-dissect --no-pager /usr/share/conf0.raw | grep -q '✓ confext for portable service' export SYSTEMD_LOG_LEVEL=debug mkdir -p /run/systemd/system/systemd-portabled.service.d/ @@ -187,6 +188,17 @@ portablectl inspect --force --cat --extension /tmp/app10.raw /usr/share/minimal_ portablectl detach --force --now --runtime --extension /tmp/app10.raw /usr/share/minimal_0.raw app0 +# portablectl also accepts confexts +portablectl "${ARGS[@]}" attach --now --runtime --extension /usr/share/app0.raw --extension /usr/share/conf0.raw /usr/share/minimal_0.raw app0 + +systemctl is-active app0.service +status="$(portablectl is-attached --extension /usr/share/app0.raw --extension /usr/share/conf0.raw /usr/share/minimal_0.raw)" +[[ "${status}" == "running-runtime" ]] + +portablectl inspect --force --cat --extension /usr/share/app0.raw --extension /usr/share/conf0.raw /usr/share/minimal_0.raw app0 | grep -q -F "Extension Release: /usr/share/conf0.raw" + +portablectl detach --now --runtime --extension /usr/share/app0.raw --extension /usr/share/conf0.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