chiark / gitweb /
machinectl: fix minor memory leak
[elogind.git] / src / machine / machinectl.c
index 17fbe51f9f8486aae5375fb5487cae609c0b62c0..7ea991a81f1e15df594ec53e297f47bac6b3b6b0 100644 (file)
 #include <net/if.h>
 #include <sys/mount.h>
 #include <libgen.h>
-
+#undef basename
 #include "sd-bus.h"
 #include "log.h"
 #include "util.h"
 #include "macro.h"
 #include "pager.h"
+#include "spawn-polkit-agent.h"
 #include "bus-util.h"
 #include "bus-error.h"
 #include "build.h"
 #include "strv.h"
 #include "unit-name.h"
 #include "cgroup-show.h"
+#include "logs-show.h"
 #include "cgroup-util.h"
 #include "ptyfwd.h"
 #include "event-util.h"
@@ -63,23 +65,62 @@ 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 bool arg_ask_password = true;
+static unsigned arg_lines = 10;
+static OutputMode arg_output = OUTPUT_SHORT;
 
 static void pager_open_if_enabled(void) {
 
-        /* Cache result before we open the pager */
         if (arg_no_pager)
                 return;
 
         pager_open(false);
 }
 
+static void polkit_agent_open_if_enabled(void) {
+
+        /* Open the polkit agent as a child process if necessary */
+
+        if (!arg_ask_password)
+                return;
+
+        if (arg_transport != BUS_TRANSPORT_LOCAL)
+                return;
+
+        polkit_agent_open();
+}
+
+static OutputFlags get_output_flags(void) {
+        return
+                arg_all * OUTPUT_SHOW_ALL |
+                arg_full * OUTPUT_FULL_WIDTH |
+                (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH |
+                on_tty() * OUTPUT_COLOR |
+                !arg_quiet * OUTPUT_WARN_CUTOFF;
+}
+
+typedef struct MachineInfo {
+        const char *name;
+        const char *class;
+        const char *service;
+} MachineInfo;
+
+static int compare_machine_info(const void *a, const void *b) {
+        const MachineInfo *x = a, *y = b;
+
+        return strcmp(x->name, y->name);
+}
+
 static int list_machines(int argc, char *argv[], void *userdata) {
 
-        _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+        size_t max_name = strlen("MACHINE"), max_class = strlen("CLASS"), max_service = strlen("SERVICE");
         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+        _cleanup_free_ MachineInfo *machines = NULL;
         const char *name, *class, *service, *object;
+        size_t n_machines = 0, n_allocated = 0, j;
         sd_bus *bus = userdata;
-        unsigned k = 0;
         int r;
 
         assert(bus);
@@ -100,17 +141,33 @@ static int list_machines(int argc, char *argv[], void *userdata) {
                 return r;
         }
 
-        if (arg_legend)
-                printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE");
-
         r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssso)");
         if (r < 0)
                 return bus_log_parse_error(r);
 
         while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) {
-                printf("%-32s %-9s %-16s\n", name, class, service);
+                size_t l;
+
+                if (!GREEDY_REALLOC(machines, n_allocated, n_machines + 1))
+                        return log_oom();
+
+                machines[n_machines].name = name;
+                machines[n_machines].class = class;
+                machines[n_machines].service = service;
+
+                l = strlen(name);
+                if (l > max_name)
+                        max_name = l;
 
-                k++;
+                l = strlen(class);
+                if (l > max_class)
+                        max_class = l;
+
+                l = strlen(service);
+                if (l > max_service)
+                        max_service = l;
+
+                n_machines ++;
         }
         if (r < 0)
                 return bus_log_parse_error(r);
@@ -119,8 +176,22 @@ static int list_machines(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return bus_log_parse_error(r);
 
+        qsort_safe(machines, n_machines, sizeof(MachineInfo), compare_machine_info);
+
+        if (arg_legend)
+                printf("%-*s %-*s %-*s\n",
+                       (int) max_name, "MACHINE",
+                       (int) max_class, "CLASS",
+                       (int) max_service, "SERVICE");
+
+        for (j = 0; j < n_machines; j++)
+                printf("%-*s %-*s %-*s\n",
+                       (int) max_name, machines[j].name,
+                       (int) max_class, machines[j].class,
+                       (int) max_service, machines[j].service);
+
         if (arg_legend)
-                printf("\n%u machines listed.\n", k);
+                printf("\n%zu machines listed.\n", n_machines);
 
         return 0;
 }
