]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
machinectl: reorder verb functions to match --help
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Fri, 15 May 2026 16:29:29 +0000 (18:29 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Fri, 15 May 2026 19:49:05 +0000 (21:49 +0200)
The net diff is negative because some spurious whitespace and forward
declarations were dropped. One new forward declaration was added. (For
verb_poweroff_machine. The func could be moved, but I think it's better
to keep it adjacent to verb_reboot_machine which is very similar.)

src/machine/machinectl.c

index 3d7f782a8c775463051095dd95ef9428fbb48bb1..1e704e4d962142c4d211f1c2848b0ac677441f59 100644 (file)
@@ -361,66 +361,6 @@ static int verb_list_machines(int argc, char *argv[], uintptr_t _data, void *use
         return show_table(table, "machines");
 }
 
-static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) {
-
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
-        _cleanup_(table_unrefp) Table *table = NULL;
-        sd_bus *bus = ASSERT_PTR(userdata);
-        int r;
-
-        pager_open(arg_pager_flags);
-
-        r = bus_call_method(bus, bus_machine_mgr, "ListImages", &error, &reply, NULL);
-        if (r < 0)
-                return log_error_errno(r, "Could not get images: %s", bus_error_message(&error, r));
-
-        table = table_new("name", "type", "ro", "usage", "created", "modified");
-        if (!table)
-                return log_oom();
-
-        if (arg_full)
-                table_set_width(table, 0);
-
-        (void) table_set_align_percent(table, TABLE_HEADER_CELL(3), 100);
-
-        r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)");
-        if (r < 0)
-                return bus_log_parse_error(r);
-
-        for (;;) {
-                uint64_t crtime, mtime, size;
-                const char *name, *type;
-                int ro_int;
-
-                r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &ro_int, &crtime, &mtime, &size, NULL);
-                if (r < 0)
-                        return bus_log_parse_error(r);
-                if (r == 0)
-                        break;
-
-                if (name[0] == '.' && !arg_all)
-                        continue;
-
-                r = table_add_many(table,
-                                   TABLE_STRING, name,
-                                   TABLE_STRING, type,
-                                   TABLE_BOOLEAN, ro_int,
-                                   TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL,
-                                   TABLE_SIZE, size,
-                                   TABLE_TIMESTAMP, crtime,
-                                   TABLE_TIMESTAMP, mtime);
-                if (r < 0)
-                        return table_log_add_error(r);
-        }
-
-        r = sd_bus_message_exit_container(reply);
-        if (r < 0)
-                return bus_log_parse_error(r);
-
-        return show_table(table, "images");
-}
-
 static int show_unit_cgroup(
                 sd_bus *bus,
                 const char *unit,
@@ -683,7 +623,6 @@ static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_
 }
 
 static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
-
         static const struct bus_properties_map map[]  = {
                 { "Name",               "s",  NULL,          offsetof(MachineStatusInfo, name)                },
                 { "Class",              "s",  NULL,          offsetof(MachineStatusInfo, class)               },
@@ -792,396 +731,441 @@ static int verb_show_machine(int argc, char *argv[], uintptr_t _data, void *user
         return r;
 }
 
-static int print_image_hostname(sd_bus *bus, const char *name) {
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
-        const char *hn;
+static int make_service_name(const char *name, char **ret) {
         int r;
 
-        r = bus_call_method(bus, bus_machine_mgr, "GetImageHostname", NULL, &reply, "s", name);
-        if (r < 0)
-                return r;
+        assert(name);
+        assert(ret);
+        assert(arg_runner >= 0 && arg_runner < _RUNNER_MAX);
 
-        r = sd_bus_message_read(reply, "s", &hn);
-        if (r < 0)
-                return r;
+        if (!hostname_is_valid(name, 0))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Invalid machine name %s.", name);
 
-        if (!isempty(hn))
-                printf("\tHostname: %s\n", hn);
+        r = unit_name_build(machine_runner_unit_prefix_table[arg_runner], name, ".service", ret);
+        if (r < 0)
+                return log_error_errno(r, "Failed to build unit name: %m");
 
         return 0;
 }
 
-static int print_image_machine_id(sd_bus *bus, const char *name) {
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
-        sd_id128_t id;
+static int image_exists(sd_bus *bus, const char *name) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         int r;
 
-        r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineID", NULL, &reply, "s", name);
-        if (r < 0)
-                return r;
+        assert(bus);
+        assert(name);
 
-        r = bus_message_read_id128(reply, &id);
-        if (r < 0)
-                return r;
+        r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, NULL, "s", name);
+        if (r < 0) {
+                if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_IMAGE))
+                        return 0;
 
-        if (!sd_id128_is_null(id))
-                printf("      Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id));
+                return log_error_errno(r, "Failed to check whether image %s exists: %s", name, bus_error_message(&error, r));
+        }
 
-        return 0;
+        return 1;
 }
 
-static int print_image_machine_info(sd_bus *bus, const char *name) {
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+static int verb_start_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
+        sd_bus *bus = ASSERT_PTR(userdata);
         int r;
 
-        r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineInfo", NULL, &reply, "s", name);
-        if (r < 0)
-                return r;
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+        ask_password_agent_open_if_enabled(arg_transport, arg_ask_password);
 
-        r = sd_bus_message_enter_container(reply, 'a', "{ss}");
+        r = bus_wait_for_jobs_new(bus, &w);
         if (r < 0)
-                return r;
+                return log_error_errno(r, "Could not watch jobs: %m");
 
-        for (;;) {
-                const char *p, *q;
+        for (int i = 1; i < argc; i++) {
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+                _cleanup_free_ char *unit = NULL;
+                const char *object;
 
-                r = sd_bus_message_read(reply, "{ss}", &p, &q);
+                r = make_service_name(argv[i], &unit);
+                if (r < 0)
+                        return r;
+
+                r = image_exists(bus, argv[i]);
                 if (r < 0)
                         return r;
                 if (r == 0)
-                        break;
+                        return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
+                                               "Machine image '%s' does not exist.",
+                                               argv[i]);
 
-                if (streq(p, "DEPLOYMENT"))
-                        printf("      Deployment: %s\n", q);
+                r = bus_call_method(
+                                bus,
+                                bus_systemd_mgr,
+                                "StartUnit",
+                                &error,
+                                &reply,
+                                "ss", unit, "fail");
+                if (r < 0)
+                        return log_error_errno(r, "Failed to start unit: %s", bus_error_message(&error, r));
+
+                r = sd_bus_message_read(reply, "o", &object);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                r = bus_wait_for_jobs_add(w, object);
+                if (r < 0)
+                        return log_oom();
         }
 
-        r = sd_bus_message_exit_container(reply);
+        r = bus_wait_for_jobs(w, arg_quiet, NULL);
         if (r < 0)
                 return r;
 
         return 0;
 }
 
