From e31e2b84cf5afff73b793dd335600265dca1ca7e Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 25 Jan 2022 15:49:22 +0000 Subject: [PATCH] portable: add flag to return extension-releases in GetImageMetadataWithExtensions Return the name of each extension and the associated extension-release file, and pretty-print them in 'portablectl inspect', if a new flag is passed. $ portablectl inspect --extension app2 --extension app0 minimal app0 app1 (Matching unit files with prefixes 'app0', 'app1'.) Image: /run/portables/minimal.raw Portable Service: n/a Operating System: Debian GNU/Linux 10 (buster) Extension: /run/portables/app2.raw Extension Scope: n/a Extension Compatibility Level: n/a Portable Service: n/a Portable Prefixes: n/a Operating System: n/a (debian 10) Extension: /run/portables/app0.raw Extension Scope: n/a Extension Compatibility Level: n/a Portable Service: n/a Portable Prefixes: n/a Operating System: n/a (debian 10) Unit files: app0.service (cherry picked from commit e3f7ed944ae750a40685c52349f3cc850db0876e) (cherry picked from commit a87fdd2af22128bce621508315ed5126a8d11f45) Conflict:NA Reference:https://github.com/systemd/systemd/commit/e31e2b84cf5afff73b793dd335600265dca1ca7e --- man/org.freedesktop.portable1.xml | 10 ++- src/portable/portable.c | 99 ++++++++++++++++++++---------- src/portable/portable.h | 15 ++--- src/portable/portablectl.c | 78 ++++++++++++++++++++++- src/portable/portabled-image-bus.c | 38 ++++++++++-- test/units/testsuite-29.sh | 3 + 6 files changed, 199 insertions(+), 44 deletions(-) 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 8c5e5b6821..23fe6bf926 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -533,13 +533,14 @@ 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, sd_bus_error *error) { _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_(image_unrefp) Image *image = NULL; Image *ext; @@ -561,6 +562,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; @@ -600,6 +607,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; r = portable_extract_by_path(ext->path, /* path_is_extension= */ true, matches, &extension_release_meta, &extra_unit_files, error); @@ -610,10 +618,15 @@ static int extract_image_and_extensions( if (r < 0) return r; - if (!validate_sysext) + if (!validate_sysext && !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; @@ -621,15 +634,28 @@ static int extract_image_and_extensions( if (r < 0) return r; - r = extension_release_validate(ext->path, id, version_id, sysext_level, extension_release); - 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) - return sd_bus_error_set_errnof(error, r, "Failed to compare image %s extension-release metadata with the root's os-release: %m", ext->path); + if (validate_sysext) { + r = extension_release_validate(ext->path, id, version_id, sysext_level, extension_release); + 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) + return sd_bus_error_set_errnof(error, r, "Failed to compare image %s extension-release metadata with the root's os-release: %m", ext->path); + } + + 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); + } } - *ret_image = TAKE_PTR(image); - *ret_extension_images = TAKE_PTR(extension_images); + if (ret_image) + *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) @@ -643,24 +669,29 @@ int portable_extract( char **matches, char **extension_image_paths, PortableMetadata **ret_os_release, + OrderedHashmap **ret_extension_releases, Hashmap **ret_unit_files, sd_bus_error *error) { _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL; - _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL; + _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL; _cleanup_hashmap_free_ Hashmap *unit_files = NULL; _cleanup_(image_unrefp) Image *image = NULL; int r; - r = extract_image_and_extensions(name_or_path, - matches, - extension_image_paths, - /* validate_sysext= */ false, - &image, - &extension_images, - &os_release, - &unit_files, - error); + assert(name_or_path); + + r = extract_image_and_extensions( + name_or_path, + matches, + extension_image_paths, + /* validate_sysext= */ false, + &image, + &extension_images, + &extension_releases, + &os_release, + &unit_files, + error); if (r < 0) return r; @@ -677,8 +708,12 @@ int portable_extract( isempty(extensions) ? "" : extensions); } - *ret_os_release = TAKE_PTR(os_release); - *ret_unit_files = TAKE_PTR(unit_files); + 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); return 0; } @@ -1225,15 +1260,17 @@ int portable_attach( PortableMetadata *item; int r; - r = extract_image_and_extensions(name_or_path, - matches, - extension_image_paths, - /* validate_sysext= */ true, - &image, - &extension_images, - /* os_release= */ NULL, - &unit_files, - error); + r = extract_image_and_extensions( + name_or_path, + matches, + extension_image_paths, + /* validate_sysext= */ true, + &image, + &extension_images, + /* extension_releases= */ NULL, + /* os_release= */ NULL, + &unit_files, + error); if (r < 0) return r; diff --git a/src/portable/portable.h b/src/portable/portable.h index 94144287ae..ce55f050a2 100644 --- a/src/portable/portable.h +++ b/src/portable/portable.h @@ -20,13 +20,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 @@ -64,7 +65,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, 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, 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 2d8079ad97..af5e78c998 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -259,8 +259,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); @@ -365,6 +365,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; @@ -699,6 +767,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 23c6e2633a..3e6310f23e 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, error); if (r < 0) @@ -186,6 +190,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 34fa730514..ca09f321b7 100755 --- a/test/units/testsuite-29.sh +++ b/test/units/testsuite-29.sh @@ -80,6 +80,9 @@ systemctl is-active app1.service portablectl "${ARGS[@]}" reattach --now --runtime --extension ${app1} ${root} app1 systemctl is-active app1.service +portablectl inspect --cat --extension ${app1} ${root} app1 | grep -F "MARKER=1" +portablectl inspect --cat --extension ${app1} ${root} app1 | grep -F "Extension Release: /usr/share/app1.raw" +portablectl inspect --cat --extension ${app1} ${root} app1 | grep -F "ExecStart=/opt/script1.sh" portablectl detach --now --runtime --extension ${app1} ${root} app1 -- 2.33.0