diff --git a/man/org.freedesktop.portable1.xml b/man/org.freedesktop.portable1.xml index 53c960206e..053f2a5434 100644 --- a/man/org.freedesktop.portable1.xml +++ b/man/org.freedesktop.portable1.xml @@ -187,7 +187,15 @@ node /org/freedesktop/portable1 { This method is a superset of GetImageMetadata() with the addition of a list of extensions as input parameter, which were overlaid on top of the main image via AttachImageWithExtensions(). - The flag parameter is currently unused and reserved for future purposes. + The flag parameter can be used to request that, before the units, the path of + each extension and an array of bytes with the content of the respective extension-release file + are sent. One such structure will be sent for each extension named in the input arguments. The + flag value to enable this functionality is defined as follows: + + +#define PORTABLE_INSPECT_EXTENSION_RELEASES (UINT64_C(1) << 1) + + GetImageState() retrieves the image state as one of the following strings: diff --git a/src/portable/portable.c b/src/portable/portable.c index be311f94c4..0e6461ba93 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -505,6 +505,7 @@ static int extract_image_and_extensions( bool validate_sysext, Image **ret_image, OrderedHashmap **ret_extension_images, + OrderedHashmap **ret_extension_releases, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, char ***ret_valid_prefixes, @@ -512,7 +513,7 @@ static int extract_image_and_extensions( _cleanup_free_ char *id = NULL, *version_id = NULL, *sysext_level = NULL; _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL; - _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL; + _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL; _cleanup_hashmap_free_ Hashmap *unit_files = NULL; _cleanup_strv_free_ char **valid_prefixes = NULL; _cleanup_(image_unrefp) Image *image = NULL; @@ -533,6 +534,12 @@ static int extract_image_and_extensions( if (!extension_images) return -ENOMEM; + if (ret_extension_releases) { + extension_releases = ordered_hashmap_new(&portable_metadata_hash_ops); + if (!extension_releases) + return -ENOMEM; + } + STRV_FOREACH(p, extension_image_paths) { _cleanup_(image_unrefp) Image *new = NULL; @@ -581,6 +588,7 @@ static int extract_image_and_extensions( _cleanup_(portable_metadata_unrefp) PortableMetadata *extension_release_meta = NULL; _cleanup_hashmap_free_ Hashmap *extra_unit_files = NULL; _cleanup_strv_free_ char **extension_release = NULL; + _cleanup_close_ int extension_release_fd = -1; _cleanup_fclose_ FILE *f = NULL; const char *e; @@ -592,10 +600,15 @@ static int extract_image_and_extensions( if (r < 0) return r; - if (!validate_sysext && !ret_valid_prefixes) + if (!validate_sysext && !ret_valid_prefixes && !ret_extension_releases) continue; - r = take_fdopen_unlocked(&extension_release_meta->fd, "r", &f); + /* We need to keep the fd valid, to return the PortableMetadata to the caller. */ + extension_release_fd = fd_reopen(extension_release_meta->fd, O_CLOEXEC); + if (extension_release_fd < 0) + return extension_release_fd; + + r = take_fdopen_unlocked(&extension_release_fd, "r", &f); if (r < 0) return r; @@ -623,6 +636,13 @@ static int extract_image_and_extensions( if (r < 0) return r; } + + if (ret_extension_releases) { + r = ordered_hashmap_put(extension_releases, ext->name, extension_release_meta); + if (r < 0) + return r; + TAKE_PTR(extension_release_meta); + } } strv_sort(valid_prefixes); @@ -631,6 +651,8 @@ static int extract_image_and_extensions( *ret_image = TAKE_PTR(image); if (ret_extension_images) *ret_extension_images = TAKE_PTR(extension_images); + if (ret_extension_releases) + *ret_extension_releases = TAKE_PTR(extension_releases); if (ret_os_release) *ret_os_release = TAKE_PTR(os_release); if (ret_unit_files) @@ -646,12 +668,13 @@ int portable_extract( char **matches, char **extension_image_paths, PortableMetadata **ret_os_release, + OrderedHashmap **ret_extension_releases, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error) { _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL; - _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL; + _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL; _cleanup_hashmap_free_ Hashmap *unit_files = NULL; _cleanup_(strv_freep) char **valid_prefixes = NULL; _cleanup_(image_unrefp) Image *image = NULL; @@ -666,6 +689,7 @@ int portable_extract( /* validate_sysext= */ false, &image, &extension_images, + &extension_releases, &os_release, &unit_files, ret_valid_prefixes ? &valid_prefixes : NULL, @@ -688,6 +712,8 @@ int portable_extract( if (ret_os_release) *ret_os_release = TAKE_PTR(os_release); + if (ret_extension_releases) + *ret_extension_releases = TAKE_PTR(extension_releases); if (ret_unit_files) *ret_unit_files = TAKE_PTR(unit_files); if (ret_valid_prefixes) @@ -1261,6 +1287,7 @@ int portable_attach( /* validate_sysext= */ true, &image, &extension_images, + /* extension_releases= */ NULL, /* os_release= */ NULL, &unit_files, &valid_prefixes, diff --git a/src/portable/portable.h b/src/portable/portable.h index 2837e8b286..a0704f971b 100644 --- a/src/portable/portable.h +++ b/src/portable/portable.h @@ -21,13 +21,14 @@ typedef struct PortableMetadata { #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_PREFER_COPY = 1 << 1, - PORTABLE_PREFER_SYMLINK = 1 << 2, - PORTABLE_REATTACH = 1 << 3, - _PORTABLE_MASK_PUBLIC = PORTABLE_RUNTIME, + PORTABLE_RUNTIME = 1 << 0, + PORTABLE_INSPECT_EXTENSION_RELEASES = 1 << 1, /* Public API via DBUS, do not change */ + PORTABLE_PREFER_COPY = 1 << 2, + PORTABLE_PREFER_SYMLINK = 1 << 3, + PORTABLE_REATTACH = 1 << 4, + _PORTABLE_MASK_PUBLIC = PORTABLE_RUNTIME | PORTABLE_INSPECT_EXTENSION_RELEASES, _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 @@ -65,7 +66,7 @@ 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, char **extension_image_paths, PortableMetadata **ret_os_release, Hashmap **ret_unit_files, char ***ret_valid_prefixes, sd_bus_error *error); +int portable_extract(const char *image, char **matches, char **extension_image_paths, PortableMetadata **ret_os_release, OrderedHashmap **ret_extension_releases, Hashmap **ret_unit_files, char ***ret_valid_prefixes, 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); diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 60feac6f5d..33d7162be9 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -260,8 +260,8 @@ 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; + PortableFlags flags = PORTABLE_INSPECT_EXTENSION_RELEASES; const char *method; - uint64_t flags = 0; int r; assert(bus); @@ -366,6 +366,74 @@ static int inspect_image(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_parse_error(r); + /* If we specified any extensions, we'll first get back exactly the + * paths (and extension-release content) for each one of the arguments. */ + for (size_t i = 0; i < strv_length(arg_extension_images); ++i) { + const char *name; + + r = sd_bus_message_enter_container(reply, 'e', "say"); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + r = sd_bus_message_read(reply, "s", &name); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &data, &sz); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_cat) { + if (nl) + fputc('\n', stdout); + + printf("%s-- Extension Release: %s --%s\n", ansi_highlight(), name, ansi_normal()); + fwrite(data, sz, 1, stdout); + fflush(stdout); + nl = true; + } else { + _cleanup_free_ char *pretty_portable = NULL, *pretty_os = NULL, *sysext_level = NULL, + *id = NULL, *version_id = NULL, *sysext_scope = NULL, *portable_prefixes = NULL; + _cleanup_fclose_ FILE *f = NULL; + + f = fmemopen_unlocked((void*) data, sz, "re"); + if (!f) + return log_error_errno(errno, "Failed to open extension-release buffer: %m"); + + r = parse_env_file(f, name, + "ID", &id, + "VERSION_ID", &version_id, + "SYSEXT_SCOPE", &sysext_scope, + "SYSEXT_LEVEL", &sysext_level, + "PORTABLE_PRETTY_NAME", &pretty_portable, + "PORTABLE_PREFIXES", &portable_prefixes, + "PRETTY_NAME", &pretty_os); + if (r < 0) + return log_error_errno(r, "Failed to parse extension release from '%s': %m", name); + + printf("Extension:\n\t%s\n" + "\tExtension Scope:\n\t\t%s\n" + "\tExtension Compatibility Level:\n\t\t%s\n" + "\tPortable Service:\n\t\t%s\n" + "\tPortable Prefixes:\n\t\t%s\n" + "\tOperating System:\n\t\t%s (%s %s)\n", + name, + strna(sysext_scope), + strna(sysext_level), + strna(pretty_portable), + strna(portable_prefixes), + strna(pretty_os), + strna(id), + strna(version_id)); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + for (;;) { const char *name; @@ -700,6 +768,14 @@ static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) { if (r < 0) return bus_log_parse_error(r); + /* If we specified any extensions, we'll first get back exactly the + * paths (and extension-release content) for each one of the arguments. */ + for (size_t i = 0; i < strv_length(arg_extension_images); ++i) { + r = sd_bus_message_skip(reply, "{say}"); + if (r < 0) + return bus_log_parse_error(r); + } + for (;;) { const char *name; diff --git a/src/portable/portabled-image-bus.c b/src/portable/portabled-image-bus.c index ede062dbfb..af21fde96f 100644 --- a/src/portable/portabled-image-bus.c +++ b/src/portable/portabled-image-bus.c @@ -102,13 +102,13 @@ int bus_image_common_get_metadata( Image *image, sd_bus_error *error) { + _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_releases = NULL; _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; - /* Unused for now, but added to the DBUS methods for future-proofing */ - uint64_t input_flags = 0; + PortableFlags flags = 0; size_t i; int r; @@ -133,14 +133,17 @@ int bus_image_common_get_metadata( if (sd_bus_message_is_method_call(message, NULL, "GetImageMetadataWithExtensions") || sd_bus_message_is_method_call(message, NULL, "GetMetadataWithExtensions")) { + uint64_t input_flags = 0; + 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) + + 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; } r = bus_image_acquire(m, @@ -161,6 +164,7 @@ int bus_image_common_get_metadata( matches, extension_images, &os_release, + &extension_releases, &unit_files, NULL, error); @@ -187,6 +191,32 @@ int bus_image_common_get_metadata( if (r < 0) return r; + /* If it was requested, also send back the extension path and the content + * of each extension-release file. Behind a flag, as it's an incompatible + * change. */ + if (FLAGS_SET(flags, PORTABLE_INSPECT_EXTENSION_RELEASES)) { + PortableMetadata *extension_release; + + ORDERED_HASHMAP_FOREACH(extension_release, extension_releases) { + + r = sd_bus_message_open_container(reply, 'e', "say"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", extension_release->image_path); + if (r < 0) + return r; + + r = append_fd(reply, extension_release); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + } + for (i = 0; i < hashmap_size(unit_files); i++) { r = sd_bus_message_open_container(reply, 'e', "say"); diff --git a/test/units/testsuite-29.sh b/test/units/testsuite-29.sh index 13bdc59e3b..94771b49a7 100755 --- a/test/units/testsuite-29.sh +++ b/test/units/testsuite-29.sh @@ -102,6 +102,10 @@ portablectl "${ARGS[@]}" reattach --now --runtime --extension /usr/share/app1.ra systemctl is-active app1.service +portablectl inspect --cat --extension /usr/share/app1.raw /usr/share/minimal_1.raw app1 | grep -F "MARKER=2" +portablectl inspect --cat --extension /usr/share/app1.raw /usr/share/minimal_1.raw app1 | grep -F "PORTABLE_PREFIXES=app1" +portablectl inspect --cat --extension /usr/share/app1.raw /usr/share/minimal_1.raw app1 | grep -F "ExecStart=/opt/script1.sh" + portablectl detach --now --runtime --extension /usr/share/app1.raw /usr/share/minimal_1.raw app1 # Ensure that the combination of read-only images, state directory and dynamic user works, and that