-typedef struct ImageStatusInfo {
-        const char *name;
-        const char *path;
-        const char *type;
-        bool read_only;
-        usec_t crtime;
-        usec_t mtime;
-        uint64_t usage;
-        uint64_t limit;
-        uint64_t usage_exclusive;
-        uint64_t limit_exclusive;
-} ImageStatusInfo;
-
-static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) {
-        assert(bus);
-        assert(i);
+static int parse_machine_uid(const char *spec, const char **machine, char **uid) {
+        /*
+         * Whatever is specified in the spec takes priority over global arguments.
+         */
+        char *_uid = NULL;
+        const char *_machine = NULL;
 
-        if (i->name) {
-                fputs(i->name, stdout);
-                putchar('\n');
-        }
+        assert(uid);
+        assert(machine);
 
-        if (i->type)
-                printf("\t    Type: %s\n", i->type);
+        if (spec) {
+                const char *at;
 
-        if (i->path)
-                printf("\t    Path: %s\n", i->path);
+                at = strchr(spec, '@');
+                if (at) {
+                        if (at == spec)
+                                /* Do the same as ssh and refuse "@host". */
+                                return -EINVAL;
 
-        (void) print_image_hostname(bus, i->name);
-        (void) print_image_machine_id(bus, i->name);
-        (void) print_image_machine_info(bus, i->name);
+                        _machine = at + 1;
+                        _uid = strndup(spec, at - spec);
+                        if (!_uid)
+                                return -ENOMEM;
+                } else
+                        _machine = spec;
+        };
 
-        print_os_release(bus, "GetImageOSRelease", i->name, "\t      OS: ");
+        if (arg_uid && !_uid) {
+                _uid = strdup(arg_uid);
+                if (!_uid)
+                        return -ENOMEM;
+        }
 
-        printf("\t      RO: %s%s%s\n",
-               i->read_only ? ansi_highlight_red() : "",
-               i->read_only ? "read-only" : "writable",
-               i->read_only ? ansi_normal() : "");
+        *uid = _uid;
+        *machine = isempty(_machine) ? ".host" : _machine;
+        return 0;
+}
 
-        if (timestamp_is_set(i->crtime))
-                printf("\t Created: %s; %s\n",
-                       FORMAT_TIMESTAMP(i->crtime), FORMAT_TIMESTAMP_RELATIVE(i->crtime));
+static int process_forward(sd_event *event, sd_bus_slot *machine_removed_slot, int master, PTYForwardFlags flags, const char *name) {
+        int r;
 
-        if (timestamp_is_set(i->mtime))
-                printf("\tModified: %s; %s\n",
-                       FORMAT_TIMESTAMP(i->mtime), FORMAT_TIMESTAMP_RELATIVE(i->mtime));
+        assert(event);
+        assert(machine_removed_slot);
+        assert(master >= 0);
+        assert(name);
 
-        if (i->usage != UINT64_MAX) {
-                if (i->usage_exclusive != i->usage && i->usage_exclusive != UINT64_MAX)
-                        printf("\t   Usage: %s (exclusive: %s)\n",
-                               FORMAT_BYTES(i->usage), FORMAT_BYTES(i->usage_exclusive));
+        if (!arg_quiet) {
+                if (streq(name, ".host"))
+                        log_info("Connected to the local host. Press ^] three times within 1s to exit session.");
                 else
-                        printf("\t   Usage: %s\n", FORMAT_BYTES(i->usage));
+                        log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name);
         }
 
-        if (i->limit != UINT64_MAX) {
-                if (i->limit_exclusive != i->limit && i->limit_exclusive != UINT64_MAX)
-                        printf("\t   Limit: %s (exclusive: %s)\n",
-                               FORMAT_BYTES(i->limit), FORMAT_BYTES(i->limit_exclusive));
-                else
-                        printf("\t   Limit: %s\n", FORMAT_BYTES(i->limit));
+        _cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
+        if (!terminal_is_dumb()) {
+                r = osc_context_open_container(name, /* ret_seq= */ NULL, &osc_context_id);
+                if (r < 0)
+                        return r;
         }
-}
-
-static int show_image_info(sd_bus *bus, const char *path, bool *new_line) {
 
-        static const struct bus_properties_map map[]  = {
-                { "Name",                  "s",  NULL, offsetof(ImageStatusInfo, name)            },
-                { "Path",                  "s",  NULL, offsetof(ImageStatusInfo, path)            },
-                { "Type",                  "s",  NULL, offsetof(ImageStatusInfo, type)            },
-                { "ReadOnly",              "b",  NULL, offsetof(ImageStatusInfo, read_only)       },
-                { "CreationTimestamp",     "t",  NULL, offsetof(ImageStatusInfo, crtime)          },
-                { "ModificationTimestamp", "t",  NULL, offsetof(ImageStatusInfo, mtime)           },
-                { "Usage",                 "t",  NULL, offsetof(ImageStatusInfo, usage)           },
-                { "Limit",                 "t",  NULL, offsetof(ImageStatusInfo, limit)           },
-                { "UsageExclusive",        "t",  NULL, offsetof(ImageStatusInfo, usage_exclusive) },
-                { "LimitExclusive",        "t",  NULL, offsetof(ImageStatusInfo, limit_exclusive) },
-                {}
-        };
+        r = sd_event_set_signal_exit(event, true);
+        if (r < 0)
+                return log_error_errno(r, "Failed to enable SIGINT/SITERM handling: %m");
 
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
-        ImageStatusInfo info = {};
-        int r;
+        _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
+        r = pty_forward_new(event, master, flags, &forward);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create PTY forwarder: %m");
 
-        assert(bus);
-        assert(path);
-        assert(new_line);
+        /* No userdata should not set previously. */
+        assert_se(!sd_bus_slot_set_userdata(machine_removed_slot, forward));
 
-        r = bus_map_all_properties(bus,
-                                   "org.freedesktop.machine1",
-                                   path,
-                                   map,
-                                   BUS_MAP_BOOLEAN_AS_BOOL,
-                                   &error,
-                                   &m,
-                                   &info);
+        r = sd_event_loop(event);
         if (r < 0)
-                return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
+                return log_error_errno(r, "Failed to run event loop: %m");
 
-        if (*new_line)
-                printf("\n");
-        *new_line = true;
+        bool machine_died =
+                FLAGS_SET(flags, PTY_FORWARD_IGNORE_VHANGUP) &&
+                !pty_forward_vhangup_honored(forward);
 
-        print_image_status_info(bus, &info);
+        if (!arg_quiet) {
+                if (machine_died)
+                        log_info("Machine %s terminated.", name);
+                else if (streq(name, ".host"))
+                        log_info("Connection to the local host terminated.");
+                else
+                        log_info("Connection to machine %s terminated.", name);
+        }
 
-        return r;
+        return 0;
 }
 
-typedef struct PoolStatusInfo {
-        const char *path;
-        uint64_t usage;
-        uint64_t limit;
-} PoolStatusInfo;
+static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+        PTYForward *forward = ASSERT_PTR(userdata);
+        int r;
 
-static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) {
-        if (i->path)
-                printf("\t    Path: %s\n", i->path);
+        assert(m);
 
-        if (i->usage != UINT64_MAX)
-                printf("\t   Usage: %s\n", FORMAT_BYTES(i->usage));
+        /* Tell the forwarder to exit on the next vhangup(), so that we still flush out what might be queued
+         * and exit then. */
 
-        if (i->limit != UINT64_MAX)
-                printf("\t   Limit: %s\n", FORMAT_BYTES(i->limit));
-}
+        r = pty_forward_honor_vhangup(forward);
+        if (r < 0) {
+                /* On error, quit immediately. */
+                log_error_errno(r, "Failed to make PTY forwarder honor vhangup(): %m");
+                (void) sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE);
+        }
 
-static int show_pool_info(sd_bus *bus) {
+        return 0;
+}
 
-        static const struct bus_properties_map map[]  = {
-                { "PoolPath",  "s",  NULL, offsetof(PoolStatusInfo, path)  },
-                { "PoolUsage", "t",  NULL, offsetof(PoolStatusInfo, usage) },
-                { "PoolLimit", "t",  NULL, offsetof(PoolStatusInfo, limit) },
-                {}
-        };
+static int verb_login_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL;
+        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+        int master = -1, r;
+        sd_bus *bus = ASSERT_PTR(userdata);
+        const char *match, *machine;
 
-        PoolStatusInfo info = {
-                .usage = UINT64_MAX,
-                .limit = UINT64_MAX,
-        };
+        if (!strv_isempty(arg_setenv) || arg_uid)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "--setenv= and --uid= are not supported for 'login'. Use 'shell' instead.");
 
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
-        int r;
+        if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE))
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                       "Login only supported on local machines.");
 
-        assert(bus);
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
 
-        r = bus_map_all_properties(bus,
-                                   "org.freedesktop.machine1",
-                                   "/org/freedesktop/machine1",
-                                   map,
-                                   0,
-                                   &error,
-                                   &m,
-                                   &info);
+        r = sd_event_default(&event);
         if (r < 0)
-                return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
-
-        print_pool_status_info(bus, &info);
+                return log_error_errno(r, "Failed to get event loop: %m");
 
-        return 0;
-}
+        r = sd_bus_attach_event(bus, event, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to attach bus to event loop: %m");
 
-static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) {
-        int r;
+        machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1];
 
-        assert(bus);
-        assert(path);
-        assert(new_line);
+        match = strjoina("type='signal',"
+                         "sender='org.freedesktop.machine1',"
+                         "path='/org/freedesktop/machine1',",
+                         "interface='org.freedesktop.machine1.Manager',"
+                         "member='MachineRemoved',"
+                         "arg0='", machine, "'");
 
-        if (*new_line)
-                printf("\n");
+        r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to request machine removal match: %m");
 
-        *new_line = true;
+        r = bus_call_method(bus, bus_machine_mgr, "OpenMachineLogin", &error, &reply, "s", machine);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get login PTY: %s", bus_error_message(&error, r));
 
