chiark / gitweb /
machinectl: add "enable" and "disable" verbs for enabling/disabling systemd-nspawn...
[elogind.git] / src / machine / machinectl.c
index b9e8381e6c57cac9c6d8823d6a3cf5ed7378146d..0abc251cf808f1815ea40939b1184b682b6a8b9f 100644 (file)
@@ -63,6 +63,7 @@ static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
 static char *arg_host = NULL;
 static bool arg_read_only = false;
 static bool arg_mkdir = false;
+static bool arg_quiet = false;
 
 static void pager_open_if_enabled(void) {
 
@@ -129,6 +130,9 @@ typedef struct ImageInfo {
         const char *name;
         const char *type;
         bool read_only;
+        usec_t crtime;
+        usec_t mtime;
+        uint64_t size;
 } ImageInfo;
 
 static int compare_image_info(const void *a, const void *b) {
@@ -140,14 +144,14 @@ static int compare_image_info(const void *a, const void *b) {
 static int list_images(int argc, char *argv[], void *userdata) {
 
         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
-        size_t max_name = strlen("NAME"), max_type = strlen("TYPE");
+        size_t max_name = strlen("NAME"), max_type = strlen("TYPE"), max_size = strlen("SIZE"), max_crtime = strlen("CREATED"), max_mtime = strlen("MODIFIED");
         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
         _cleanup_free_ ImageInfo *images = NULL;
         size_t n_images = 0, n_allocated = 0, j;
         const char *name, *type, *object;
         sd_bus *bus = userdata;
-        int read_only;
-        int r;
+        uint64_t crtime, mtime, size;
+        int read_only, r;
 
         assert(bus);
 
@@ -167,11 +171,13 @@ static int list_images(int argc, char *argv[], void *userdata) {
                 return r;
         }
 
-        r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbo)");
+        r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)");
         if (r < 0)
                 return bus_log_parse_error(r);
 
-        while ((r = sd_bus_message_read(reply, "(ssbo)", &name, &type, &read_only, &object)) > 0) {
+        while ((r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &read_only, &crtime, &mtime, &size, &object)) > 0) {
+                char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_BYTES_MAX)];
+                size_t l;
 
                 if (name[0] == '.' && !arg_all)
                         continue;
@@ -182,12 +188,35 @@ static int list_images(int argc, char *argv[], void *userdata) {
                 images[n_images].name = name;
                 images[n_images].type = type;
                 images[n_images].read_only = read_only;
+                images[n_images].crtime = crtime;
+                images[n_images].mtime = mtime;
+                images[n_images].size = size;
+
+                l = strlen(name);
+                if (l > max_name)
+                        max_name = l;
+
+                l = strlen(type);
+                if (l > max_type)
+                        max_type = l;
+
+                if (crtime != 0) {
+                        l = strlen(strna(format_timestamp(buf, sizeof(buf), crtime)));
+                        if (l > max_crtime)
+                                max_crtime = l;
+                }
 
-                if (strlen(name) > max_name)
-                        max_name = strlen(name);
+                if (mtime != 0) {
+                        l = strlen(strna(format_timestamp(buf, sizeof(buf), mtime)));
+                        if (l > max_mtime)
+                                max_mtime = l;
+                }
 
-                if (strlen(type) > max_type)
-                        max_type = strlen(type);
+                if (size != (uint64_t) -1) {
+                        l = strlen(strna(format_bytes(buf, sizeof(buf), size)));
+                        if (l > max_size)
+                                max_size = l;
+                }
 
                 n_images++;
         }
@@ -201,13 +230,24 @@ static int list_images(int argc, char *argv[], void *userdata) {
         qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info);
 
         if (arg_legend)
-                printf("%-*s %-*s %-3s\n", (int) max_name, "NAME", (int) max_type, "TYPE", "RO");
+                printf("%-*s %-*s %-3s %-*s %-*s %-*s\n",
+                       (int) max_name, "NAME",
+                       (int) max_type, "TYPE",
+                       "RO",
+                       (int) max_size, "SIZE",
+                       (int) max_crtime, "CREATED",
+                       (int) max_mtime, "MODIFIED");
 
         for (j = 0; j < n_images; j++) {
-                printf("%-*s %-*s %-3s\n",
+                char crtime_buf[FORMAT_TIMESTAMP_MAX], mtime_buf[FORMAT_TIMESTAMP_MAX], size_buf[FORMAT_BYTES_MAX];
+
+                printf("%-*s %-*s %s%-3s%s %-*s %-*s %-*s\n",
                        (int) max_name, images[j].name,
                        (int) max_type, images[j].type,
-                       yes_no(images[j].read_only));
+                       images[j].read_only ? ansi_highlight_red() : "", yes_no(images[j].read_only), images[j].read_only ? ansi_highlight_off() : "",
+                       (int) max_size, strna(format_bytes(size_buf, sizeof(size_buf), images[j].size)),
+                       (int) max_crtime, strna(format_timestamp(crtime_buf, sizeof(crtime_buf), images[j].crtime)),
+                       (int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime)));
         }
 
         if (r < 0)
