]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
portablectl: retry inspect with PORTABLE_PREFIXES
authordongshengyuan <545258830@qq.com>
Thu, 2 Jul 2026 08:09:21 +0000 (16:09 +0800)
committerLennart Poettering <lennart@poettering.net>
Tue, 14 Jul 2026 09:26:40 +0000 (11:26 +0200)
When no prefix is specified, portablectl inspect first tries the
prefix derived from the image name. This keeps inspect aligned with
attach behavior.

If that lookup finds no matching units, retry with validated
PORTABLE_PREFIXES read from the image os-release. This makes inspect
work for images whose filename does not match their portable service
prefix.

Keep metadata error handling explicit so request-construction failures
are not logged twice, while sd_bus_call() failures still include the
inspect context.

Add a TEST-29-PORTABLE regression case for a directory image whose
name does not match the portable service prefix.

Fixes #37296.

Signed-off-by: dongshengyuan <dongshengyuan@uniontech.com>
src/libsystemd/sd-bus/bus-common-errors.c
src/libsystemd/sd-bus/bus-common-errors.h
src/portable/portable.c
src/portable/portablectl.c
test/units/TEST-29-PORTABLE.directory.sh

index 6c2b0fd8814b12856d28d543a1a1171831d453b5..63dfa186266410233aeac42601c65bc89a38b4ff 100644 (file)
@@ -48,6 +48,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
 
         SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_PORTABLE_IMAGE,       ENOENT),
         SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PORTABLE_IMAGE_TYPE,      EMEDIUMTYPE),
+        SD_BUS_ERROR_MAP(BUS_ERROR_NO_MATCHING_UNIT_FILES,       ENOENT),
 
         SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SESSION,              ENXIO),
         SD_BUS_ERROR_MAP(BUS_ERROR_NO_SESSION_FOR_PID,           ENXIO),
index b4788433bfce8f14c9531f7e35b889fae1968ea5..2c2a57eae527e90b7549fa6760d96d271e9bfdb5 100644 (file)
@@ -47,6 +47,7 @@
 
 #define BUS_ERROR_NO_SUCH_PORTABLE_IMAGE       "org.freedesktop.portable1.NoSuchImage"
 #define BUS_ERROR_BAD_PORTABLE_IMAGE_TYPE      "org.freedesktop.portable1.BadImageType"
+#define BUS_ERROR_NO_MATCHING_UNIT_FILES       "org.freedesktop.portable1.NoMatchingUnitFiles"
 
 #define BUS_ERROR_NO_SUCH_SESSION              "org.freedesktop.login1.NoSuchSession"
 #define BUS_ERROR_NO_SESSION_FOR_PID           "org.freedesktop.login1.NoSessionForPID"