-        r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, NULL, arg_property, arg_print_flags, NULL);
+        r = sd_bus_message_read(reply, "hs", &master, NULL);
         if (r < 0)
-                log_error_errno(r, "Could not get properties: %m");
+                return bus_log_parse_error(r);
 
-        return r;
+        return process_forward(event, slot, master, PTY_FORWARD_IGNORE_VHANGUP, machine);
 }
 
-static int verb_show_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
+static int verb_shell_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        bool properties, new_line = false;
+        _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL;
+        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+        int master = -1, r;
         sd_bus *bus = ASSERT_PTR(userdata);
-        int r = 0;
+        const char *match, *machine, *path;
+        _cleanup_free_ char *uid = NULL;
 
-        properties = !strstr(argv[0], "status");
+        if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE))
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                       "Shell only supported on local machines.");
 
-        pager_open(arg_pager_flags);
+        if (terminal_is_dumb()) {
+                /* Set TERM=dumb if we are running on a dumb terminal or with a pipe.
+                 * Otherwise, we will get unwanted OSC sequences. */
+                if (!strv_find_prefix(arg_setenv, "TERM="))
+                        if (strv_extend(&arg_setenv, "TERM=dumb") < 0)
+                                return log_oom();
+        } else
+                /* Pass $TERM & Co. to shell session, if not explicitly specified. */
+                FOREACH_STRING(v, "TERM=", "COLORTERM=", "NO_COLOR=") {
+                        if (strv_find_prefix(arg_setenv, v))
+                                continue;
 
-        if (argc <= 1) {
+                        const char *t = strv_find_prefix(environ, v);
+                        if (!t)
+                                continue;
 
-                /* If no argument is specified, inspect the manager
-                 * itself */
+                        if (strv_extend(&arg_setenv, t) < 0)
+                                return log_oom();
+                }
 
-                if (properties)
-                        r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line);
-                else
-                        r = show_pool_info(bus);
-                if (r < 0)
-                        return r;
-        }
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
 
-        for (int i = 1; i < argc; i++) {
-                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
-                const char *path = NULL;
+        r = sd_event_default(&event);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get event loop: %m");
 
-                r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, &reply, "s", argv[i]);
-                if (r < 0)
-                        return log_error_errno(r, "Could not get path to image: %s", bus_error_message(&error, r));
+        r = sd_bus_attach_event(bus, event, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to attach bus to event loop: %m");
 
-                r = sd_bus_message_read(reply, "o", &path);
-                if (r < 0)
-                        return bus_log_parse_error(r);
+        r = parse_machine_uid(argc >= 2 ? argv[1] : NULL, &machine, &uid);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse machine specification: %m");
 
-                if (properties)
-                        r = show_image_properties(bus, path, &new_line);
-                else
-                        r = show_image_info(bus, path, &new_line);
-        }
+        match = strjoina("type='signal',"
+                         "sender='org.freedesktop.machine1',"
+                         "path='/org/freedesktop/machine1',",
+                         "interface='org.freedesktop.machine1.Manager',"
+                         "member='MachineRemoved',"
+                         "arg0='", machine, "'");
 
-        return r;
-}
+        r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to request machine removal match: %m");
 
-static int verb_kill_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        sd_bus *bus = ASSERT_PTR(userdata);
-        int r;
+        r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "OpenMachineShell");
+        if (r < 0)
+                return bus_log_create_error(r);
 
-        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+        path = argc < 3 || isempty(argv[2]) ? NULL : argv[2];
 
-        if (!arg_kill_whom)
-                arg_kill_whom = "all";
+        r = sd_bus_message_append(m, "sss", machine, uid, path);
+        if (r < 0)
+                return bus_log_create_error(r);
 
-        for (int i = 1; i < argc; i++) {
-                r = bus_call_method(
-                                bus,
-                                bus_machine_mgr,
-                                "KillMachine",
-                                &error,
-                                NULL,
-                                "ssi", argv[i], arg_kill_whom, arg_signal);
-                if (r < 0)
-                        return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r));
-        }
+        r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2);
+        if (r < 0)
+                return bus_log_create_error(r);
 
-        return 0;
+        r = sd_bus_message_append_strv(m, arg_setenv);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_call(bus, m, 0, &error, &reply);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get shell PTY: %s", bus_error_message(&error, r));
+
+        r = sd_bus_message_read(reply, "hs", &master, NULL);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        return process_forward(event, slot, master, /* flags= */ 0, machine);
 }
 
-static int verb_machine_control_one(const char *machine_name, const char *method);
+static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata);
 
-static int verb_reboot_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
+static int verb_enable_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        const char *method;
         sd_bus *bus = ASSERT_PTR(userdata);
         int r;
+        bool enable;
 
         (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
 
+        enable = streq(argv[0], "enable");
+        method = enable ? "EnableUnitFiles" : "DisableUnitFiles";
+
+        r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_message_open_container(m, 'a', "s");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        if (enable) {
+                r = sd_bus_message_append(m, "s", "machines.target");
+                if (r < 0)
+                        return bus_log_create_error(r);
+        }
+
         for (int i = 1; i < argc; i++) {
-                r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.Reboot");
-                if (r >= 0)
-                        continue;
-                if (r != -EOPNOTSUPP)
+                _cleanup_free_ char *unit = NULL;
+
+                r = make_service_name(argv[i], &unit);
+                if (r < 0)
                         return r;
 
-                /* Container fallback: SIGINT to init (sysvinit + systemd compatible) */
-                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-                r = bus_call_method(bus, bus_machine_mgr, "KillMachine", &error, NULL,
-                                   "ssi", argv[i], "leader", (int32_t) SIGINT);
+                r = image_exists(bus, argv[i]);
                 if (r < 0)
-                        return log_error_errno(r, "Could not reboot machine '%s': %s", argv[i], bus_error_message(&error, r));
-        }
+                        return r;
+                if (r == 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
+                                               "Machine image '%s' does not exist.",
+                                               argv[i]);
 
-        return 0;
-}
+                r = sd_bus_message_append(m, "s", unit);
+                if (r < 0)
+                        return bus_log_create_error(r);
+        }
 
-static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
-        sd_bus *bus = ASSERT_PTR(userdata);
-        int r;
+        r = sd_bus_message_close_container(m);
+        if (r < 0)
+                return bus_log_create_error(r);
 
-        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+        if (enable)
+                r = sd_bus_message_append(m, "bb", false, false);
+        else
+                r = sd_bus_message_append(m, "b", false);
+        if (r < 0)
+                return bus_log_create_error(r);
 
-        for (int i = 1; i < argc; i++) {
-                /* VM with varlink control socket: QMP graceful powerdown */
-                r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.PowerOff");
-                if (r >= 0)
-                        continue;
-                if (r != -EOPNOTSUPP)
-                        return r;
+        r = sd_bus_call(bus, m, 0, &error, &reply);
+        if (r < 0)
+                return log_error_errno(r, "Failed to enable or disable unit: %s", bus_error_message(&error, r));
 
-                /* Not a VM: signal-based poweroff */
-                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-                r = bus_call_method(bus, bus_machine_mgr, "KillMachine", &error, NULL,
-                                   "ssi", argv[i], "leader", (int32_t) (SIGRTMIN+4));
+        if (enable) {
+                r = sd_bus_message_read(reply, "b", NULL);
                 if (r < 0)
-                        return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r));
+                        return bus_log_parse_error(r);
         }
 
-        return 0;
-}
+        r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
+        if (r < 0)
+                return r;
 
-static int verb_terminate_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        sd_bus *bus = ASSERT_PTR(userdata);
-        int r;
+        r = bus_service_manager_reload(bus);
+        if (r < 0)
+                return r;
 