@@ -496,7 +536,7 @@ static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_
         return 0;
 }
 
-static int show_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
+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) },
@@ -543,7 +583,7 @@ static int show_info(const char *verb, sd_bus *bus, const char *path, bool *new_
         return r;
 }
 
-static int show_properties(sd_bus *bus, const char *path, bool *new_line) {
+static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line) {
         int r;
 
         assert(bus);
@@ -562,7 +602,7 @@ static int show_properties(sd_bus *bus, const char *path, bool *new_line) {
         return r;
 }
 
-static int show(int argc, char *argv[], void *userdata) {
+static int show_machine(int argc, char *argv[], void *userdata) {
 
         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
@@ -580,7 +620,7 @@ static int show(int argc, char *argv[], void *userdata) {
 
                 /* If no argument is specified, inspect the manager
                  * itself */
-                r = show_properties(bus, "/org/freedesktop/machine1", &new_line);
+                r = show_machine_properties(bus, "/org/freedesktop/machine1", &new_line);
                 if (r < 0)
                         return r;
         }
@@ -607,9 +647,193 @@ static int show(int argc, char *argv[], void *userdata) {
                         return bus_log_parse_error(r);
 
                 if (properties)
-                        r = show_properties(bus, path, &new_line);
+                        r = show_machine_properties(bus, path, &new_line);
                 else
-                        r = show_info(argv[0], bus, path, &new_line);
+                        r = show_machine_info(argv[0], bus, path, &new_line);
+        }
+
+        return r;
+}
+
+typedef struct ImageStatusInfo {
+        char *name;
+        char *path;
+        char *type;
+        int read_only;
+        usec_t crtime;
+        usec_t mtime;
+        uint64_t size;
+        uint64_t limit;
+        uint64_t size_exclusive;
+        uint64_t limit_exclusive;
+} ImageStatusInfo;
+
+static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) {
+        char ts_relative[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
+        char ts_absolute[FORMAT_TIMESTAMP_MAX], *s2;
+        char bs[FORMAT_BYTES_MAX], *s3;
+        char bs_exclusive[FORMAT_BYTES_MAX], *s4;
+
+        assert(bus);
+        assert(i);
+
+        if (i->name) {
+                fputs(i->name, stdout);
+                putchar('\n');
+        }
+
+        if (i->path)
+                printf("\t    Type: %s\n", i->type);
+
+        if (i->path)
+                printf("\t    Path: %s\n", i->path);
+
+        printf("\t      RO: %s%s%s\n",
+               i->read_only ? ansi_highlight_red() : "",
+               i->read_only ? "read-only" : "writable",
+               i->read_only ? ansi_highlight_off() : "");
+
+        s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->crtime);
+        s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->crtime);
+        if (s1 && s2)
+                printf("\t Created: %s; %s\n", s2, s1);
+        else if (s2)
+                printf("\t Created: %s\n", s2);
+
+        s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->mtime);
+        s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->mtime);
+        if (s1 && s2)
+                printf("\tModified: %s; %s\n", s2, s1);
+        else if (s2)
+                printf("\tModified: %s\n", s2);
+
+        s3 = format_bytes(bs, sizeof(bs), i->size);
+        s4 = i->size_exclusive != i->size ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->size_exclusive) : NULL;
+        if (s3 && s4)
+                printf("\t    Size: %s (exclusive: %s)\n", s3, s4);
+        else if (s3)
+                printf("\t    Size: %s\n", s3);
+
+        s3 = format_bytes(bs, sizeof(bs), i->limit);
+        s4 = i->limit_exclusive != i->limit ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->limit_exclusive) : NULL;
+        if (s3 && s4)
+                printf("\t   Limit: %s (exclusive: %s)\n", s3, s4);
+        else if (s3)
+                printf("\t   Limit: %s\n", s3);
+}
+
+static int show_image_info(const char *verb, 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)           },
+                { "Size",                  "t",  NULL, offsetof(ImageStatusInfo, size)            },
+                { "Limit",                 "t",  NULL, offsetof(ImageStatusInfo, limit)           },
+                { "SizeExclusive",         "t",  NULL, offsetof(ImageStatusInfo, size_exclusive)  },
+                { "LimitExclusive",        "t",  NULL, offsetof(ImageStatusInfo, limit_exclusive) },
+                {}
+        };
+
+        ImageStatusInfo info = {};
+        int r;
+
+        assert(verb);
+        assert(bus);
+        assert(path);
+        assert(new_line);
+
+        r = bus_map_all_properties(bus,
+                                   "org.freedesktop.machine1",
+                                   path,
+                                   map,
+                                   &info);
+        if (r < 0)
+                return log_error_errno(r, "Could not get properties: %m");
+
+        if (*new_line)
+                printf("\n");
+        *new_line = true;
+
+        print_image_status_info(bus, &info);
+
+        free(info.name);
+        free(info.path);
+        free(info.type);
+
+        return r;
+}
+
+static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) {
+        int r;
+
+        assert(bus);
+        assert(path);
+        assert(new_line);
+
+        if (*new_line)
+                printf("\n");
+
+        *new_line = true;
+
+        r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all);
+        if (r < 0)
+                log_error_errno(r, "Could not get properties: %m");
+
+        return r;
+}
+
+static int show_image(int argc, char *argv[], void *userdata) {
+
+        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+        bool properties, new_line = false;
+        sd_bus *bus = userdata;
+        int r = 0, i;
+
+        assert(bus);
+
+        properties = !strstr(argv[0], "status");
+
+        pager_open_if_enabled();
+
+        if (properties && argc <= 1) {
+
+                /* If no argument is specified, inspect the manager
+                 * itself */
+                r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line);
+                if (r < 0)
+                        return r;
+        }
+
+        for (i = 1; i < argc; i++) {
+                const char *path = NULL;
+
+                r = sd_bus_call_method(
+                                        bus,
+                                        "org.freedesktop.machine1",
+                                        "/org/freedesktop/machine1",
+                                        "org.freedesktop.machine1.Manager",
+                                        "GetImage",
+                                        &error,
+                                        &reply,
+                                        "s", argv[i]);
+                if (r < 0) {
+                        log_error("Could not get path to image: %s", bus_error_message(&error, -r));
+                        return r;
+                }
+
+                r = sd_bus_message_read(reply, "o", &path);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                if (properties)
+                        r = show_image_properties(bus, path, &new_line);
+                else
+                        r = show_image_info(argv[0], bus, path, &new_line);
         }
 
         return r;
