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 <