-        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+        if (arg_now) {
+                _cleanup_strv_free_ char **new_args = NULL;
 
-        for (int i = 1; i < argc; i++) {
-                /* VM with varlink control socket: QMP quit (immediate termination) */
-                r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.Terminate");
-                if (r >= 0)
-                        continue;
-                if (r != -EOPNOTSUPP)
-                        return r;
+                new_args = strv_new(enable ? "start" : "poweroff");
+                if (!new_args)
+                        return log_oom();
 
-                /* Not a VM or no varlink socket: fall back to machined */
-                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-                r = bus_call_method(bus, bus_machine_mgr, "TerminateMachine", &error, NULL, "s", argv[i]);
+                r = strv_extend_strv(&new_args, argv + 1, /* filter_duplicates= */ false);
                 if (r < 0)
-                        return log_error_errno(r, "Could not terminate machine: %s", bus_error_message(&error, r));
+                        return log_oom();
+
+                if (enable)
+                        return verb_start_machine(strv_length(new_args), new_args, data, userdata);
+
+                return verb_poweroff_machine(strv_length(new_args), new_args, data, userdata);
         }
 
         return 0;
@@ -1258,6 +1242,55 @@ static int verb_machine_control_one(const char *machine_name, const char *method
         return 0;
 }
 
+static int verb_poweroff_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
+        sd_bus *bus = ASSERT_PTR(userdata);
+        int r;
+
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        for (int i = 1; i < argc; i++) {
+                /* VM with varlink control socket: QMP graceful powerdown */
+                r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.PowerOff");
+                if (r >= 0)
+                        continue;
+                if (r != -EOPNOTSUPP)
+                        return r;
+
+                /* Not a VM: signal-based poweroff */
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                r = bus_call_method(bus, bus_machine_mgr, "KillMachine", &error, NULL,
+                                   "ssi", argv[i], "leader", (int32_t) (SIGRTMIN+4));
+                if (r < 0)
+                        return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r));
+        }
+
+        return 0;
+}
+
+static int verb_reboot_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
+        sd_bus *bus = ASSERT_PTR(userdata);
+        int r;
+
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        for (int i = 1; i < argc; i++) {
+                r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.Reboot");
+                if (r >= 0)
+                        continue;
+                if (r != -EOPNOTSUPP)
+                        return r;
+
+                /* Container fallback: SIGINT to init (sysvinit + systemd compatible) */
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                r = bus_call_method(bus, bus_machine_mgr, "KillMachine", &error, NULL,
+                                   "ssi", argv[i], "leader", (int32_t) SIGINT);
+                if (r < 0)
+                        return log_error_errno(r, "Could not reboot machine '%s': %s", argv[i], bus_error_message(&error, r));
+        }
+
+        return 0;
+}
+
 static int verb_vm_control(int argc, char *argv[], const char *method) {
         int r;
 
@@ -1280,7 +1313,56 @@ static int verb_resume(int argc, char *argv[], uintptr_t _data, void *userdata)
         return verb_vm_control(argc, argv, "io.systemd.MachineInstance.Resume");
 }
 
-static const char *select_copy_method(bool copy_from, bool force) {
+static int verb_terminate_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        sd_bus *bus = ASSERT_PTR(userdata);
+        int r;
+
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        for (int i = 1; i < argc; i++) {
+                /* VM with varlink control socket: QMP quit (immediate termination) */
+                r = verb_machine_control_one(argv[i], "io.systemd.MachineInstance.Terminate");
+                if (r >= 0)
+                        continue;
+                if (r != -EOPNOTSUPP)
+                        return r;
+
+                /* Not a VM or no varlink socket: fall back to machined */
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                r = bus_call_method(bus, bus_machine_mgr, "TerminateMachine", &error, NULL, "s", argv[i]);
+                if (r < 0)
+                        return log_error_errno(r, "Could not terminate machine: %s", bus_error_message(&error, r));
+        }
+
+        return 0;
+}
+
+static int verb_kill_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        sd_bus *bus = ASSERT_PTR(userdata);
+        int r;
+
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        if (!arg_kill_whom)
+                arg_kill_whom = "all";
+
+        for (int i = 1; i < argc; i++) {
+                r = bus_call_method(
+                                bus,
+                                bus_machine_mgr,
+                                "KillMachine",
+                                &error,
+                                NULL,
+                                "ssi", argv[i], arg_kill_whom, arg_signal);
+                if (r < 0)
+                        return log_error_errno(r, "Could not kill machine: %s", bus_error_message(&error, r));
+        }
+
+        return 0;
+}
+
+static const char* select_copy_method(bool copy_from, bool force) {
         if (force)
                 return copy_from ? "CopyFromMachineWithFlags" : "CopyToMachineWithFlags";
         else
@@ -1342,393 +1424,381 @@ static int verb_copy_files(int argc, char *argv[], uintptr_t _data, void *userda
         return 0;
 }
 
-static int verb_bind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) {
+static int verb_bind_mount(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        sd_bus *bus = ASSERT_PTR(userdata);
         int r;
 
-        if (arg_transport != BUS_TRANSPORT_LOCAL)
-                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
-                                       "bind-volume is only supported on the local transport.");
-
-        _cleanup_(bind_volume_freep) BindVolume *bv = NULL;
-        r = bind_volume_parse(argv[2], &bv);
-        if (r < 0)
-                return log_error_errno(r, "Failed to parse bind-volume argument '%s': %m", argv[2]);
-
         (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
 
-        /* Locate and connect to the target machine before acquiring storage, so a missing
-         * machine doesn't trigger 'create=new' side effects on the StorageProvider. */
-        _cleanup_free_ char *address = NULL;
-        r = machine_get_control_address(argv[1], &address);
-        if (r == -EOPNOTSUPP)
-                return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]);
-        if (r < 0)
-                return r;
-
-        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
-        r = sd_varlink_connect_address(&vl, address);
+        r = bus_call_method(
+                        bus,
+                        bus_machine_mgr,
+                        "BindMountMachine",
+                        &error,
+                        NULL,
+                        "sssbb",
+                        argv[1],
+                        argv[2],
+                        argv[3],
+                        arg_read_only,
+                        arg_mkdir);
         if (r < 0)
-                return log_error_errno(r, "Failed to connect to machine control socket %s: %m", address);
+                return log_error_errno(r, "Failed to bind mount: %s", bus_error_message(&error, r));
 
-        r = sd_varlink_set_allow_fd_passing_output(vl, true);
-        if (r < 0)
-                return log_error_errno(r, "Failed to enable fd passing on varlink connection: %m");
+        return 0;
+}
 
-        _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT;
-        _cleanup_free_ char *acquire_error_id = NULL;
-        r = storage_acquire_volume(arg_runtime_scope, bv, arg_ask_password, &acquire_error_id, &reply);
-        if (r < 0) {
-                if (acquire_error_id)
-                        return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %s",
-                                               bv->provider, bv->volume, acquire_error_id);
-                return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %m",
-                                       bv->provider, bv->volume);
-        }
+static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        _cleanup_(table_unrefp) Table *table = NULL;
+        sd_bus *bus = ASSERT_PTR(userdata);
+        int r;
 
-        int fd_index = sd_varlink_push_fd(vl, reply.fd);
-        if (fd_index < 0)
-                return log_error_errno(fd_index, "Failed to push storage fd onto varlink connection: %m");
-        TAKE_FD(reply.fd);
+        pager_open(arg_pager_flags);
 
-        _cleanup_free_ char *name = strjoin(bv->provider, ":", bv->volume);
-        if (!name)
+        r = bus_call_method(bus, bus_machine_mgr, "ListImages", &error, &reply, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Could not get images: %s", bus_error_message(&error, r));
+
+        table = table_new("name", "type", "ro", "usage", "created", "modified");
+        if (!table)
                 return log_oom();
 
-        sd_json_variant *vl_reply = NULL;
-        const char *error_id = NULL;
-        r = sd_varlink_callbo(
-                        vl,
-                        "io.systemd.MachineInstance.AddStorage",
-                        &vl_reply, &error_id,
-                        SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", fd_index),
-                        SD_JSON_BUILD_PAIR_STRING("name", name),
-                        JSON_BUILD_PAIR_STRING_NON_EMPTY("config", bv->config));
+        if (arg_full)
+                table_set_width(table, 0);
+
+        (void) table_set_align_percent(table, TABLE_HEADER_CELL(3), 100);
+
+        r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)");
         if (r < 0)
-                return log_error_errno(r, "Failed to call io.systemd.MachineInstance.AddStorage: %m");
-        if (error_id)
-                return log_error_errno(sd_varlink_error_to_errno(error_id, vl_reply),
-                                       "AddStorage failed for '%s': %s", name, error_id);
+                return bus_log_parse_error(r);
 
-        return 0;
-}
+        for (;;) {
+                uint64_t crtime, mtime, size;
+                const char *name, *type;
+                int ro_int;
 
-static int verb_unbind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        int r;
+                r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &ro_int, &crtime, &mtime, &size, NULL);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+                if (r == 0)
+                        break;
 