@@ -249,10 +320,6 @@ static int list_images(int argc, char *argv[], void *userdata) {
                        (int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime)));
         }
 
-        if (r < 0)
-                return bus_log_parse_error(r);
-
-
         if (arg_legend)
                 printf("\n%zu images listed.\n", n_images);
 
@@ -264,7 +331,7 @@ static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) {
         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_free_ char *path = NULL;
         const char *cgroup;
-        int r, output_flags;
+        int r;
         unsigned c;
 
         assert(bus);
@@ -301,17 +368,13 @@ static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) {
         if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0)
                 return 0;
 
-        output_flags =
-                arg_all * OUTPUT_SHOW_ALL |
-                arg_full * OUTPUT_FULL_WIDTH;
-
         c = columns();
         if (c > 18)
                 c -= 18;
         else
                 c = 0;
 
-        show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t  ", c, false, &leader, leader > 0, output_flags);
+        show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t  ", c, false, &leader, leader > 0, get_output_flags());
         return 0;
 }
 
@@ -426,7 +489,7 @@ typedef struct MachineStatusInfo {
         char *unit;
         char *root_directory;
         pid_t leader;
-        usec_t timestamp;
+        struct dual_timestamp timestamp;
         int *netif;
         unsigned n_netif;
 } MachineStatusInfo;
@@ -446,8 +509,8 @@ static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
         else
                 putchar('\n');
 
-        s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp);
-        s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
+        s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp.realtime);
+        s2 = format_timestamp(since2, sizeof(since2), i->timestamp.realtime);
 
         if (s1)
                 printf("\t   Since: %s; %s\n", s2, s1);
@@ -511,6 +574,22 @@ static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
         if (i->unit) {
                 printf("\t    Unit: %s\n", i->unit);
                 show_unit_cgroup(bus, i->unit, i->leader);
+
+                if (arg_transport == BUS_TRANSPORT_LOCAL) {
+
+                        show_journal_by_unit(
+                                        stdout,
+                                        i->unit,
+                                        arg_output,
+                                        0,
+                                        i->timestamp.monotonic,
+                                        arg_lines,
+                                        0,
+                                        get_output_flags() | OUTPUT_BEGIN_NEWLINE,
+                                        SD_JOURNAL_LOCAL_ONLY,
+                                        true,
+                                        NULL);
+                }
         }
 }
 
@@ -538,15 +617,16 @@ 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) },
-                { "Service",           "s",  NULL,          offsetof(MachineStatusInfo, service) },
-                { "Unit",              "s",  NULL,          offsetof(MachineStatusInfo, unit) },
-                { "RootDirectory",     "s",  NULL,          offsetof(MachineStatusInfo, root_directory) },
-                { "Leader",            "u",  NULL,          offsetof(MachineStatusInfo, leader) },
-                { "Timestamp",         "t",  NULL,          offsetof(MachineStatusInfo, timestamp) },
-                { "Id",                "ay", bus_map_id128, offsetof(MachineStatusInfo, id) },
-                { "NetworkInterfaces", "ai", map_netif,     0 },
+                { "Name",               "s",  NULL,          offsetof(MachineStatusInfo, name)                },
+                { "Class",              "s",  NULL,          offsetof(MachineStatusInfo, class)               },
+                { "Service",            "s",  NULL,          offsetof(MachineStatusInfo, service)             },
+                { "Unit",               "s",  NULL,          offsetof(MachineStatusInfo, unit)                },
+                { "RootDirectory",      "s",  NULL,          offsetof(MachineStatusInfo, root_directory)      },
+                { "Leader",             "u",  NULL,          offsetof(MachineStatusInfo, leader)              },
+                { "Timestamp",          "t",  NULL,          offsetof(MachineStatusInfo, timestamp.realtime)  },
+                { "TimestampMonotonic", "t",  NULL,          offsetof(MachineStatusInfo, timestamp.monotonic) },
+                { "Id",                 "ay", bus_map_id128, offsetof(MachineStatusInfo, id)                  },
+                { "NetworkInterfaces",  "ai", map_netif,     0                                                },
                 {}
         };
 
@@ -681,7 +761,7 @@ static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) {
                 putchar('\n');
         }
 
-        if (i->path)
+        if (i->type)
                 printf("\t    Type: %s\n", i->type);
 
         if (i->path)