@@ -1012,20 +1236,19 @@ finish:
 
 static int login_machine(int argc, char *argv[], void *userdata) {
         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
-        _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
-        _cleanup_bus_close_unref_ sd_bus *container_bus = NULL;
+        _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
         _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
         _cleanup_event_unref_ sd_event *event = NULL;
-        _cleanup_free_ char *getty = NULL;
         int master = -1, r, ret = 0;
         sd_bus *bus = userdata;
-        const char *pty, *p;
+        const char *pty;
         sigset_t mask;
         char last_char = 0;
 
         assert(bus);
 
-        if (arg_transport != BUS_TRANSPORT_LOCAL) {
+        if (arg_transport != BUS_TRANSPORT_LOCAL &&
+            arg_transport != BUS_TRANSPORT_MACHINE) {
                 log_error("Login only supported on local machines.");
                 return -ENOTSUP;
         }
@@ -1038,53 +1261,32 @@ static int login_machine(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return log_error_errno(r, "Failed to attach bus to event loop: %m");
 
-        r = sd_bus_call_method(bus,
-                               "org.freedesktop.machine1",
-                               "/org/freedesktop/machine1",
-                               "org.freedesktop.machine1.Manager",
-                               "OpenMachinePTY",
-                               &error,
-                               &reply,
-                               "s", argv[1]);
-        if (r < 0) {
-                log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r));
-                return r;
-        }
-
-        r = sd_bus_message_read(reply, "hs", &master, &pty);
+        r = sd_bus_message_new_method_call(bus,
+                                           &m,
+                                           "org.freedesktop.machine1",
+                                           "/org/freedesktop/machine1",
+                                           "org.freedesktop.machine1.Manager",
+                                           "OpenMachineLogin");
         if (r < 0)
-                return bus_log_parse_error(r);
+                return bus_log_create_error(r);
 
-        p = startswith(pty, "/dev/pts/");
-        if (!p) {
-                log_error("Invalid pty name %s.", pty);
-                return -EIO;
-        }
-
-        r = sd_bus_open_system_container(&container_bus, argv[1]);
+        r = sd_bus_message_set_allow_interactive_authorization(m, true);
         if (r < 0)
-                return log_error_errno(r, "Failed to get container bus: %m");
-
-        getty = strjoin("container-getty@", p, ".service", NULL);
-        if (!getty)
-                return log_oom();
+                return bus_log_create_error(r);
 
-        if (unlockpt(master) < 0)
-                return log_error_errno(errno, "Failed to unlock tty: %m");
+        r = sd_bus_message_append(m, "s", argv[1]);
+        if (r < 0)
+                return bus_log_create_error(r);
 
-        r = sd_bus_call_method(container_bus,
-                               "org.freedesktop.systemd1",
-                               "/org/freedesktop/systemd1",
-                               "org.freedesktop.systemd1.Manager",
-                               "StartUnit",
-                               &error, NULL,
-                               "ss", getty, "replace");
+        r = sd_bus_call(bus, m, 0, &error, &reply);
         if (r < 0) {
-                log_error("Failed to start getty service: %s", bus_error_message(&error, r));
+                log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r));
                 return r;
         }
 