-        if (arg_transport != BUS_TRANSPORT_LOCAL)
-                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
-                                       "unbind-volume is only supported on the local transport.");
+                if (name[0] == '.' && !arg_all)
+                        continue;
 
-        r = machine_storage_name_split(argv[2], /* ret_provider= */ NULL, /* ret_volume= */ NULL);
-        if (r == -ENOMEM)
-                return log_oom();
+                r = table_add_many(table,
+                                   TABLE_STRING, name,
+                                   TABLE_STRING, type,
+                                   TABLE_BOOLEAN, ro_int,
+                                   TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL,
+                                   TABLE_SIZE, size,
+                                   TABLE_TIMESTAMP, crtime,
+                                   TABLE_TIMESTAMP, mtime);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        r = sd_bus_message_exit_container(reply);
         if (r < 0)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "Invalid unbind-volume name '%s', expected '<provider>:<volume>'.", argv[2]);
+                return bus_log_parse_error(r);
 
-        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+        return show_table(table, "images");
+}
 
-        _cleanup_free_ char *address = NULL;
-        r = machine_get_control_address(argv[1], &address);
-        if (r == -EOPNOTSUPP)
-                return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]);
+static int print_image_hostname(sd_bus *bus, const char *name) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        const char *hn;
+        int r;
+
+        r = bus_call_method(bus, bus_machine_mgr, "GetImageHostname", NULL, &reply, "s", name);
         if (r < 0)
                 return r;
 
-        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
-        r = sd_varlink_connect_address(&vl, address);
+        r = sd_bus_message_read(reply, "s", &hn);
         if (r < 0)
-                return log_error_errno(r, "Failed to connect to machine control socket %s: %m", address);
+                return r;
 
-        sd_json_variant *reply = NULL;
-        const char *error_id = NULL;
-        r = sd_varlink_callbo(
-                        vl,
-                        "io.systemd.MachineInstance.RemoveStorage",
-                        &reply, &error_id,
-                        SD_JSON_BUILD_PAIR_STRING("name", argv[2]));
-        if (r < 0)
-                return log_error_errno(r, "Failed to call io.systemd.MachineInstance.RemoveStorage: %m");
-        if (error_id)
-                return log_error_errno(sd_varlink_error_to_errno(error_id, reply),
-                                       "RemoveStorage failed for '%s': %s", argv[2], error_id);
+        if (!isempty(hn))
+                printf("\tHostname: %s\n", hn);
 
         return 0;
 }
 
-static int verb_bind_mount(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        sd_bus *bus = ASSERT_PTR(userdata);
+static int print_image_machine_id(sd_bus *bus, const char *name) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        sd_id128_t id;
         int r;
 
-        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+        r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineID", NULL, &reply, "s", name);
+        if (r < 0)
+                return r;
 
-        r = bus_call_method(
-                        bus,
-                        bus_machine_mgr,
-                        "BindMountMachine",
-                        &error,
-                        NULL,
-                        "sssbb",
-                        argv[1],
-                        argv[2],
-                        argv[3],
-                        arg_read_only,
-                        arg_mkdir);
+        r = bus_message_read_id128(reply, &id);
         if (r < 0)
-                return log_error_errno(r, "Failed to bind mount: %s", bus_error_message(&error, r));
+                return r;
+
+        if (!sd_id128_is_null(id))
+                printf("      Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id));
 
         return 0;
 }
 
-static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
-        PTYForward *forward = ASSERT_PTR(userdata);
+static int print_image_machine_info(sd_bus *bus, const char *name) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
         int r;
 
-        assert(m);
+        r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineInfo", NULL, &reply, "s", name);
+        if (r < 0)
+                return r;
 
-        /* Tell the forwarder to exit on the next vhangup(), so that we still flush out what might be queued
-         * and exit then. */
+        r = sd_bus_message_enter_container(reply, 'a', "{ss}");
+        if (r < 0)
+                return r;
 
-        r = pty_forward_honor_vhangup(forward);
-        if (r < 0) {
-                /* On error, quit immediately. */
-                log_error_errno(r, "Failed to make PTY forwarder honor vhangup(): %m");
-                (void) sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE);
+        for (;;) {
+                const char *p, *q;
+
+                r = sd_bus_message_read(reply, "{ss}", &p, &q);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+
+                if (streq(p, "DEPLOYMENT"))
+                        printf("      Deployment: %s\n", q);
         }
 
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+                return r;
+
         return 0;
 }
 
-static int process_forward(sd_event *event, sd_bus_slot *machine_removed_slot, int master, PTYForwardFlags flags, const char *name) {
-        int r;
+typedef struct ImageStatusInfo {
+        const char *name;
+        const char *path;
+        const char *type;
+        bool read_only;
+        usec_t crtime;
+        usec_t mtime;
+        uint64_t usage;
+        uint64_t limit;
+        uint64_t usage_exclusive;
+        uint64_t limit_exclusive;
+} ImageStatusInfo;
 
-        assert(event);
-        assert(machine_removed_slot);
-        assert(master >= 0);
-        assert(name);
+static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) {
+        assert(bus);
+        assert(i);
 
-        if (!arg_quiet) {
-                if (streq(name, ".host"))
-                        log_info("Connected to the local host. Press ^] three times within 1s to exit session.");
-                else
-                        log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name);
+        if (i->name) {
+                fputs(i->name, stdout);
+                putchar('\n');
         }
 
-        _cleanup_(osc_context_closep) sd_id128_t osc_context_id = SD_ID128_NULL;
-        if (!terminal_is_dumb()) {
-                r = osc_context_open_container(name, /* ret_seq= */ NULL, &osc_context_id);
-                if (r < 0)
-                        return r;
-        }
+        if (i->type)
+                printf("\t    Type: %s\n", i->type);
 
-        r = sd_event_set_signal_exit(event, true);
-        if (r < 0)
-                return log_error_errno(r, "Failed to enable SIGINT/SITERM handling: %m");
+        if (i->path)
+                printf("\t    Path: %s\n", i->path);
 
-        _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
-        r = pty_forward_new(event, master, flags, &forward);
-        if (r < 0)
-                return log_error_errno(r, "Failed to create PTY forwarder: %m");
+        (void) print_image_hostname(bus, i->name);
+        (void) print_image_machine_id(bus, i->name);
+        (void) print_image_machine_info(bus, i->name);
 
-        /* No userdata should not set previously. */
-        assert_se(!sd_bus_slot_set_userdata(machine_removed_slot, forward));
+        print_os_release(bus, "GetImageOSRelease", i->name, "\t      OS: ");
 
-        r = sd_event_loop(event);
-        if (r < 0)
-                return log_error_errno(r, "Failed to run event loop: %m");
+        printf("\t      RO: %s%s%s\n",
+               i->read_only ? ansi_highlight_red() : "",
+               i->read_only ? "read-only" : "writable",
+               i->read_only ? ansi_normal() : "");
 
-        bool machine_died =
-                FLAGS_SET(flags, PTY_FORWARD_IGNORE_VHANGUP) &&
-                !pty_forward_vhangup_honored(forward);
+        if (timestamp_is_set(i->crtime))
+                printf("\t Created: %s; %s\n",
+                       FORMAT_TIMESTAMP(i->crtime), FORMAT_TIMESTAMP_RELATIVE(i->crtime));
 
-        if (!arg_quiet) {
-                if (machine_died)
-                        log_info("Machine %s terminated.", name);
-                else if (streq(name, ".host"))
-                        log_info("Connection to the local host terminated.");
+        if (timestamp_is_set(i->mtime))
+                printf("\tModified: %s; %s\n",
+                       FORMAT_TIMESTAMP(i->mtime), FORMAT_TIMESTAMP_RELATIVE(i->mtime));
+
+        if (i->usage != UINT64_MAX) {
+                if (i->usage_exclusive != i->usage && i->usage_exclusive != UINT64_MAX)
+                        printf("\t   Usage: %s (exclusive: %s)\n",
+                               FORMAT_BYTES(i->usage), FORMAT_BYTES(i->usage_exclusive));
                 else
-                        log_info("Connection to machine %s terminated.", name);
+                        printf("\t   Usage: %s\n", FORMAT_BYTES(i->usage));
         }
 
-        return 0;
+        if (i->limit != UINT64_MAX) {
+                if (i->limit_exclusive != i->limit && i->limit_exclusive != UINT64_MAX)
+                        printf("\t   Limit: %s (exclusive: %s)\n",
+                               FORMAT_BYTES(i->limit), FORMAT_BYTES(i->limit_exclusive));
+                else
+                        printf("\t   Limit: %s\n", FORMAT_BYTES(i->limit));
+        }
 }
 
-static int parse_machine_uid(const char *spec, const char **machine, char **uid) {
-        /*
-         * Whatever is specified in the spec takes priority over global arguments.
-         */
-        char *_uid = NULL;
-        const char *_machine = NULL;
+static int show_image_info(sd_bus *bus, const char *path, bool *new_line) {
+        static const struct bus_properties_map map[]  = {
+                { "Name",                  "s",  NULL, offsetof(ImageStatusInfo, name)            },
+                { "Path",                  "s",  NULL, offsetof(ImageStatusInfo, path)            },
+                { "Type",                  "s",  NULL, offsetof(ImageStatusInfo, type)            },
+                { "ReadOnly",              "b",  NULL, offsetof(ImageStatusInfo, read_only)       },
+                { "CreationTimestamp",     "t",  NULL, offsetof(ImageStatusInfo, crtime)          },
+                { "ModificationTimestamp", "t",  NULL, offsetof(ImageStatusInfo, mtime)           },
+                { "Usage",                 "t",  NULL, offsetof(ImageStatusInfo, usage)           },
+                { "Limit",                 "t",  NULL, offsetof(ImageStatusInfo, limit)           },
+                { "UsageExclusive",        "t",  NULL, offsetof(ImageStatusInfo, usage_exclusive) },
+                { "LimitExclusive",        "t",  NULL, offsetof(ImageStatusInfo, limit_exclusive) },
+                {}
+        };
 
-        assert(uid);
-        assert(machine);
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+        ImageStatusInfo info = {};
+        int r;
 
-        if (spec) {
-                const char *at;
+        assert(bus);
+        assert(path);
+        assert(new_line);
 
-                at = strchr(spec, '@');
-                if (at) {
-                        if (at == spec)
-                                /* Do the same as ssh and refuse "@host". */
-                                return -EINVAL;
+        r = bus_map_all_properties(bus,
+                                   "org.freedesktop.machine1",
+                                   path,
+                                   map,
+                                   BUS_MAP_BOOLEAN_AS_BOOL,
+                                   &error,
+                                   &m,
+                                   &info);
+        if (r < 0)
+                return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
 
-                        _machine = at + 1;
-                        _uid = strndup(spec, at - spec);
-                        if (!_uid)
-                                return -ENOMEM;
-                } else
-                        _machine = spec;
-        };
+        if (*new_line)
+                printf("\n");
+        *new_line = true;
 
-        if (arg_uid && !_uid) {
-                _uid = strdup(arg_uid);
-                if (!_uid)
-                        return -ENOMEM;
-        }
+        print_image_status_info(bus, &info);
 
-        *uid = _uid;
-        *machine = isempty(_machine) ? ".host" : _machine;
-        return 0;
+        return r;
 }
 
-static int verb_login_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL;
-        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
-        int master = -1, r;
-        sd_bus *bus = ASSERT_PTR(userdata);
-        const char *match, *machine;
-
-        if (!strv_isempty(arg_setenv) || arg_uid)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "--setenv= and --uid= are not supported for 'login'. Use 'shell' instead.");
+typedef struct PoolStatusInfo {
+        const char *path;
+        uint64_t usage;
+        uint64_t limit;
+} PoolStatusInfo;
 
-        if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE))
-                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
-                                       "Login only supported on local machines.");
+static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) {
+        if (i->path)
+                printf("\t    Path: %s\n", i->path);
 
-        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+        if (i->usage != UINT64_MAX)
+                printf("\t   Usage: %s\n", FORMAT_BYTES(i->usage));
 
-        r = sd_event_default(&event);
-        if (r < 0)
-                return log_error_errno(r, "Failed to get event loop: %m");
+        if (i->limit != UINT64_MAX)
+                printf("\t   Limit: %s\n", FORMAT_BYTES(i->limit));
+}
 