@@ -845,6 +925,8 @@ static int kill_machine(int argc, char *argv[], void *userdata) {
 
         assert(bus);
 
+        polkit_agent_open_if_enabled();
+
         if (!arg_kill_who)
                 arg_kill_who = "all";
 
@@ -890,6 +972,8 @@ static int terminate_machine(int argc, char *argv[], void *userdata) {
 
         assert(bus);
 
+        polkit_agent_open_if_enabled();
+
         for (i = 1; i < argc; i++) {
                 int r;
 
@@ -981,11 +1065,11 @@ static int copy_files(int argc, char *argv[], void *userdata) {
                 return -EINVAL;
         }
 
-        t = strdup(host_path);
+        t = strdupa(host_path);
         host_basename = basename(t);
         host_dirname = dirname(host_path);
 
-        t = strdup(container_path);
+        t = strdupa(container_path);
         container_basename = basename(t);
         container_dirname = dirname(container_path);
 
@@ -1233,16 +1317,43 @@ finish:
         return r;
 }
 
+static int on_machine_removed(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+        PTYForward ** forward = (PTYForward**) userdata;
+        int r;
+
+        assert(bus);
+        assert(m);
+        assert(forward);
+
+        if (*forward) {
+                /* If the forwarder is already initialized, tell it to
+                 * exit on the next vhangup(), so that we still flush
+                 * out what might be queued and exit then. */
+
+                r = pty_forward_set_ignore_vhangup(*forward, false);
+                if (r >= 0)
+                        return 0;
+
+                log_error_errno(r, "Failed to set ignore_vhangup flag: %m");
+        }
+
+        /* On error, or when the forwarder is not initialized yet, quit immediately */
+        sd_event_exit(sd_bus_get_event(bus), EXIT_FAILURE);
+        return 0;
+}
+
 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 *m = NULL, *reply = NULL;
+        _cleanup_bus_slot_unref_ sd_bus_slot *slot = NULL;
         _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
         _cleanup_event_unref_ sd_event *event = NULL;
         int master = -1, r, ret = 0;
         sd_bus *bus = userdata;
-        const char *pty;
+        const char *pty, *match;
         sigset_t mask;
         char last_char = 0;
+        bool machine_died;
 
         assert(bus);
 
@@ -1252,6 +1363,8 @@ static int login_machine(int argc, char *argv[], void *userdata) {
                 return -ENOTSUP;
         }
 
+        polkit_agent_open_if_enabled();
+
         r = sd_event_default(&event);
         if (r < 0)
                 return log_error_errno(r, "Failed to get event loop: %m");
@@ -1260,6 +1373,19 @@ 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");
 
+        match = strappenda("type='signal',"
+                           "sender='org.freedesktop.machine1',"
+                           "path='/org/freedesktop/machine1',",
+                           "interface='org.freedesktop.machine1.Manager',"
+                           "member='MachineRemoved',"
+                           "arg0='",
+                           argv[1],
+                           "'");
+
+        r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add machine removal match: %m");
+
         r = sd_bus_message_new_method_call(bus,
                                            &m,
                                            "org.freedesktop.machine1",
@@ -1269,7 +1395,7 @@ static int login_machine(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return bus_log_create_error(r);
 
-        r = sd_bus_message_set_allow_interactive_authorization(m, true);
+        r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
         if (r < 0)
                 return bus_log_create_error(r);
 
@@ -1291,7 +1417,7 @@ static int login_machine(int argc, char *argv[], void *userdata) {
         sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1);
         assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
 
-        log_info("Connected to container %s. Press ^] three times within 1s to exit session.", argv[1]);
+        log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", argv[1]);
 
         sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
         sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
@@ -1304,14 +1430,18 @@ static int login_machine(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return log_error_errno(r, "Failed to run event loop: %m");
 
-        pty_forward_last_char(forward, &last_char);
+        pty_forward_get_last_char(forward, &last_char);
+        machine_died = pty_forward_get_ignore_vhangup(forward) == 0;
 
         forward = pty_forward_free(forward);
 
         if (last_char != '\n')
                 fputc('\n', stdout);
 
-        log_info("Connection to container %s terminated.", argv[1]);
+        if (machine_died)
+                log_info("Machine %s terminated.", argv[1]);
+        else
+                log_info("Connection to machine %s terminated.", argv[1]);
 
         sd_event_get_exit_code(event, &ret);
         return ret;
@@ -1324,6 +1454,8 @@ static int remove_image(int argc, char *argv[], void *userdata) {
 
         assert(bus);
 
+        polkit_agent_open_if_enabled();
+
         for (i = 1; i < argc; i++) {
                 r = sd_bus_call_method(
                                 bus,
@@ -1348,6 +1480,8 @@ static int rename_image(int argc, char *argv[], void *userdata) {
         sd_bus *bus = userdata;
         int r;
 
+        polkit_agent_open_if_enabled();
+
         r = sd_bus_call_method(
                         bus,
                         "org.freedesktop.machine1",
@@ -1370,6 +1504,8 @@ static int clone_image(int argc, char *argv[], void *userdata) {
         sd_bus *bus = userdata;
         int r;
 
+        polkit_agent_open_if_enabled();
+
         r = sd_bus_call_method(
                         bus,
                         "org.freedesktop.machine1",
@@ -1400,6 +1536,8 @@ static int read_only_image(int argc, char *argv[], void *userdata) {
                 }
         }
 
+        polkit_agent_open_if_enabled();
+
         r = sd_bus_call_method(
                         bus,
                         "org.freedesktop.machine1",
@@ -1417,6 +1555,183 @@ static int read_only_image(int argc, char *argv[], void *userdata) {
         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);
+
+        polkit_agent_open_if_enabled();
+
+        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, arg_ask_password);
+                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);
+
+        polkit_agent_open_if_enabled();
+
+        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, arg_ask_password);
+        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, arg_ask_password);
+        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"
@@ -1426,20 +1741,29 @@ static int help(int argc, char *argv[], void *userdata) {
                "     --version                Show package version\n"
                "     --no-pager               Do not pipe output into a pager\n"
                "     --no-legend              Do not show the headers and footers\n"
+               "     --no-ask-password        Do not ask for system passwords\n"
                "  -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"
                "  -s --signal=SIGNAL          Which signal to send\n"
                "     --read-only              Create read-only bind mount\n"
-               "     --mkdir                  Create directory before bind mounting, if missing\n\n"
+               "     --mkdir                  Create directory before bind mounting, if missing\n"
+               "  -n --lines=INTEGER          Number of journal entries to show\n"
+               "  -o --output=STRING          Change journal output mode (short,\n"
+               "                              short-monotonic, verbose, export, json,\n"
+               "                              json-pretty, json-sse, cat)\n\n"
                "Machine Commands:\n"
                "  list                        List running VMs and containers\n"
                "  status NAME...              Show VM/container details\n"
                "  show NAME...                Show properties of one or more VMs/containers\n"
+               "  start NAME...               Start container as a service\n"
                "  login NAME                  Get a login prompt on a container\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"
                "  terminate NAME...           Terminate one or more VMs/containers\n"
@@ -1454,8 +1778,8 @@ static int help(int argc, char *argv[], void *userdata) {
                "  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);
+               "  remove NAME...              Remove an image\n"
+               program_invocation_short_name);
 
         return 0;
 }
@@ -1469,6 +1793,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_KILL_WHO,
                 ARG_READ_ONLY,
                 ARG_MKDIR,
+                ARG_NO_ASK_PASSWORD,
         };
 
         static const struct option options[] = {
@@ -1485,6 +1810,10 @@ 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'                 },
+                { "lines",           required_argument, NULL, 'n'                 },
+                { "output",          required_argument, NULL, 'o'                 },
+                { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
                 {}
         };
 
@@ -1493,7 +1822,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:qn:o:", options, NULL)) >= 0)
 
                 switch (c) {
 
@@ -1524,6 +1853,21 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_full = true;
                         break;
 
+                case 'n':
+                        if (safe_atou(optarg, &arg_lines) < 0) {
+                                log_error("Failed to parse lines '%s'", optarg);
+                                return -EINVAL;
+                        }
+                        break;
+
+                case 'o':
+                        arg_output = output_mode_from_string(optarg);
+                        if (arg_output < 0) {
+                                log_error("Unknown output '%s'.", optarg);
+                                return -EINVAL;
+                        }
+                        break;
+
                 case ARG_NO_PAGER:
                         arg_no_pager = true;
                         break;
@@ -1544,6 +1888,10 @@ static int parse_argv(int argc, char *argv[]) {
                         }
                         break;
 
+                case ARG_NO_ASK_PASSWORD:
+                        arg_ask_password = false;
+                        break;
+
                 case 'H':
                         arg_transport = BUS_TRANSPORT_REMOTE;
                         arg_host = optarg;
@@ -1562,6 +1910,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_mkdir = true;
                         break;
 
+                case 'q':
+                        arg_quiet = true;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -1594,6 +1946,9 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
                 { "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    },
                 {}
         };
 
@@ -1622,6 +1977,7 @@ int main(int argc, char*argv[]) {
 
 finish:
         pager_close();
+        polkit_agent_close();
 
         strv_free(arg_property);