index 6779ddd5514f4dea87727d63dc89f3401df385e1..d49c8a76b5e3c65a91b6ee9052e5fe0f68ae93da 100644 (file)
@@ -1098,7 +1098,7 @@ int portable_extract(
                         return -ENOMEM;
 
                 return sd_bus_error_setf(error,
-                                         SD_BUS_ERROR_INVALID_ARGS,
+                                         BUS_ERROR_NO_MATCHING_UNIT_FILES,
                                          "Couldn't find any matching unit files in image '%s%s%s', refusing.",
                                          image->path,
                                          isempty(extensions) ? "" : "' or any of its extensions '",
@@ -2102,7 +2102,7 @@ int portable_attach(
 
                 return sd_bus_error_setf(
                                 error,
-                                SD_BUS_ERROR_INVALID_ARGS,
+                                BUS_ERROR_NO_MATCHING_UNIT_FILES,
                                 "Couldn't find any matching unit files in image '%s%s%s', refusing.",
                                 image->path,
                                 isempty(extensions_joined) ? "" : "' or any of its extensions '",
index 831b454372275500fff5414069f9b16cf171f99b..c6eed7ae0f0448faa2f99fe82bdcf3265201a023 100644 (file)
@@ -5,6 +5,7 @@
 #include "alloc-util.h"
 #include "ansi-color.h"
 #include "build.h"
+#include "bus-common-errors.h"
 #include "bus-error.h"
 #include "bus-locator.h"
 #include "bus-unit-util.h"
@@ -169,6 +170,27 @@ static int extract_prefix(const char *path, char **ret) {
         return 0;
 }
 
+static int log_unit_file_matches(char **matches) {
+        if (arg_quiet)
+                return 0;
+
+        if (strv_isempty(matches))
+                log_info("(Matching all unit files.)");
+        else if (strv_length(matches) == 1)
+                log_info("(Matching unit files with prefix '%s'.)", matches[0]);
+        else {
+                _cleanup_free_ char *joined = NULL;
+
+                joined = strv_join(matches, "', '");
+                if (!joined)
+                        return log_oom();
+
+                log_info("(Matching unit files with prefixes '%s'.)", joined);
+        }
+
+        return 0;
+}
+
 static int determine_matches(const char *image, char **l, bool allow_any, char ***ret) {
         _cleanup_strv_free_ char **k = NULL;
         int r;
@@ -184,9 +206,6 @@ static int determine_matches(const char *image, char **l, bool allow_any, char *
                 if (r < 0)
                         return log_error_errno(r, "Failed to extract prefix of image name '%s': %m", image);
 
-                if (!arg_quiet)
-                        log_info("(Matching unit files with prefix '%s'.)", prefix);
-
                 r = strv_consume(&k, prefix);
                 if (r < 0)
                         return log_oom();
@@ -197,25 +216,17 @@ static int determine_matches(const char *image, char **l, bool allow_any, char *
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                "Refusing all unit file match.");
 
-                if (!arg_quiet)
-                        log_info("(Matching all unit files.)");
         } else {
 
                 k = strv_copy(l);
                 if (!k)
                         return log_oom();
-
-                if (!arg_quiet) {
-                        _cleanup_free_ char *joined = NULL;
-
-                        joined = strv_join(k, "', '");
-                        if (!joined)
-                                return log_oom();
-
-                        log_info("(Matching unit files with prefixes '%s'.)", joined);
-                }
         }
 
+        r = log_unit_file_matches(k);
+        if (r < 0)
+                return r;
+
         *ret = TAKE_PTR(k);
 
         return 0;
@@ -329,15 +340,78 @@ static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userd
         return 0;
 }
 
-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;
+static int determine_matches_from_os_release(sd_bus *bus, const char *image, char ***ret) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_strv_free_ char **matches = NULL;
+        int r;
+
+        assert(bus);
+        assert(image);
+        assert(ret);
+
+        r = bus_call_method(bus, bus_portable_mgr, "GetImageOSRelease", &error, &reply, "s", image);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to inspect image os-release: %s", bus_error_message(&error, r));
+                *ret = NULL;
+                return 0;
+        }
+
+        r = sd_bus_message_enter_container(reply, 'a', "{ss}");
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        for (;;) {
+                _cleanup_strv_free_ char **split = NULL;
+                const char *key, *value;
+
+                r = sd_bus_message_read(reply, "{ss}", &key, &value);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+                if (r == 0)
+                        break;
+
+                if (!streq(key, "PORTABLE_PREFIXES"))
+                        continue;
+
+                split = strv_split(value, WHITESPACE);
+                if (!split)
+                        return log_oom();
+
+                STRV_FOREACH(prefix, split)
+                        if (!string_is_safe(*prefix, STRING_FILENAME_PART))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Invalid PORTABLE_PREFIXES= entry in image os-release, refusing.");
+
+                strv_free_and_replace(matches, split);
+        }
+
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        if (strv_isempty(matches)) {
+                *ret = NULL;
+                return 0;
+        }
+
+        *ret = TAKE_PTR(matches);
+        return 0;
+}
+
+static int make_get_image_metadata_message(
+                sd_bus *bus,
+                const char *image,
+                char **matches,
+                sd_bus_message **ret) {
+
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
         uint64_t flags = arg_force ? PORTABLE_FORCE_EXTENSION : 0;
         const char *method;
         int r;
 
         assert(bus);
-        assert(reply);
+        assert(ret);
 
         method = strv_isempty(arg_extension_images) && !arg_force ? "GetImageMetadata" : "GetImageMetadataWithExtensions";
 
@@ -363,19 +437,42 @@ static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd
                         return bus_log_create_error(r);
         }
 
+        *ret = TAKE_PTR(m);
+        return 0;
+}
+
+static int log_image_metadata_error(int r, const sd_bus_error *error) {
+        return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(error, r));
+}
+
+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;
+        int r;
+
+        r = make_get_image_metadata_message(bus, image, matches, &m);
+        if (r < 0)
+                return r;
+
         r = sd_bus_call(bus, m, 0, &error, reply);
         if (r < 0)