-        r = sd_bus_attach_event(bus, event, 0);
-        if (r < 0)
-                return log_error_errno(r, "Failed to attach bus to event loop: %m");
+static int show_pool_info(sd_bus *bus) {
+        static const struct bus_properties_map map[]  = {
+                { "PoolPath",  "s",  NULL, offsetof(PoolStatusInfo, path)  },
+                { "PoolUsage", "t",  NULL, offsetof(PoolStatusInfo, usage) },
+                { "PoolLimit", "t",  NULL, offsetof(PoolStatusInfo, limit) },
+                {}
+        };
 
-        machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1];
+        PoolStatusInfo info = {
+                .usage = UINT64_MAX,
+                .limit = UINT64_MAX,
+        };
 
-        match = strjoina("type='signal',"
-                         "sender='org.freedesktop.machine1',"
-                         "path='/org/freedesktop/machine1',",
-                         "interface='org.freedesktop.machine1.Manager',"
-                         "member='MachineRemoved',"
-                         "arg0='", machine, "'");
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+        int r;
 
-        r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL);
-        if (r < 0)
-                return log_error_errno(r, "Failed to request machine removal match: %m");
+        assert(bus);
 
-        r = bus_call_method(bus, bus_machine_mgr, "OpenMachineLogin", &error, &reply, "s", machine);
+        r = bus_map_all_properties(bus,
+                                   "org.freedesktop.machine1",
+                                   "/org/freedesktop/machine1",
+                                   map,
+                                   0,
+                                   &error,
+                                   &m,
+                                   &info);
         if (r < 0)
-                return log_error_errno(r, "Failed to get login PTY: %s", bus_error_message(&error, r));
+                return log_error_errno(r, "Could not get properties: %s", bus_error_message(&error, r));
 
-        r = sd_bus_message_read(reply, "hs", &master, NULL);
-        if (r < 0)
-                return bus_log_parse_error(r);
+        print_pool_status_info(bus, &info);
 
-        return process_forward(event, slot, master, PTY_FORWARD_IGNORE_VHANGUP, machine);
+        return 0;
 }
 
-static int verb_shell_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL;
-        _cleanup_(sd_event_unrefp) sd_event *event = NULL;
-        int master = -1, r;
-        sd_bus *bus = ASSERT_PTR(userdata);
-        const char *match, *machine, *path;
-        _cleanup_free_ char *uid = NULL;
-
-        if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE))
-                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
-                                       "Shell only supported on local machines.");
-
-        if (terminal_is_dumb()) {
-                /* Set TERM=dumb if we are running on a dumb terminal or with a pipe.
-                 * Otherwise, we will get unwanted OSC sequences. */
-                if (!strv_find_prefix(arg_setenv, "TERM="))
-                        if (strv_extend(&arg_setenv, "TERM=dumb") < 0)
-                                return log_oom();
-        } else
-                /* Pass $TERM & Co. to shell session, if not explicitly specified. */
-                FOREACH_STRING(v, "TERM=", "COLORTERM=", "NO_COLOR=") {
-                        if (strv_find_prefix(arg_setenv, v))
-                                continue;
+static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) {
+        int r;
 
-                        const char *t = strv_find_prefix(environ, v);
-                        if (!t)
-                                continue;
+        assert(bus);
+        assert(path);
+        assert(new_line);
 
-                        if (strv_extend(&arg_setenv, t) < 0)
-                                return log_oom();
-                }
+        if (*new_line)
+                printf("\n");
 
-        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+        *new_line = true;
 
-        r = sd_event_default(&event);
+        r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, NULL, arg_property, arg_print_flags, NULL);
         if (r < 0)
-                return log_error_errno(r, "Failed to get event loop: %m");
+                log_error_errno(r, "Could not get properties: %m");
 
-        r = sd_bus_attach_event(bus, event, 0);
-        if (r < 0)
-                return log_error_errno(r, "Failed to attach bus to event loop: %m");
+        return r;
+}
 
-        r = parse_machine_uid(argc >= 2 ? argv[1] : NULL, &machine, &uid);
-        if (r < 0)
-                return log_error_errno(r, "Failed to parse machine specification: %m");
+static int verb_show_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        bool properties, new_line = false;
+        sd_bus *bus = ASSERT_PTR(userdata);
+        int r = 0;
 
-        match = strjoina("type='signal',"
-                         "sender='org.freedesktop.machine1',"
-                         "path='/org/freedesktop/machine1',",
-                         "interface='org.freedesktop.machine1.Manager',"
-                         "member='MachineRemoved',"
-                         "arg0='", machine, "'");
+        properties = !strstr(argv[0], "status");
 
