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/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); 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/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 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 <