-                return log_error_errno(r, "Failed to inspect image metadata: %s", bus_error_message(&error, r));
+                return log_image_metadata_error(r, &error);
 
         return 0;
 }
 
+static bool image_metadata_error_is_no_match(const sd_bus_error *error) {
+        return sd_bus_error_has_name(error, BUS_ERROR_NO_MATCHING_UNIT_FILES);
+}
+
 VERB(verb_inspect_image, "inspect", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0,
      "Show details of specified portable service image");
 static int verb_inspect_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        _cleanup_strv_free_ char **matches = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_strv_free_ char **matches = NULL, **fallback_matches = NULL;
         _cleanup_free_ char *image = NULL;
         bool nl = false, header = false;
         const char *path;
@@ -387,18 +484,44 @@ static int verb_inspect_image(int argc, char *argv[], uintptr_t _data, void *use
         if (r < 0)
                 return r;
 
-        r = determine_matches(argv[1], argv + 2, true, &matches);
+        r = acquire_bus(&bus);
         if (r < 0)
                 return r;
 
-        r = acquire_bus(&bus);
+        r = determine_matches(argv[1], argv + 2, true, &matches);
         if (r < 0)
                 return r;
 
-        r = get_image_metadata(bus, image, matches, &reply);
+        r = make_get_image_metadata_message(bus, image, matches, &m);
         if (r < 0)
                 return r;
 
+        r = sd_bus_call(bus, m, 0, &error, &reply);
+        if (r < 0) {
+                int first_error = r;
+
+                if (!strv_isempty(argv + 2) || !image_metadata_error_is_no_match(&error))
+                        return log_image_metadata_error(r, &error);
+
+                r = determine_matches_from_os_release(bus, image, &fallback_matches);
+                if (r < 0)
+                        return r;
+
+                if (strv_isempty(fallback_matches))
+                        return log_image_metadata_error(first_error, &error);
+
+                if (!arg_quiet)
+                        log_info("(No matching unit files found with the image name prefix, retrying with PORTABLE_PREFIXES= from os-release.)");
+
+                r = log_unit_file_matches(fallback_matches);
+                if (r < 0)
+                        return r;
+
+                r = get_image_metadata(bus, image, fallback_matches, &reply);
+                if (r < 0)
+                        return r;
+        }
+
         r = sd_bus_message_read(reply, "s", &path);
         if (r < 0)
                 return bus_log_parse_error(r);
index 42f280b78ad2cc4c22637b772888b6aba3255468..e6aafcb6af0bc2a1b8ec7be3ef1e7e8c24e4b95a 100755 (executable)
@@ -20,6 +20,10 @@ fi
 unsquashfs -force -no-xattrs -d /tmp/minimal_0 /usr/share/minimal_0.raw
 unsquashfs -force -no-xattrs -d /tmp/minimal_1 /usr/share/minimal_1.raw
 
+rm -rf /tmp/mismatched-name
+cp -a /tmp/minimal_0 /tmp/mismatched-name
+portablectl inspect /tmp/mismatched-name | grep -F "minimal-app0.service" >/dev/null
+
 portablectl "${ARGS[@]}" attach --copy=symlink --now --runtime /tmp/minimal_0 minimal-app0
 
 systemctl is-active minimal-app0.service