-        r = sd_bus_add_match_async(bus, &slot, match, on_machine_removed, NULL, NULL);
-        if (r < 0)
-                return log_error_errno(r, "Failed to request machine removal match: %m");
+        pager_open(arg_pager_flags);
 
-        r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "OpenMachineShell");
-        if (r < 0)
-                return bus_log_create_error(r);
+        if (argc <= 1) {
 
-        path = argc < 3 || isempty(argv[2]) ? NULL : argv[2];
+                /* If no argument is specified, inspect the manager
+                 * itself */
 
-        r = sd_bus_message_append(m, "sss", machine, uid, path);
-        if (r < 0)
-                return bus_log_create_error(r);
+                if (properties)
+                        r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line);
+                else
+                        r = show_pool_info(bus);
+                if (r < 0)
+                        return r;
+        }
 
-        r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2);
-        if (r < 0)
-                return bus_log_create_error(r);
+        for (int i = 1; i < argc; i++) {
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+                const char *path = NULL;
 
-        r = sd_bus_message_append_strv(m, arg_setenv);
-        if (r < 0)
-                return bus_log_create_error(r);
+                r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, &reply, "s", argv[i]);
+                if (r < 0)
+                        return log_error_errno(r, "Could not get path to image: %s", bus_error_message(&error, r));
 
-        r = sd_bus_call(bus, m, 0, &error, &reply);
-        if (r < 0)
-                return log_error_errno(r, "Failed to get shell PTY: %s", bus_error_message(&error, r));
+                r = sd_bus_message_read(reply, "o", &path);
+                if (r < 0)
+                        return bus_log_parse_error(r);
 
-        r = sd_bus_message_read(reply, "hs", &master, NULL);
-        if (r < 0)
-                return bus_log_parse_error(r);
+                if (properties)
+                        r = show_image_properties(bus, path, &new_line);
+                else
+                        r = show_image_info(bus, path, &new_line);
+        }
 
-        return process_forward(event, slot, master, /* flags= */ 0, machine);
+        return r;
 }
 
 static int normalize_nspawn_filename(const char *name, char **ret_file) {
@@ -1873,301 +1943,113 @@ static int verb_cat_settings(int argc, char *argv[], uintptr_t _data, void *user
                 if (q < 0)
                         return r < 0 ? r : q;
 
-                q = get_settings_path(file, &path);
-                if (q == -ENOENT) {
-                        log_error_errno(q, "No settings file found for machine '%s'.", *name);
-                        r = r < 0 ? r : q;
-                        continue;
-                }
-                if (q < 0) {
-                        log_error_errno(q, "Failed to get the path of the settings file: %m");
-                        return r < 0 ? r : q;
-                }
-
-                q = cat_files(path, /* dropins= */ NULL, /* flags= */ CAT_FORMAT_HAS_SECTIONS);
-                if (q < 0)
-                        return r < 0 ? r : q;
-        }
-
-        return r;
-}
-
-static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        sd_bus *bus = ASSERT_PTR(userdata);
-        int r;
-
-        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
-
-        for (int i = 1; i < argc; i++) {
-                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
-
-                r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RemoveImage");
-                if (r < 0)
-                        return bus_log_create_error(r);
-
-                r = sd_bus_message_append(m, "s", argv[i]);
-                if (r < 0)
-                        return bus_log_create_error(r);
-
-                /* This is a slow operation, hence turn off any method call timeouts */
-                r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
-                if (r < 0)
-                        return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
-        }
-
-        return 0;
-}
-
-static int verb_rename_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        sd_bus *bus = ASSERT_PTR(userdata);
-        int r;
-
-        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
-
-        r = bus_call_method(
-                        bus,
-                        bus_machine_mgr,
-                        "RenameImage",
-                        &error,
-                        NULL,
-                        "ss", argv[1], argv[2]);
-        if (r < 0)
-                return log_error_errno(r, "Could not rename image: %s", bus_error_message(&error, r));
-
-        return 0;
-}
-
-static int verb_clone_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
-        sd_bus *bus = ASSERT_PTR(userdata);
-        int r;
-
-        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
-
-        r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "CloneImage");
-        if (r < 0)
-                return bus_log_create_error(r);
-
-        r = sd_bus_message_append(m, "ssb", argv[1], argv[2], arg_read_only);
-        if (r < 0)
-                return bus_log_create_error(r);
-
-        /* This is a slow operation, hence turn off any method call timeouts */
-        r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
-        if (r < 0)
-                return log_error_errno(r, "Could not clone image: %s", bus_error_message(&error, r));
-
-        return 0;
-}
-
-static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        sd_bus *bus = ASSERT_PTR(userdata);
-        int b = true, r;
-
-        if (argc > 2) {
-                b = parse_boolean(argv[2]);
-                if (b < 0)
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                               "Failed to parse boolean argument: %s",
-                                               argv[2]);
-        }
-
-        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
-
-        r = bus_call_method(bus, bus_machine_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b);
-        if (r < 0)
-                return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
-
-        return 0;
-}
-
-static int image_exists(sd_bus *bus, const char *name) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        int r;
-
-        assert(bus);
-        assert(name);
-
-        r = bus_call_method(bus, bus_machine_mgr, "GetImage", &error, NULL, "s", name);
-        if (r < 0) {
-                if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_IMAGE))
-                        return 0;
-
-                return log_error_errno(r, "Failed to check whether image %s exists: %s", name, bus_error_message(&error, r));
-        }
-
-        return 1;
-}
-
-static int make_service_name(const char *name, char **ret) {
-        int r;
-
-        assert(name);
-        assert(ret);
-        assert(arg_runner >= 0 && arg_runner < _RUNNER_MAX);
-
-        if (!hostname_is_valid(name, 0))
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "Invalid machine name %s.", name);
-
-        r = unit_name_build(machine_runner_unit_prefix_table[arg_runner], name, ".service", ret);
-        if (r < 0)
-                return log_error_errno(r, "Failed to build unit name: %m");
-
-        return 0;
-}
-
-static int verb_start_machine(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
-        sd_bus *bus = ASSERT_PTR(userdata);
-        int r;
-
-        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
-        ask_password_agent_open_if_enabled(arg_transport, arg_ask_password);
-
-        r = bus_wait_for_jobs_new(bus, &w);
-        if (r < 0)
-                return log_error_errno(r, "Could not watch jobs: %m");
-
-        for (int i = 1; i < argc; i++) {
-                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
-                _cleanup_free_ char *unit = NULL;
-                const char *object;
-
-                r = make_service_name(argv[i], &unit);
-                if (r < 0)
-                        return r;
-
-                r = image_exists(bus, argv[i]);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
-                                               "Machine image '%s' does not exist.",
-                                               argv[i]);
-
-                r = bus_call_method(
-                                bus,
-                                bus_systemd_mgr,
-                                "StartUnit",
-                                &error,
-                                &reply,
-                                "ss", unit, "fail");
-                if (r < 0)
-                        return log_error_errno(r, "Failed to start unit: %s", bus_error_message(&error, r));
-
-                r = sd_bus_message_read(reply, "o", &object);
-                if (r < 0)
-                        return bus_log_parse_error(r);
-
-                r = bus_wait_for_jobs_add(w, object);
-                if (r < 0)
-                        return log_oom();
-        }
+                q = get_settings_path(file, &path);
+                if (q == -ENOENT) {
+                        log_error_errno(q, "No settings file found for machine '%s'.", *name);
+                        r = r < 0 ? r : q;
+                        continue;
+                }
+                if (q < 0) {
+                        log_error_errno(q, "Failed to get the path of the settings file: %m");
+                        return r < 0 ? r : q;
+                }
 
-        r = bus_wait_for_jobs(w, arg_quiet, NULL);
-        if (r < 0)
-                return r;
+                q = cat_files(path, /* dropins= */ NULL, /* flags= */ CAT_FORMAT_HAS_SECTIONS);
+                if (q < 0)
+                        return r < 0 ? r : q;
+        }
 
-        return 0;
+        return r;
 }
 
-static int verb_enable_machine(int argc, char *argv[], uintptr_t data, void *userdata) {
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+static int verb_clone_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        const char *method;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
         sd_bus *bus = ASSERT_PTR(userdata);
         int r;
-        bool enable;
 
         (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
 
-        enable = streq(argv[0], "enable");
-        method = enable ? "EnableUnitFiles" : "DisableUnitFiles";
-
-        r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, method);
+        r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "CloneImage");
         if (r < 0)
                 return bus_log_create_error(r);
 
