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