chiark / gitweb /
bus: update bus_map_all_properties()
[elogind.git] / src / machine / machinectl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2013 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <unistd.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <getopt.h>
26 #include <pwd.h>
27 #include <locale.h>
28 #include <socket.h>
29 #include <fcntl.h>
30
31 #include "sd-bus.h"
32 #include "log.h"
33 #include "util.h"
34 #include "macro.h"
35 #include "pager.h"
36 #include "bus-util.h"
37 #include "bus-error.h"
38 #include "build.h"
39 #include "strv.h"
40 #include "unit-name.h"
41 #include "cgroup-show.h"
42 #include "cgroup-util.h"
43 #include "ptyfwd.h"
44
45 static char **arg_property = NULL;
46 static bool arg_all = false;
47 static bool arg_full = false;
48 static bool arg_no_pager = false;
49 static const char *arg_kill_who = NULL;
50 static int arg_signal = SIGTERM;
51 static bool arg_ask_password = true;
52 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
53 static char *arg_host = NULL;
54
55 static void pager_open_if_enabled(void) {
56
57         /* Cache result before we open the pager */
58         if (arg_no_pager)
59                 return;
60
61         pager_open(false);
62 }
63
64 static int list_machines(sd_bus *bus, char **args, unsigned n) {
65         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
66         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
67         const char *name, *class, *service, *object;
68         unsigned k = 0;
69         int r;
70
71         pager_open_if_enabled();
72
73         r = sd_bus_call_method(
74                                 bus,
75                                 "org.freedesktop.machine1",
76                                 "/org/freedesktop/machine1",
77                                 "org.freedesktop.machine1.Manager",
78                                 "ListMachines",
79                                 &error,
80                                 &reply,
81                                 "");
82         if (r < 0) {
83                 log_error("Could not get machines: %s", bus_error_message(&error, -r));
84                 return r;
85         }
86
87         if (on_tty())
88                 printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE");
89
90         r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssso)");
91         if (r < 0)
92                 goto fail;
93
94         while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) {
95                 printf("%-32s %-9s %-16s\n", name, class, service);
96
97                 k++;
98         }
99         if (r < 0)
100                 goto fail;
101
102         r = sd_bus_message_exit_container(reply);
103         if (r < 0)
104                 goto fail;
105
106         if (on_tty())
107                 printf("\n%u machines listed.\n", k);
108
109         return 0;
110
111 fail:
112         log_error("Failed to parse reply: %s", strerror(-r));
113         return r;
114 }
115
116 static int show_scope_cgroup(sd_bus *bus, const char *unit, pid_t leader) {
117         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
118         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
119         _cleanup_free_ char *path = NULL;
120         const char *cgroup;
121         int r, output_flags;
122         unsigned c;
123
124         assert(bus);
125         assert(unit);
126
127         if (arg_transport == BUS_TRANSPORT_REMOTE)
128                 return 0;
129
130         path = unit_dbus_path_from_name(unit);
131         if (!path)
132                 return log_oom();
133
134         r = sd_bus_get_property(
135                         bus,
136                         "org.freedesktop.systemd1",
137                         path,
138                         "org.freedesktop.systemd1.Scope",
139                         "ControlGroup",
140                         &error,
141                         &reply,
142                         "s");
143         if (r < 0) {
144                 log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r));
145                 return r;
146         }
147
148         r = sd_bus_message_read(reply, "s", &cgroup);
149         if (r < 0) {
150                 log_error("Failed to parse reply: %s", strerror(-r));
151                 return r;
152         }
153
154         if (isempty(cgroup))
155                 return 0;
156
157         if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0)
158                 return 0;
159
160         output_flags =
161                 arg_all * OUTPUT_SHOW_ALL |
162                 arg_full * OUTPUT_FULL_WIDTH;
163
164         c = columns();
165         if (c > 18)
166                 c -= 18;
167         else
168                 c = 0;
169
170         show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t  ", c, false, &leader, leader > 0, output_flags);
171         return 0;
172 }
173
174 typedef struct MachineStatusInfo {
175         char *name;
176         sd_id128_t id;
177         char *class;
178         char *service;
179         char *scope;
180         char *root_directory;
181         pid_t leader;
182         usec_t timestamp;
183 } MachineStatusInfo;
184
185 static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
186         char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
187         char since2[FORMAT_TIMESTAMP_MAX], *s2;
188         assert(i);
189
190         fputs(strna(i->name), stdout);
191
192         if (!sd_id128_equal(i->id, SD_ID128_NULL))
193                 printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id));
194         else
195                 putchar('\n');
196
197         s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp);
198         s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
199
200         if (s1)
201                 printf("\t   Since: %s; %s\n", s2, s1);
202         else if (s2)
203                 printf("\t   Since: %s\n", s2);
204
205         if (i->leader > 0) {
206                 _cleanup_free_ char *t = NULL;
207
208                 printf("\t  Leader: %u", (unsigned) i->leader);
209
210                 get_process_comm(i->leader, &t);
211                 if (t)
212                         printf(" (%s)", t);
213
214                 putchar('\n');
215         }
216
217         if (i->service) {
218                 printf("\t Service: %s", i->service);
219
220                 if (i->class)
221                         printf("; class %s", i->class);
222
223                 putchar('\n');
224         } else if (i->class)
225                 printf("\t   Class: %s\n", i->class);
226
227         if (i->root_directory)
228                 printf("\t    Root: %s\n", i->root_directory);
229
230         if (i->scope) {
231                 printf("\t    Unit: %s\n", i->scope);
232                 show_scope_cgroup(bus, i->scope, i->leader);
233         }
234 }
235
236 static int show_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
237         MachineStatusInfo info = {};
238         static const struct bus_properties_map map[]  = {
239                 { "Name",          "s",  NULL,          offsetof(MachineStatusInfo, name) },
240                 { "Class",         "s",  NULL,          offsetof(MachineStatusInfo, class) },
241                 { "Service",       "s",  NULL,          offsetof(MachineStatusInfo, service) },
242                 { "Scope",         "s",  NULL,          offsetof(MachineStatusInfo, scope) },
243                 { "RootDirectory", "s",  NULL,          offsetof(MachineStatusInfo, root_directory) },
244                 { "Leader",        "u",  NULL,          offsetof(MachineStatusInfo, leader) },
245                 { "Timestamp",     "t",  NULL,          offsetof(MachineStatusInfo, timestamp) },
246                 { "Id",            "ay", bus_map_id128, offsetof(MachineStatusInfo, id) },
247                 {}
248         };
249         int r;
250
251         assert(path);
252         assert(new_line);
253
254         r = bus_map_all_properties(bus,
255                                    "org.freedesktop.machine1",
256                                    path,
257                                    map,
258                                    &info);
259         if (r < 0) {
260                 log_error("Could not get properties: %s", strerror(-r));
261                 return r;
262         }
263
264         if (*new_line)
265                 printf("\n");
266         *new_line = true;
267
268         print_machine_status_info(bus, &info);
269
270         free(info.name);
271         free(info.class);
272         free(info.service);
273         free(info.scope);
274         free(info.root_directory);
275
276         return r;
277 }
278
279 static int show_properties(sd_bus *bus, const char *path, bool *new_line) {
280         int r;
281
282         if (*new_line)
283                 printf("\n");
284
285         *new_line = true;
286
287         r = bus_print_all_properties(bus, path, arg_property, arg_all);
288         if (r < 0)
289                 log_error("Could not get properties: %s", strerror(-r));
290
291         return r;
292 }
293
294 static int show(sd_bus *bus, char **args, unsigned n) {
295         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
296         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
297         int r = 0;
298         unsigned i;
299         bool properties, new_line = false;
300
301         assert(bus);
302         assert(args);
303
304         properties = !strstr(args[0], "status");
305
306         pager_open_if_enabled();
307
308         if (properties && n <= 1) {
309
310                 /* If no argument is specified, inspect the manager
311                  * itself */
312                 r = show_properties(bus, "/org/freedesktop/machine1", &new_line);
313                 if (r < 0) {
314                         log_error("Failed to query properties: %s", strerror(-r));
315                         return r;
316                 }
317         }
318
319         for (i = 1; i < n; i++) {
320                 const char *path = NULL;
321
322                 r = sd_bus_call_method(
323                                         bus,
324                                         "org.freedesktop.machine1",
325                                         "/org/freedesktop/machine1",
326                                         "org.freedesktop.machine1.Manager",
327                                         "GetMachine",
328                                         &error,
329                                         &reply,
330                                         "s", args[i]);
331                 if (r < 0) {
332                         log_error("Could not get path to machine: %s", bus_error_message(&error, -r));
333                         return r;
334                 }
335
336                 r = sd_bus_message_read(reply, "o", &path);
337                 if (r < 0) {
338                         log_error("Failed to parse reply: %s", strerror(-r));
339                         break;
340                 }
341
342                 if (properties)
343                         r = show_properties(bus, path, &new_line);
344                 else
345                         r = show_info(args[0], bus, path, &new_line);
346         }
347
348         return r;
349 }
350
351 static int kill_machine(sd_bus *bus, char **args, unsigned n) {
352         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
353         unsigned i;
354
355         assert(args);
356
357         if (!arg_kill_who)
358                 arg_kill_who = "all";
359
360         for (i = 1; i < n; i++) {
361                 int r;
362
363                 r = sd_bus_call_method(
364                                         bus,
365                                         "org.freedesktop.machine1",
366                                         "/org/freedesktop/machine1",
367                                         "org.freedesktop.machine1.Manager",
368                                         "KillMachine",
369                                         &error,
370                                         NULL,
371                                         "ssi", args[i], arg_kill_who, arg_signal);
372                 if (r < 0) {
373                         log_error("Could not kill machine: %s", bus_error_message(&error, -r));
374                         return r;
375                 }
376         }
377
378         return 0;
379 }
380
381 static int terminate_machine(sd_bus *bus, char **args, unsigned n) {
382         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
383         unsigned i;
384
385         assert(args);
386
387         for (i = 1; i < n; i++) {
388                 int r;
389
390                 r = sd_bus_call_method(
391                                 bus,
392                                 "org.freedesktop.machine1",
393                                 "/org/freedesktop/machine1",
394                                 "org.freedesktop.machine1.Manager",
395                                 "TerminateMachine",
396                                 &error,
397                                 NULL,
398                                 "s", args[i]);
399                 if (r < 0) {
400                         log_error("Could not terminate machine: %s", bus_error_message(&error, -r));
401                         return r;
402                 }
403         }
404
405         return 0;
406 }
407
408 static int openpt_in_namespace(pid_t pid, int flags) {
409         _cleanup_close_ int nsfd = -1, rootfd = -1;
410         _cleanup_free_ char *ns = NULL, *root = NULL;
411         _cleanup_close_pipe_ int sock[2] = { -1, -1 };
412         struct msghdr mh;
413         union {
414                 struct cmsghdr cmsghdr;
415                 uint8_t buf[CMSG_SPACE(sizeof(int))];
416         } control;
417         struct cmsghdr *cmsg;
418         int master = -1, r;
419         pid_t child;
420         siginfo_t si;
421
422         r = asprintf(&ns, "/proc/%lu/ns/mnt", (unsigned long) pid);
423         if (r < 0)
424                 return -ENOMEM;
425
426         nsfd = open(ns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
427         if (nsfd < 0)
428                 return -errno;
429
430         r = asprintf(&root, "/proc/%lu/root", (unsigned long) pid);
431         if (r < 0)
432                 return -ENOMEM;
433
434         rootfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
435         if (rootfd < 0)
436                 return -errno;
437
438         if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sock) < 0)
439                 return -errno;
440
441         zero(control);
442         zero(mh);
443         mh.msg_control = &control;
444         mh.msg_controllen = sizeof(control);
445
446         child = fork();
447         if (child < 0)
448                 return -errno;
449
450         if (child == 0) {
451                 close_nointr_nofail(sock[0]);
452                 sock[0] = -1;
453
454                 r = setns(nsfd, CLONE_NEWNS);
455                 if (r < 0)
456                         _exit(EXIT_FAILURE);
457
458                 if (fchdir(rootfd) < 0)
459                         _exit(EXIT_FAILURE);
460
461                 if (chroot(".") < 0)
462                         _exit(EXIT_FAILURE);
463
464                 master = posix_openpt(flags);
465                 if (master < 0)
466                         _exit(EXIT_FAILURE);
467
468                 cmsg = CMSG_FIRSTHDR(&mh);
469                 cmsg->cmsg_level = SOL_SOCKET;
470                 cmsg->cmsg_type = SCM_RIGHTS;
471                 cmsg->cmsg_len = CMSG_LEN(sizeof(int));
472                 memcpy(CMSG_DATA(cmsg), &master, sizeof(int));
473
474                 mh.msg_controllen = cmsg->cmsg_len;
475
476                 r = sendmsg(sock[1], &mh, MSG_NOSIGNAL);
477                 close_nointr_nofail(master);
478                 if (r < 0)
479                         _exit(EXIT_FAILURE);
480
481                 _exit(EXIT_SUCCESS);
482         }
483
484         close_nointr_nofail(sock[1]);
485         sock[1] = -1;
486
487         if (recvmsg(sock[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0)
488                 return -errno;
489
490         for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg))
491                 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
492                         int *fds;
493                         unsigned n_fds;
494
495                         fds = (int*) CMSG_DATA(cmsg);
496                         n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
497
498                         if (n_fds != 1) {
499                                 close_many(fds, n_fds);
500                                 return -EIO;
501                         }
502
503                         master = fds[0];
504                 }
505
506         r = wait_for_terminate(child, &si);
507         if (r < 0 || si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS || master < 0) {
508
509                 if (master >= 0)
510                         close_nointr_nofail(master);
511
512                 return r < 0 ? r : -EIO;
513         }
514
515         return master;
516 }
517
518 static int login_machine(sd_bus *bus, char **args, unsigned n) {
519         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL, *reply2 = NULL, *reply3 = NULL;
520         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
521         _cleanup_bus_unref_ sd_bus *container_bus = NULL;
522         _cleanup_close_ int master = -1;
523         _cleanup_free_ char *getty = NULL;
524         const char *path, *pty, *p;
525         uint32_t leader;
526         sigset_t mask;
527         int r;
528
529         assert(bus);
530         assert(args);
531
532         if (arg_transport != BUS_TRANSPORT_LOCAL) {
533                 log_error("Login only support on local machines.");
534                 return -ENOTSUP;
535         }
536
537         r = sd_bus_call_method(
538                         bus,
539                         "org.freedesktop.machine1",
540                         "/org/freedesktop/machine1",
541                         "org.freedesktop.machine1.Manager",
542                         "GetMachine",
543                         &error,
544                         &reply,
545                         "s", args[1]);
546         if (r < 0) {
547                 log_error("Could not get path to machine: %s", bus_error_message(&error, -r));
548                 return r;
549         }
550
551         r = sd_bus_message_read(reply, "o", &path);
552         if (r < 0) {
553                 log_error("Failed to parse reply: %s", strerror(-r));
554                 return r;
555         }
556
557         r = sd_bus_get_property(
558                         bus,
559                         "org.freedesktop.machine1",
560                         path,
561                         "org.freedesktop.machine1.Machine",
562                         "Leader",
563                         &error,
564                         &reply2,
565                         "u");
566         if (r < 0) {
567                 log_error("Failed to retrieve PID of leader: %s", strerror(-r));
568                 return r;
569         }
570
571         r = sd_bus_message_read(reply2, "u", &leader);
572         if (r < 0) {
573                 log_error("Failed to parse reply: %s", strerror(-r));
574                 return r;
575         }
576
577         master = openpt_in_namespace(leader, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY);
578         if (master < 0) {
579                 log_error("Failed to acquire pseudo tty: %s", strerror(-master));
580                 return master;
581         }
582
583         pty = ptsname(master);
584         if (!pty) {
585                 log_error("Failed to get pty name: %m");
586                 return -errno;
587         }
588
589         p = startswith(pty, "/dev/pts/");
590         if (!p) {
591                 log_error("Invalid pty name %s.", pty);
592                 return -EIO;
593         }
594
595         r = sd_bus_open_system_container(args[1], &container_bus);
596         if (r < 0) {
597                 log_error("Failed to get container bus: %s", strerror(-r));
598                 return r;
599         }
600
601         getty = strjoin("container-getty@", p, ".service", NULL);
602         if (!getty)
603                 return log_oom();
604
605         if (unlockpt(master) < 0) {
606                 log_error("Failed to unlock tty: %m");
607                 return -errno;
608         }
609
610         r = sd_bus_call_method(container_bus,
611                                "org.freedesktop.systemd1",
612                                "/org/freedesktop/systemd1",
613                                "org.freedesktop.systemd1.Manager",
614                                "StartUnit",
615                                &error, &reply3,
616                                "ss", getty, "replace");
617         if (r < 0) {
618                 log_error("Failed to start getty service: %s", bus_error_message(&error, r));
619                 return r;
620         }
621
622         assert_se(sigemptyset(&mask) == 0);
623         sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1);
624         assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
625
626         log_info("Connected to container %s. Press ^] three times within 1s to exit session.", args[1]);
627
628         r = process_pty(master, &mask, 0, 0);
629         if (r < 0) {
630                 log_error("Failed to process pseudo tty: %s", strerror(-r));
631                 return r;
632         }
633
634         fputc('\n', stdout);
635
636         log_info("Connection to container %s terminated.", args[1]);
637
638         return 0;
639 }
640
641 static int help(void) {
642
643         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
644                "Send control commands to or query the virtual machine and container registration manager.\n\n"
645                "  -h --help              Show this help\n"
646                "     --version           Show package version\n"
647                "     --no-pager          Do not pipe output into a pager\n"
648                "     --no-ask-password   Don't prompt for password\n"
649                "  -H --host=[USER@]HOST  Operate on remote host\n"
650                "  -M --machine=CONTAINER Operate on local container\n"
651                "  -p --property=NAME     Show only properties by this name\n"
652                "  -a --all               Show all properties, including empty ones\n"
653                "  -l --full              Do not ellipsize output\n"
654                "     --kill-who=WHO      Who to send signal to\n"
655                "  -s --signal=SIGNAL     Which signal to send\n\n"
656                "Commands:\n"
657                "  list                   List running VMs and containers\n"
658                "  status [NAME...]       Show VM/container status\n"
659                "  show [NAME...]         Show properties of one or more VMs/containers\n"
660                "  terminate [NAME...]    Terminate one or more VMs/containers\n"
661                "  kill [NAME...]         Send signal to processes of a VM/container\n"
662                "  login [NAME]           Get a login prompt on a container\n",
663                program_invocation_short_name);
664
665         return 0;
666 }
667
668 static int parse_argv(int argc, char *argv[]) {
669
670         enum {
671                 ARG_VERSION = 0x100,
672                 ARG_NO_PAGER,
673                 ARG_KILL_WHO,
674                 ARG_NO_ASK_PASSWORD,
675         };
676
677         static const struct option options[] = {
678                 { "help",            no_argument,       NULL, 'h'                 },
679                 { "version",         no_argument,       NULL, ARG_VERSION         },
680                 { "property",        required_argument, NULL, 'p'                 },
681                 { "all",             no_argument,       NULL, 'a'                 },
682                 { "full",            no_argument,       NULL, 'l'                 },
683                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
684                 { "kill-who",        required_argument, NULL, ARG_KILL_WHO        },
685                 { "signal",          required_argument, NULL, 's'                 },
686                 { "host",            required_argument, NULL, 'H'                 },
687                 { "machine",         required_argument, NULL, 'M'                 },
688                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
689                 { NULL,              0,                 NULL, 0                   }
690         };
691
692         int c, r;
693
694         assert(argc >= 0);
695         assert(argv);
696
697         while ((c = getopt_long(argc, argv, "hp:als:H:M:", options, NULL)) >= 0) {
698
699                 switch (c) {
700
701                 case 'h':
702                         help();
703                         return 0;
704
705                 case ARG_VERSION:
706                         puts(PACKAGE_STRING);
707                         puts(SYSTEMD_FEATURES);
708                         return 0;
709
710                 case 'p':
711                         r = strv_extend(&arg_property, optarg);
712                         if (r < 0)
713                                 return log_oom();
714
715                         /* If the user asked for a particular
716                          * property, show it to him, even if it is
717                          * empty. */
718                         arg_all = true;
719                         break;
720
721                 case 'a':
722                         arg_all = true;
723                         break;
724
725                 case 'l':
726                         arg_full = true;
727                         break;
728
729                 case ARG_NO_PAGER:
730                         arg_no_pager = true;
731                         break;
732
733                 case ARG_NO_ASK_PASSWORD:
734                         arg_ask_password = false;
735                         break;
736
737                 case ARG_KILL_WHO:
738                         arg_kill_who = optarg;
739                         break;
740
741                 case 's':
742                         arg_signal = signal_from_string_try_harder(optarg);
743                         if (arg_signal < 0) {
744                                 log_error("Failed to parse signal string %s.", optarg);
745                                 return -EINVAL;
746                         }
747                         break;
748
749                 case 'H':
750                         arg_transport = BUS_TRANSPORT_REMOTE;
751                         arg_host = optarg;
752                         break;
753
754                 case 'M':
755                         arg_transport = BUS_TRANSPORT_CONTAINER;
756                         arg_host = optarg;
757                         break;
758
759                 case '?':
760                         return -EINVAL;
761
762                 default:
763                         log_error("Unknown option code %c", c);
764                         return -EINVAL;
765                 }
766         }
767
768         return 1;
769 }
770
771 static int machinectl_main(sd_bus *bus, int argc, char *argv[]) {
772
773         static const struct {
774                 const char* verb;
775                 const enum {
776                         MORE,
777                         LESS,
778                         EQUAL
779                 } argc_cmp;
780                 const int argc;
781                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
782         } verbs[] = {
783                 { "list",                  LESS,   1, list_machines     },
784                 { "status",                MORE,   2, show              },
785                 { "show",                  MORE,   1, show              },
786                 { "terminate",             MORE,   2, terminate_machine },
787                 { "kill",                  MORE,   2, kill_machine      },
788                 { "login",                 MORE,   2, login_machine     },
789         };
790
791         int left;
792         unsigned i;
793
794         assert(argc >= 0);
795         assert(argv);
796
797         left = argc - optind;
798
799         if (left <= 0)
800                 /* Special rule: no arguments means "list-sessions" */
801                 i = 0;
802         else {
803                 if (streq(argv[optind], "help")) {
804                         help();
805                         return 0;
806                 }
807
808                 for (i = 0; i < ELEMENTSOF(verbs); i++)
809                         if (streq(argv[optind], verbs[i].verb))
810                                 break;
811
812                 if (i >= ELEMENTSOF(verbs)) {
813                         log_error("Unknown operation %s", argv[optind]);
814                         return -EINVAL;
815                 }
816         }
817
818         switch (verbs[i].argc_cmp) {
819
820         case EQUAL:
821                 if (left != verbs[i].argc) {
822                         log_error("Invalid number of arguments.");
823                         return -EINVAL;
824                 }
825
826                 break;
827
828         case MORE:
829                 if (left < verbs[i].argc) {
830                         log_error("Too few arguments.");
831                         return -EINVAL;
832                 }
833
834                 break;
835
836         case LESS:
837                 if (left > verbs[i].argc) {
838                         log_error("Too many arguments.");
839                         return -EINVAL;
840                 }
841
842                 break;
843
844         default:
845                 assert_not_reached("Unknown comparison operator.");
846         }
847
848         return verbs[i].dispatch(bus, argv + optind, left);
849 }
850
851 int main(int argc, char*argv[]) {
852         int r, ret = EXIT_FAILURE;
853         _cleanup_bus_unref_ sd_bus *bus = NULL;
854
855         setlocale(LC_ALL, "");
856         log_parse_environment();
857         log_open();
858
859         r = parse_argv(argc, argv);
860         if (r < 0)
861                 goto finish;
862         else if (r == 0) {
863                 ret = EXIT_SUCCESS;
864                 goto finish;
865         }
866
867         r = bus_open_transport(arg_transport, arg_host, false, &bus);
868         if (r < 0) {
869                 log_error("Failed to create bus connection: %s", strerror(-r));
870                 ret = EXIT_FAILURE;
871                 goto finish;
872         }
873
874         r = machinectl_main(bus, argc, argv);
875         ret = r < 0 ? EXIT_FAILURE : r;
876
877 finish:
878         strv_free(arg_property);
879
880         pager_close();
881
882         return ret;
883 }