-        r = sd_bus_message_open_container(m, 'a', "s");
+        r = sd_bus_message_append(m, "ssb", argv[1], argv[2], arg_read_only);
         if (r < 0)
                 return bus_log_create_error(r);
 
-        if (enable) {
-                r = sd_bus_message_append(m, "s", "machines.target");
-                if (r < 0)
-                        return bus_log_create_error(r);
-        }
-
-        for (int i = 1; i < argc; i++) {
-                _cleanup_free_ char *unit = NULL;
+        /* This is a slow operation, hence turn off any method call timeouts */
+        r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Could not clone image: %s", bus_error_message(&error, r));
 
-                r = make_service_name(argv[i], &unit);
-                if (r < 0)
-                        return r;
+        return 0;
+}
 
-                r = image_exists(bus, argv[i]);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
-                                               "Machine image '%s' does not exist.",
-                                               argv[i]);
+static int verb_rename_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        sd_bus *bus = ASSERT_PTR(userdata);
+        int r;
 
-                r = sd_bus_message_append(m, "s", unit);
-                if (r < 0)
-                        return bus_log_create_error(r);
-        }
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
 
-        r = sd_bus_message_close_container(m);
+        r = bus_call_method(
+                        bus,
+                        bus_machine_mgr,
+                        "RenameImage",
+                        &error,
+                        NULL,
+                        "ss", argv[1], argv[2]);
         if (r < 0)
-                return bus_log_create_error(r);
+                return log_error_errno(r, "Could not rename image: %s", bus_error_message(&error, r));
 
-        if (enable)
-                r = sd_bus_message_append(m, "bb", false, false);
-        else
-                r = sd_bus_message_append(m, "b", false);
-        if (r < 0)
-                return bus_log_create_error(r);
+        return 0;
+}
 
-        r = sd_bus_call(bus, m, 0, &error, &reply);
-        if (r < 0)
-                return log_error_errno(r, "Failed to enable or disable unit: %s", bus_error_message(&error, r));
+static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        sd_bus *bus = ASSERT_PTR(userdata);
+        int b = true, r;
 
-        if (enable) {
-                r = sd_bus_message_read(reply, "b", NULL);
-                if (r < 0)
-                        return bus_log_parse_error(r);
+        if (argc > 2) {
+                b = parse_boolean(argv[2]);
+                if (b < 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                               "Failed to parse boolean argument: %s",
+                                               argv[2]);
         }
 
-        r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
-        if (r < 0)
-                return r;
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
 
-        r = bus_service_manager_reload(bus);
+        r = bus_call_method(bus, bus_machine_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b);
         if (r < 0)
-                return r;
+                return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r));
 
-        if (arg_now) {
-                _cleanup_strv_free_ char **new_args = NULL;
+        return 0;
+}
 
-                new_args = strv_new(enable ? "start" : "poweroff");
-                if (!new_args)
-                        return log_oom();
+static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        sd_bus *bus = ASSERT_PTR(userdata);
+        int r;
 
-                r = strv_extend_strv(&new_args, argv + 1, /* filter_duplicates= */ false);
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        for (int i = 1; i < argc; i++) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+                r = bus_message_new_method_call(bus, &m, bus_machine_mgr, "RemoveImage");
                 if (r < 0)
-                        return log_oom();
+                        return bus_log_create_error(r);
 
-                if (enable)
-                        return verb_start_machine(strv_length(new_args), new_args, data, userdata);
+                r = sd_bus_message_append(m, "s", argv[i]);
+                if (r < 0)
+                        return bus_log_create_error(r);
 
-                return verb_poweroff_machine(strv_length(new_args), new_args, data, userdata);
+                /* This is a slow operation, hence turn off any method call timeouts */
+                r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r));
         }
 
         return 0;
@@ -2257,6 +2139,120 @@ static int verb_clean_images(int argc, char *argv[], uintptr_t _data, void *user
         return 0;
 }
 
+static int verb_bind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        int r;
+
+        if (arg_transport != BUS_TRANSPORT_LOCAL)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                       "bind-volume is only supported on the local transport.");
+
+        _cleanup_(bind_volume_freep) BindVolume *bv = NULL;
+        r = bind_volume_parse(argv[2], &bv);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse bind-volume argument '%s': %m", argv[2]);
+
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        /* Locate and connect to the target machine before acquiring storage, so a missing
+         * machine doesn't trigger 'create=new' side effects on the StorageProvider. */
+        _cleanup_free_ char *address = NULL;
+        r = machine_get_control_address(argv[1], &address);
+        if (r == -EOPNOTSUPP)
+                return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]);
+        if (r < 0)
+                return r;
+
+        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
+        r = sd_varlink_connect_address(&vl, address);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to machine control socket %s: %m", address);
+
+        r = sd_varlink_set_allow_fd_passing_output(vl, true);
+        if (r < 0)
+                return log_error_errno(r, "Failed to enable fd passing on varlink connection: %m");
+
+        _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT;
+        _cleanup_free_ char *acquire_error_id = NULL;
+        r = storage_acquire_volume(arg_runtime_scope, bv, arg_ask_password, &acquire_error_id, &reply);
+        if (r < 0) {
+                if (acquire_error_id)
+                        return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %s",
+                                               bv->provider, bv->volume, acquire_error_id);
+                return log_error_errno(r, "Failed to acquire storage volume '%s:%s' from provider: %m",
+                                       bv->provider, bv->volume);
+        }
+
+        int fd_index = sd_varlink_push_fd(vl, reply.fd);
+        if (fd_index < 0)
+                return log_error_errno(fd_index, "Failed to push storage fd onto varlink connection: %m");
+        TAKE_FD(reply.fd);
+
+        _cleanup_free_ char *name = strjoin(bv->provider, ":", bv->volume);
+        if (!name)
+                return log_oom();
+
+        sd_json_variant *vl_reply = NULL;
+        const char *error_id = NULL;
+        r = sd_varlink_callbo(
+                        vl,
+                        "io.systemd.MachineInstance.AddStorage",
+                        &vl_reply, &error_id,
+                        SD_JSON_BUILD_PAIR_INTEGER("fileDescriptorIndex", fd_index),
+                        SD_JSON_BUILD_PAIR_STRING("name", name),
+                        JSON_BUILD_PAIR_STRING_NON_EMPTY("config", bv->config));
+        if (r < 0)
+                return log_error_errno(r, "Failed to call io.systemd.MachineInstance.AddStorage: %m");
+        if (error_id)
+                return log_error_errno(sd_varlink_error_to_errno(error_id, vl_reply),
+                                       "AddStorage failed for '%s': %s", name, error_id);
+
+        return 0;
+}
+
+static int verb_unbind_volume(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        int r;
+
+        if (arg_transport != BUS_TRANSPORT_LOCAL)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                       "unbind-volume is only supported on the local transport.");
+
+        r = machine_storage_name_split(argv[2], /* ret_provider= */ NULL, /* ret_volume= */ NULL);
+        if (r == -ENOMEM)
+                return log_oom();
+        if (r < 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Invalid unbind-volume name '%s', expected '<provider>:<volume>'.", argv[2]);
+
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        _cleanup_free_ char *address = NULL;
+        r = machine_get_control_address(argv[1], &address);
+        if (r == -EOPNOTSUPP)
+                return log_error_errno(r, "Machine '%s' does not expose a varlink control socket.", argv[1]);
+        if (r < 0)
+                return r;
+
+        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
+        r = sd_varlink_connect_address(&vl, address);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to machine control socket %s: %m", address);
+
+        sd_json_variant *reply = NULL;
+        const char *error_id = NULL;
+        r = sd_varlink_callbo(
+                        vl,
+                        "io.systemd.MachineInstance.RemoveStorage",
+                        &reply, &error_id,
+                        SD_JSON_BUILD_PAIR_STRING("name", argv[2]));
+        if (r < 0)
+                return log_error_errno(r, "Failed to call io.systemd.MachineInstance.RemoveStorage: %m");
+        if (error_id)
+                return log_error_errno(sd_varlink_error_to_errno(error_id, reply),
+                                       "RemoveStorage failed for '%s': %s", argv[2], error_id);
+
+        return 0;
+}
+
 static int chainload_importctl(int argc, char *argv[]) {
         int r;