-        container_bus = sd_bus_unref(container_bus);
+        r = sd_bus_message_read(reply, "hs", &master, &pty);
+        if (r < 0)
+                return bus_log_parse_error(r);
 
         assert_se(sigemptyset(&mask) == 0);
         sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1);
@@ -1116,6 +1318,279 @@ static int login_machine(int argc, char *argv[], void *userdata) {
         return ret;
 }
 
+static int remove_image(int argc, char *argv[], void *userdata) {
+        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+        sd_bus *bus = userdata;
+        int r, i;
+
+        assert(bus);
+
+        for (i = 1; i < argc; i++) {
+                r = sd_bus_call_method(
+                                bus,
+                                "org.freedesktop.machine1",
+                                "/org/freedesktop/machine1",
+                                "org.freedesktop.machine1.Manager",
+                                "RemoveImage",
+                                &error,
+                                NULL,
+                                "s", argv[i]);
+                if (r < 0) {
+                        log_error("Could not remove image: %s", bus_error_message(&error, -r));
+                        return r;
+                }
+        }
+
+        return 0;
+}
+
+static int rename_image(int argc, char *argv[], void *userdata) {
+        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+        sd_bus *bus = userdata;
+        int r;
+
+        r = sd_bus_call_method(
+                        bus,
+                        "org.freedesktop.machine1",
+                        "/org/freedesktop/machine1",
+                        "org.freedesktop.machine1.Manager",
+                        "RenameImage",
+                        &error,
+                        NULL,
+                        "ss", argv[1], argv[2]);
+        if (r < 0) {
+                log_error("Could not rename image: %s", bus_error_message(&error, -r));
+                return r;
+        }
+
+        return 0;
+}
+
+static int clone_image(int argc, char *argv[], void *userdata) {
+        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+        sd_bus *bus = userdata;
+        int r;
+
+        r = sd_bus_call_method(
+                        bus,
+                        "org.freedesktop.machine1",
+                        "/org/freedesktop/machine1",
+                        "org.freedesktop.machine1.Manager",
+                        "CloneImage",
+                        &error,
+                        NULL,
+                        "ssb", argv[1], argv[2], arg_read_only);
+        if (r < 0) {
+                log_error("Could not clone image: %s", bus_error_message(&error, -r));
+                return r;
+        }
+
+        return 0;
+}
+
+static int read_only_image(int argc, char *argv[], void *userdata) {
+        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+        sd_bus *bus = userdata;
+        int b = true, r;
+
+        if (argc > 2) {
+                b = parse_boolean(argv[2]);
+                if (b < 0) {
+                        log_error("Failed to parse boolean argument: %s", argv[2]);
+                        return -EINVAL;
+                }
+        }
+
+        r = sd_bus_call_method(
+                        bus,
+                        "org.freedesktop.machine1",
+                        "/org/freedesktop/machine1",
+                        "org.freedesktop.machine1.Manager",
+                        "MarkImageReadOnly",
+                        &error,
+                        NULL,
+                        "sb", argv[1], b);
+        if (r < 0) {
+                log_error("Could not mark image read-only: %s", bus_error_message(&error, -r));
+                return r;
+        }
+
+        return 0;
+}
+
+static int start_machine(int argc, char *argv[], void *userdata) {
+        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
+        sd_bus *bus = userdata;
+        int r, i;
+
+        assert(bus);
+
+        r = bus_wait_for_jobs_new(bus, &w);
+        if (r < 0)
+                return log_oom();
+
+        for (i = 1; i < argc; i++) {
+                _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
+                _cleanup_free_ char *e = NULL, *unit = NULL;
+                const char *object;
+
+                if (!machine_name_is_valid(argv[i])) {
+                        log_error("Invalid machine name %s.", argv[i]);
+                        return -EINVAL;
+                }
+
+                e = unit_name_escape(argv[i]);
+                if (!e)
+                        return log_oom();
+
+                unit = unit_name_build("systemd-nspawn", e, ".service");
+                if (!unit)
+                        return log_oom();
+
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.systemd1",
+                                "/org/freedesktop/systemd1",
+                                "org.freedesktop.systemd1.Manager",
+                                "StartUnit");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_set_allow_interactive_authorization(m, true);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append(m, "ss", unit, "fail");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_call(bus, m, 0, &error, &reply);
+                if (r < 0) {
+                        log_error("Failed to start unit: %s", bus_error_message(&error, -r));
+                        return 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 = bus_wait_for_jobs(w, arg_quiet);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int enable_machine(int argc, char *argv[], void *userdata) {
+        _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
+        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+        int carries_install_info = 0;
+        const char *method = NULL;
+        sd_bus *bus = userdata;
+        int r, i;
+
+        assert(bus);
+
+        method = streq(argv[0], "enable") ? "EnableUnitFiles" : "DisableUnitFiles";
+
+        r = sd_bus_message_new_method_call(
+                        bus,
+                        &m,
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.systemd1.Manager",
+                        method);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_message_set_allow_interactive_authorization(m, true);
+        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);
+
+        for (i = 1; i < argc; i++) {
+                _cleanup_free_ char *e = NULL, *unit = NULL;
+
+                if (!machine_name_is_valid(argv[i])) {
+                        log_error("Invalid machine name %s.", argv[i]);
+                        return -EINVAL;
+                }
+
+                e = unit_name_escape(argv[i]);
+                if (!e)
+                        return log_oom();
+
+                unit = unit_name_build("systemd-nspawn", e, ".service");
+                if (!unit)
+                        return log_oom();
+
+                r = sd_bus_message_append(m, "s", unit);
+                if (r < 0)
+                        return bus_log_create_error(r);
+        }
+
+        r = sd_bus_message_close_container(m);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        if (streq(argv[0], "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);
+
+        r = sd_bus_call(bus, m, 0, &error, &reply);
+        if (r < 0) {
+                log_error("Failed to enable or disable unit: %s", bus_error_message(&error, -r));
+                return r;
+        }
+
+        if (streq(argv[0], "enable")) {
+                r = sd_bus_message_read(reply, "b", carries_install_info);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+        }
+
+        r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
+        if (r < 0)
+                return r;
+
+        m = sd_bus_message_unref(m);
+
+        r = sd_bus_message_new_method_call(
+                        bus,
+                        &m,
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.systemd1.Manager",
+                        "Reload");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_message_set_allow_interactive_authorization(m, true);
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_call(bus, m, 0, &error, NULL);
+        if (r < 0) {
+                log_error("Failed to reload daemon: %s", bus_error_message(&error, -r));
+                return r;
+        }
+
+        return 0;
+}
+
 static int help(int argc, char *argv[], void *userdata) {
 
         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
@@ -1128,6 +1603,7 @@ static int help(int argc, char *argv[], void *userdata) {
                "  -H --host=[USER@]HOST       Operate on remote host\n"
                "  -M --machine=CONTAINER      Operate on local container\n"
                "  -p --property=NAME          Show only properties by this name\n"
+               "  -q --quiet                  Suppress output\n"
                "  -a --all                    Show all properties, including empty ones\n"
                "  -l --full                   Do not ellipsize output\n"
                "     --kill-who=WHO           Who to send signal to\n"
@@ -1136,18 +1612,27 @@ static int help(int argc, char *argv[], void *userdata) {
                "     --mkdir                  Create directory before bind mounting, if missing\n\n"
                "Machine Commands:\n"
                "  list                        List running VMs and containers\n"
-               "  status NAME...              Show VM/container status\n"
+               "  status NAME...              Show VM/container details\n"
                "  show NAME...                Show properties of one or more VMs/containers\n"
                "  login NAME                  Get a login prompt on a container\n"
+               "  start NAME...               Start container as a service\n"
+               "  enable NAME...              Enable automatic container start at boot\n"
+               "  disable NAME...             Disable automatic container start at boot\n"
                "  poweroff NAME...            Power off one or more containers\n"
                "  reboot NAME...              Reboot one or more containers\n"
-               "  kill NAME...                Send signal to processes of a VM/container\n"
                "  terminate NAME...           Terminate one or more VMs/containers\n"
-               "  bind NAME PATH [PATH]       Bind mount a path from the host into a container\n"
+               "  kill NAME...                Send signal to processes of a VM/container\n"
                "  copy-to NAME PATH [PATH]    Copy files from the host to a container\n"
-               "  copy-from NAME PATH [PATH]  Copy files from a container to the host\n\n"
-               "Image commands:\n"
-               "  list-images                 Show available images\n",
+               "  copy-from NAME PATH [PATH]  Copy files from a container to the host\n"
+               "  bind NAME PATH [PATH]       Bind mount a path from the host into a container\n\n"
+               "Image Commands:\n"
+               "  list-images                 Show available images\n"
+               "  image-status NAME...        Show image details\n"
+               "  show-image NAME...          Show properties of image\n"
+               "  clone NAME NAME             Clone an image\n"
+               "  rename NAME NAME            Rename an image\n"
+               "  read-only NAME [BOOL]       Mark or unmark image read-only\n"
+               "  remove NAME...              Remove an image\n",
                program_invocation_short_name);
 
         return 0;
@@ -1178,6 +1663,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "machine",         required_argument, NULL, 'M'                 },
                 { "read-only",       no_argument,       NULL, ARG_READ_ONLY       },
                 { "mkdir",           no_argument,       NULL, ARG_MKDIR           },
+                { "quiet",           no_argument,       NULL, 'q'                 },
                 {}
         };
 
@@ -1186,7 +1672,7 @@ static int parse_argv(int argc, char *argv[]) {
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "hp:als:H:M:", options, NULL)) >= 0)
+        while ((c = getopt_long(argc, argv, "hp:als:H:M:q", options, NULL)) >= 0)
 
                 switch (c) {
 
@@ -1243,7 +1729,7 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case 'M':
-                        arg_transport = BUS_TRANSPORT_CONTAINER;
+                        arg_transport = BUS_TRANSPORT_MACHINE;
                         arg_host = optarg;
                         break;
 
@@ -1255,6 +1741,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_mkdir = true;
                         break;
 
+                case 'q':
+                        arg_quiet = true;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -1271,8 +1761,10 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
                 { "help",        VERB_ANY, VERB_ANY, 0,            help              },
                 { "list",        VERB_ANY, 1,        VERB_DEFAULT, list_machines     },
                 { "list-images", VERB_ANY, 1,        0,            list_images       },
-                { "status",      2,        VERB_ANY, 0,            show              },
-                { "show",        VERB_ANY, VERB_ANY, 0,            show              },
+                { "status",      2,        VERB_ANY, 0,            show_machine      },
+                { "image-status",2,        VERB_ANY, 0,            show_image        },
+                { "show",        VERB_ANY, VERB_ANY, 0,            show_machine      },
+                { "show-image",  VERB_ANY, VERB_ANY, 0,            show_image        },
                 { "terminate",   2,        VERB_ANY, 0,            terminate_machine },
                 { "reboot",      2,        VERB_ANY, 0,            reboot_machine    },
                 { "poweroff",    2,        VERB_ANY, 0,            poweroff_machine  },
@@ -1281,6 +1773,13 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
                 { "bind",        3,        4,        0,            bind_mount        },
                 { "copy-to",     3,        4,        0,            copy_files        },
                 { "copy-from",   3,        4,        0,            copy_files        },
+                { "remove",      2,        VERB_ANY, 0,            remove_image      },
+                { "rename",      3,        3,        0,            rename_image      },
+                { "clone",       3,        3,        0,            clone_image       },
+                { "read-only",   2,        3,        0,            read_only_image   },
+                { "start",       2,        VERB_ANY, 0,            start_machine     },
+                { "enable",      2,        VERB_ANY, 0,            enable_machine    },
+                { "disable",     2,        VERB_ANY, 0,            enable_machine    },
                 {}
         };