]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/portable/portabled-image-bus.c
tree-wide: use ASSERT_PTR more
[thirdparty/systemd.git] / src / portable / portabled-image-bus.c
index 76b6ddebde374068beb41508f0b79ffd67e79abf..7d393476a0f5d9a981afdd6c0b3668683b42a479 100644 (file)
@@ -9,13 +9,15 @@
 #include "bus-common-errors.h"
 #include "bus-get-properties.h"
 #include "bus-label.h"
+#include "bus-object.h"
 #include "bus-polkit.h"
 #include "bus-util.h"
+#include "discover-image.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "io-util.h"
-#include "machine-image.h"
 #include "missing_capability.h"
+#include "os-util.h"
 #include "portable.h"
 #include "portabled-bus.h"
 #include "portabled-image-bus.h"
@@ -73,20 +75,22 @@ static int bus_image_method_get_os_release(sd_bus_message *message, void *userda
 static int append_fd(sd_bus_message *m, PortableMetadata *d) {
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *buf = NULL;
-        size_t n;
+        size_t n = 0;
         int r;
 
         assert(m);
-        assert(d);
-        assert(d->fd >= 0);
 
-        f = take_fdopen(&d->fd, "r");
-        if (!f)
-                return -errno;
+        if (d) {
+                assert(d->fd >= 0);
 
-        r = read_full_stream(f, &buf, &n);
-        if (r < 0)
-                return r;
+                f = take_fdopen(&d->fd, "r");
+                if (!f)
+                        return -errno;
+
+                r = read_full_stream(f, &buf, &n);
+                if (r < 0)
+                        return r;
+        }
 
         return sd_bus_message_append_array(m, 'y', buf, n);
 }
@@ -98,12 +102,12 @@ 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;
-        _cleanup_strv_free_ char **matches = NULL;
-        size_t i;
         int r;
 
         assert(name_or_path || image);
@@ -114,10 +118,32 @@ int bus_image_common_get_metadata(
                 m = image->userdata;
         }
 
+        bool have_exti = sd_bus_message_is_method_call(message, NULL, "GetImageMetadataWithExtensions") ||
+                         sd_bus_message_is_method_call(message, NULL, "GetMetadataWithExtensions");
+
+        if (have_exti) {
+                r = sd_bus_message_read_strv(message, &extension_images);
+                if (r < 0)
+                        return r;
+        }
+
         r = sd_bus_message_read_strv(message, &matches);
         if (r < 0)
                 return r;
 
+        if (have_exti) {
+                uint64_t input_flags = 0;
+
+                r = sd_bus_message_read(message, "t", &input_flags);
+                if (r < 0)
+                        return r;
+
+                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);
+        }
+
         r = bus_image_acquire(m,
                               message,
                               name_or_path,
@@ -134,8 +160,11 @@ int bus_image_common_get_metadata(
         r = portable_extract(
                         image->path,
                         matches,
+                        extension_images,
                         &os_release,
+                        &extension_releases,
                         &unit_files,
+                        NULL,
                         error);
         if (r < 0)
                 return r;
@@ -156,11 +185,45 @@ 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 (have_exti) {
+                PortableMetadata *extension_release;
+
+                r = sd_bus_message_open_container(reply, 'a', "{say}");
+                if (r < 0)
+                        return r;
+
+                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;
+                }
+
+                r = sd_bus_message_close_container(reply);
+                if (r < 0)
+                        return r;
+        }
+
         r = sd_bus_message_open_container(reply, 'a', "{say}");
         if (r < 0)
                 return r;
 
-        for (i = 0; i < hashmap_size(unit_files); i++) {
+        for (size_t i = 0; i < hashmap_size(unit_files); i++) {
 
                 r = sd_bus_message_open_container(reply, 'e', "say");
                 if (r < 0)
@@ -195,16 +258,35 @@ static int bus_image_method_get_state(
                 void *userdata,
                 sd_bus_error *error) {
 
-        Image *image = userdata;
+        _cleanup_strv_free_ char **extension_images = NULL;
+        Image *image = ASSERT_PTR(userdata);
         PortableState state;
         int r;
 
         assert(message);
-        assert(image);
+
+        if (sd_bus_message_is_method_call(message, NULL, "GetStateWithExtensions")) {
+                uint64_t input_flags = 0;
+
+                r = sd_bus_message_read_strv(message, &extension_images);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_read(message, "t", &input_flags);
+                if (r < 0)
+                        return r;
+
+                /* No flags are supported by this method for now. */
+                if (input_flags != 0)
+                        return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS,
+                                                          "Invalid 'flags' parameter '%" PRIu64 "'",
+                                                          input_flags);
+        }
 
         r = portable_get_state(
                         sd_bus_message_get_bus(message),
                         image->path,
+                        extension_images,
                         0,
                         &state,
                         error);
@@ -221,12 +303,12 @@ int bus_image_common_attach(
                 Image *image,
                 sd_bus_error *error) {
 
-        _cleanup_strv_free_ char **matches = NULL;
+        _cleanup_strv_free_ char **matches = NULL, **extension_images = NULL;
         PortableChange *changes = NULL;
         PortableFlags flags = 0;
         const char *profile, *copy_mode;
         size_t n_changes = 0;
-        int runtime, r;
+        int r;
 
         assert(message);
         assert(name_or_path || image);
@@ -236,14 +318,44 @@ int bus_image_common_attach(
                 m = image->userdata;
         }
 
+        if (sd_bus_message_is_method_call(message, NULL, "AttachImageWithExtensions") ||
+            sd_bus_message_is_method_call(message, NULL, "AttachWithExtensions")) {
+                r = sd_bus_message_read_strv(message, &extension_images);
+                if (r < 0)
+                        return r;
+        }
+
         r = sd_bus_message_read_strv(message, &matches);
         if (r < 0)
                 return r;
 
-        r = sd_bus_message_read(message, "sbs", &profile, &runtime, &copy_mode);
+        r = sd_bus_message_read(message, "s", &profile);
         if (r < 0)
                 return r;
 
+        if (sd_bus_message_is_method_call(message, NULL, "AttachImageWithExtensions") ||
+            sd_bus_message_is_method_call(message, NULL, "AttachWithExtensions")) {
+                uint64_t input_flags = 0;
+
+                r = sd_bus_message_read(message, "st", &copy_mode, &input_flags);
+                if (r < 0)
+                        return r;
+                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;
+        } else {
+                int runtime;
+
+                r = sd_bus_message_read(message, "bs", &runtime, &copy_mode);
+                if (r < 0)
+                        return r;
+
+                if (runtime)
+                        flags |= PORTABLE_RUNTIME;
+        }
+
         if (streq(copy_mode, "symlink"))
                 flags |= PORTABLE_PREFER_SYMLINK;
         else if (streq(copy_mode, "copy"))
@@ -251,9 +363,6 @@ int bus_image_common_attach(
         else if (!isempty(copy_mode))
                 return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode);
 
-        if (runtime)
-                flags |= PORTABLE_RUNTIME;
-
         r = bus_image_acquire(m,
                               message,
                               name_or_path,
@@ -272,6 +381,7 @@ int bus_image_common_attach(
                         image->path,
                         matches,
                         profile,
+                        extension_images,
                         flags,
                         &changes,
                         &n_changes,
@@ -295,19 +405,44 @@ static int bus_image_method_detach(
                 void *userdata,
                 sd_bus_error *error) {
 
+        _cleanup_strv_free_ char **extension_images = NULL;
         PortableChange *changes = NULL;
-        Image *image = userdata;
-        Manager *m = image->userdata;
+        Image *image = ASSERT_PTR(userdata);
+        Manager *m = ASSERT_PTR(image->userdata);
+        PortableFlags flags = 0;
         size_t n_changes = 0;
-        int r, runtime;
+        int r;
 
         assert(message);
-        assert(image);
-        assert(m);
 
-        r = sd_bus_message_read(message, "b", &runtime);
-        if (r < 0)
-                return r;
+        if (sd_bus_message_is_method_call(message, NULL, "DetachWithExtensions")) {
+                r = sd_bus_message_read_strv(message, &extension_images);
+                if (r < 0)
+                        return r;
+        }
+
+        if (sd_bus_message_is_method_call(message, NULL, "DetachWithExtensions")) {
+                uint64_t input_flags = 0;
+
+                r = sd_bus_message_read(message, "t", &input_flags);
+                if (r < 0)
+                        return r;
+
+                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;
+        } else {
+                int runtime;
+
+                r = sd_bus_message_read(message, "b", &runtime);
+                if (r < 0)
+                        return r;
+
+                if (runtime)
+                        flags |= PORTABLE_RUNTIME;
+        }
 
         r = bus_verify_polkit_async(
                         message,
@@ -326,7 +461,8 @@ static int bus_image_method_detach(
         r = portable_detach(
                         sd_bus_message_get_bus(message),
                         image->path,
-                        runtime ? PORTABLE_RUNTIME : 0,
+                        extension_images,
+                        flags,
                         &changes,
                         &n_changes,
                         error);
@@ -361,7 +497,7 @@ int bus_image_common_remove(
         }
 
         if (m->n_operations >= OPERATIONS_MAX)
-                return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations.");
+                return sd_bus_error_set(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations.");
 
         r = bus_image_acquire(m,
                               message,
@@ -379,6 +515,7 @@ int bus_image_common_remove(
         r = portable_get_state(
                         sd_bus_message_get_bus(message),
                         image->path,
+                        NULL,
                         0,
                         &state,
                         error);
@@ -422,6 +559,216 @@ static int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_b
         return bus_image_common_remove(NULL, message, NULL, userdata, error);
 }
 
+/* Given two PortableChange arrays, return a new array that has all elements of the first that are
+ * not also present in the second, comparing the basename of the path values. */
+static int normalize_portable_changes(
+                const PortableChange *changes_attached,
+                size_t n_changes_attached,
+                const PortableChange *changes_detached,
+                size_t n_changes_detached,
+                PortableChange **ret_changes,
+                size_t *ret_n_changes) {
+
+        PortableChange *changes = NULL;
+        size_t n_changes = 0;
+        int r = 0;
+
+        assert(ret_n_changes);
+        assert(ret_changes);
+
+        if (n_changes_detached == 0)
+                return 0; /* Nothing to do */
+
+        changes = new0(PortableChange, n_changes_attached + n_changes_detached);
+        if (!changes)
+                return -ENOMEM;
+
+        /* Corner case: only detached, nothing attached */
+        if (n_changes_attached == 0) {
+                memcpy(changes, changes_detached, sizeof(PortableChange) * n_changes_detached);
+                *ret_changes = TAKE_PTR(changes);
+                *ret_n_changes = n_changes_detached;
+
+                return 0;
+        }
+
+        for (size_t i = 0; i < n_changes_detached; ++i) {
+                bool found = false;
+
+                for (size_t j = 0; j < n_changes_attached; ++j)
+                        if (streq(basename(changes_detached[i].path), basename(changes_attached[j].path))) {
+                                found = true;
+                                break;
+                        }
+
+                if (!found) {
+                        _cleanup_free_ char *path = NULL, *source = NULL;
+
+                        path = strdup(changes_detached[i].path);
+                        if (!path) {
+                                r = -ENOMEM;
+                                goto fail;
+                        }
+
+                        if (changes_detached[i].source) {
+                                source = strdup(changes_detached[i].source);
+                                if (!source) {
+                                        r = -ENOMEM;
+                                        goto fail;
+                                }
+                        }
+
+                        changes[n_changes++] = (PortableChange) {
+                                .type_or_errno = changes_detached[i].type_or_errno,
+                                .path = TAKE_PTR(path),
+                                .source = TAKE_PTR(source),
+                        };
+                }
+        }
+
+        *ret_n_changes = n_changes;
+        *ret_changes = TAKE_PTR(changes);
+
+        return 0;
+
+fail:
+        portable_changes_free(changes, n_changes);
+        return r;
+}
+
+int bus_image_common_reattach(
+                Manager *m,
+                sd_bus_message *message,
+                const char *name_or_path,
+                Image *image,
+                sd_bus_error *error) {
+
+        PortableChange *changes_detached = NULL, *changes_attached = NULL, *changes_gone = NULL;
+        size_t n_changes_detached = 0, n_changes_attached = 0, n_changes_gone = 0;
+        _cleanup_strv_free_ char **matches = NULL, **extension_images = NULL;
+        PortableFlags flags = PORTABLE_REATTACH;
+        const char *profile, *copy_mode;
+        int r;
+
+        assert(message);
+        assert(name_or_path || image);
+
+        if (!m) {
+                assert(image);
+                m = image->userdata;
+        }
+
+        if (sd_bus_message_is_method_call(message, NULL, "ReattachImageWithExtensions") ||
+            sd_bus_message_is_method_call(message, NULL, "ReattachWithExtensions")) {
+                r = sd_bus_message_read_strv(message, &extension_images);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_bus_message_read_strv(message, &matches);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_read(message, "s", &profile);
+        if (r < 0)
+                return r;
+
+        if (sd_bus_message_is_method_call(message, NULL, "ReattachImageWithExtensions") ||
+            sd_bus_message_is_method_call(message, NULL, "ReattachWithExtensions")) {
+                uint64_t input_flags = 0;
+
+                r = sd_bus_message_read(message, "st", &copy_mode, &input_flags);
+                if (r < 0)
+                        return r;
+
+                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;
+        } else {
+                int runtime;
+
+                r = sd_bus_message_read(message, "bs", &runtime, &copy_mode);
+                if (r < 0)
+                        return r;
+
+                if (runtime)
+                        flags |= PORTABLE_RUNTIME;
+        }
+
+        if (streq(copy_mode, "symlink"))
+                flags |= PORTABLE_PREFER_SYMLINK;
+        else if (streq(copy_mode, "copy"))
+                flags |= PORTABLE_PREFER_COPY;
+        else if (!isempty(copy_mode))
+                return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode);
+
+        r = bus_image_acquire(m,
+                              message,
+                              name_or_path,
+                              image,
+                              BUS_IMAGE_AUTHENTICATE_ALL,
+                              "org.freedesktop.portable1.attach-images",
+                              &image,
+                              error);
+        if (r < 0)
+                return r;
+        if (r == 0) /* Will call us back */
+                return 1;
+
+        r = portable_detach(
+                        sd_bus_message_get_bus(message),
+                        image->path,
+                        extension_images,
+                        flags,
+                        &changes_detached,
+                        &n_changes_detached,
+                        error);
+        if (r < 0)
+                goto finish;
+
+        r = portable_attach(
+                        sd_bus_message_get_bus(message),
+                        image->path,
+                        matches,
+                        profile,
+                        extension_images,
+                        flags,
+                        &changes_attached,
+                        &n_changes_attached,
+                        error);
+        if (r < 0)
+                goto finish;
+
+        /* We want to return the list of units really removed by the detach,
+         * and not added again by the attach */
+        r = normalize_portable_changes(changes_attached, n_changes_attached,
+                                       changes_detached, n_changes_detached,
+                                       &changes_gone, &n_changes_gone);
+        if (r < 0)
+                goto finish;
+
+        /* First, return the units that are gone (so that the caller can stop them)
+         * Then, return the units that are changed/added (so that the caller can
+         * start/restart/enable them) */
+        r = reply_portable_changes_pair(message,
+                                        changes_gone, n_changes_gone,
+                                        changes_attached, n_changes_attached);
+        if (r < 0)
+                goto finish;
+
+finish:
+        portable_changes_free(changes_detached, n_changes_detached);
+        portable_changes_free(changes_attached, n_changes_attached);
+        portable_changes_free(changes_gone, n_changes_gone);
+        return r;
+}
+
+static int bus_image_method_reattach(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return bus_image_common_reattach(NULL, message, NULL, userdata, error);
+}
+
 int bus_image_common_mark_read_only(
                 Manager *m,
                 sd_bus_message *message,
@@ -489,7 +836,7 @@ int bus_image_common_set_limit(
         if (r < 0)
                 return r;
         if (!FILE_SIZE_VALID_OR_INFINITY(limit))
-                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
+                return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range");
 
         r = bus_image_acquire(m,
                               message,
@@ -527,14 +874,101 @@ const sd_bus_vtable image_vtable[] = {
         SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0),
         SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0),
         SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0),
-        SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_image_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
-        SD_BUS_METHOD("GetMetadata", "as", "saya{say}", bus_image_method_get_metadata, SD_BUS_VTABLE_UNPRIVILEGED),
-        SD_BUS_METHOD("GetState", NULL, "s", bus_image_method_get_state, SD_BUS_VTABLE_UNPRIVILEGED),
-        SD_BUS_METHOD("Attach", "assbs", "a(sss)", bus_image_method_attach, SD_BUS_VTABLE_UNPRIVILEGED),
-        SD_BUS_METHOD("Detach", "b", "a(sss)", bus_image_method_detach, SD_BUS_VTABLE_UNPRIVILEGED),
-        SD_BUS_METHOD("Remove", NULL, NULL, bus_image_method_remove, SD_BUS_VTABLE_UNPRIVILEGED),
-        SD_BUS_METHOD("MarkReadOnly", "b", NULL, bus_image_method_mark_read_only, SD_BUS_VTABLE_UNPRIVILEGED),
-        SD_BUS_METHOD("SetLimit", "t", NULL, bus_image_method_set_limit, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("GetOSRelease",
+                                SD_BUS_NO_ARGS,
+                                SD_BUS_RESULT("a{ss}", os_release),
+                                bus_image_method_get_os_release,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("GetMetadata",
+                                SD_BUS_ARGS("as", matches),
+                                SD_BUS_RESULT("s", image,
+                                              "ay", os_release,
+                                              "a{say}", units),
+                                bus_image_method_get_metadata,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("GetMetadataWithExtensions",
+                                SD_BUS_ARGS("as", extensions,
+                                            "as", matches,
+                                            "t", flags),
+                                SD_BUS_RESULT("s", image,
+                                              "ay", os_release,
+                                              "a{say}", extensions,
+                                              "a{say}", units),
+                                bus_image_method_get_metadata,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("GetState",
+                                SD_BUS_NO_ARGS,
+                                SD_BUS_RESULT("s", state),
+                                bus_image_method_get_state,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("GetStateWithExtensions",
+                                SD_BUS_ARGS("as", extensions,
+                                            "t", flags),
+                                SD_BUS_RESULT("s", state),
+                                bus_image_method_get_state,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("Attach",
+                                SD_BUS_ARGS("as", matches,
+                                            "s", profile,
+                                            "b", runtime,
+                                            "s", copy_mode),
+                                SD_BUS_RESULT("a(sss)", changes),
+                                bus_image_method_attach,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("AttachWithExtensions",
+                                SD_BUS_ARGS("as", extensions,
+                                            "as", matches,
+                                            "s", profile,
+                                            "s", copy_mode,
+                                            "t", flags),
+                                SD_BUS_RESULT("a(sss)", changes),
+                                bus_image_method_attach,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("Detach",
+                                SD_BUS_ARGS("b", runtime),
+                                SD_BUS_RESULT("a(sss)", changes),
+                                bus_image_method_detach,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("DetachWithExtensions",
+                                SD_BUS_ARGS("as", extensions,
+                                            "t", flags),
+                                SD_BUS_RESULT("a(sss)", changes),
+                                bus_image_method_detach,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("Reattach",
+                                SD_BUS_ARGS("as", matches,
+                                            "s", profile,
+                                            "b", runtime,
+                                            "s", copy_mode),
+                                SD_BUS_RESULT("a(sss)", changes_removed,
+                                              "a(sss)", changes_updated),
+                                bus_image_method_reattach,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("ReattacheWithExtensions",
+                                SD_BUS_ARGS("as", extensions,
+                                            "as", matches,
+                                            "s", profile,
+                                            "s", copy_mode,
+                                            "t", flags),
+                                SD_BUS_RESULT("a(sss)", changes_removed,
+                                              "a(sss)", changes_updated),
+                                bus_image_method_reattach,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("Remove",
+                                SD_BUS_NO_ARGS,
+                                SD_BUS_NO_RESULT,
+                                bus_image_method_remove,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("MarkReadOnly",
+                                SD_BUS_ARGS("b", read_only),
+                                SD_BUS_NO_RESULT,
+                                bus_image_method_mark_read_only,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD_WITH_ARGS("SetLimit",
+                                SD_BUS_ARGS("t", limit),
+                                SD_BUS_NO_RESULT,
+                                bus_image_method_set_limit,
+                                SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_VTABLE_END
 };
 
@@ -608,19 +1042,23 @@ int bus_image_acquire(
                 /* If it's a short name, let's search for it */
                 r = image_find(IMAGE_PORTABLE, name_or_path, NULL, &loaded);
                 if (r == -ENOENT)
-                        return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PORTABLE_IMAGE, "No image '%s' found.", name_or_path);
+                        return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PORTABLE_IMAGE,
+                                                 "No image '%s' found.", name_or_path);
 
                 /* other errors are handled below… */
         } else {
                 /* Don't accept path if this is always forbidden */
                 if (mode == BUS_IMAGE_REFUSE_BY_PATH)
-                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Expected image name, not path in place of '%s'.", name_or_path);
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+                                                 "Expected image name, not path in place of '%s'.", name_or_path);
 
                 if (!path_is_absolute(name_or_path))
-                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is not valid or not a valid path.", name_or_path);
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+                                                 "Image name '%s' is not valid or not a valid path.", name_or_path);
 
                 if (!path_is_normalized(name_or_path))
-                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image path '%s' is not normalized.", name_or_path);
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS,
+                                                 "Image path '%s' is not normalized.", name_or_path);
 
                 if (mode == BUS_IMAGE_AUTHENTICATE_BY_PATH) {
                         r = bus_verify_polkit_async(
@@ -643,7 +1081,9 @@ int bus_image_acquire(
                 r = image_from_path(name_or_path, &loaded);
         }
         if (r == -EMEDIUMTYPE) {
-                sd_bus_error_setf(error, BUS_ERROR_BAD_PORTABLE_IMAGE_TYPE, "Typ of image '%s' not recognized; supported image types are directories/btrfs subvolumes, block devices, and raw disk image files with suffix '.raw'.", name_or_path);
+                sd_bus_error_setf(error, BUS_ERROR_BAD_PORTABLE_IMAGE_TYPE,
+                                  "Type of image '%s' not recognized; supported image types are directories/btrfs subvolumes, block devices, and raw disk image files with suffix '.raw'.",
+                                  name_or_path);
                 return r;
         }
         if (r < 0)
@@ -683,6 +1123,9 @@ int bus_image_object_find(
                 return 0;
         if (r == 0)
                 goto not_found;
+        if (isempty(e))
+                /* The path is "/org/freedesktop/portable1/image" itself */
+                goto not_found;
 
         r = bus_image_acquire(m, sd_bus_get_current_message(bus), e, NULL, BUS_IMAGE_REFUSE_BY_PATH, NULL, &image, error);
         if (r == -ENOENT)
@@ -701,8 +1144,8 @@ not_found:
 int bus_image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
         _cleanup_hashmap_free_ Hashmap *images = NULL;
         _cleanup_strv_free_ char **l = NULL;
-        size_t n_allocated = 0, n = 0;
         Manager *m = userdata;
+        size_t n = 0;
         Image *image;
         int r;
 
@@ -725,7 +1168,7 @@ int bus_image_node_enumerator(sd_bus *bus, const char *path, void *userdata, cha
                 if (r < 0)
                         return r;
 
-                if (!GREEDY_REALLOC(l, n_allocated, n+2)) {
+                if (!GREEDY_REALLOC(l, n+2)) {
                         free(p);
                         return -ENOMEM;
                 }
@@ -738,3 +1181,10 @@ int bus_image_node_enumerator(sd_bus *bus, const char *path, void *userdata, cha
 
         return 1;
 }
+
+const BusObjectImplementation image_object = {
+        "/org/freedesktop/portable1/image",
+        "org.freedesktop.portable1.Image",
+        .fallback_vtables = BUS_FALLBACK_VTABLES({image_vtable, bus_image_object_find}),
+        .node_enumerator = bus_image_node_enumerator,
+};