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