chiark / gitweb /
systemctl: fix "reboot" call
[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
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
242         MachineStatusInfo info = {};
243         int r;
244
245         assert(path);
246         assert(new_line);
247
248         r = bus_map_all_properties(bus,
249                                    "org.freedesktop.machine1",
250                                    path,
251                                    map,
252                                    &info);
253         if (r < 0) {
254                 log_error("Could not get properties: %s", strerror(-r));
255                 return r;
256         }
257
258         if (*new_line)
259                 printf("\n");
260         *new_line = true;
261
262         print_machine_status_info(bus, &info);
263
264         free(info.name);
265         free(info.class);
266         free(info.service);
267         free(info.scope);
268         free(info.root_directory);
269
270         return r;
271 }
272
273 static int show_properties(sd_bus *bus, const char *path, bool *new_line) {
274         int r;
275
276         if (*new_line)
277                 printf("\n");
278
279         *new_line = true;
280
281         r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all);
282         if (r < 0)
283                 log_error("Could not get properties: %s", strerror(-r));
284
285         return r;
286 }
287
288 static int show(sd_bus *bus, char **args, unsigned n) {
289         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
290         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
291         int r = 0;
292         unsigned i;
293         bool properties, new_line = false;
294
295         assert(bus);
296         assert(args);
297
298         properties = !strstr(args[0], "status");
299
300         pager_open_if_enabled();
301
302         if (properties && n <= 1) {
303
304                 /* If no argument is specified, inspect the manager
305                  * itself */
306                 r = show_properties(bus, "/org/freedesktop/machine1", &new_line);
307                 if (r < 0) {
308                         log_error("Failed to query properties: %s", strerror(-r));
309                         return r;
310                 }
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 terminate_machine(sd_bus *bus, char **args, unsigned n) {
374         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
375         unsigned i;
376
377         assert(args);
378
379         for (i = 1; i < n; i++) {
380                 int r;
381
382                 r = sd_bus_call_method(
383                                 bus,
384                                 "org.freedesktop.machine1",
385                                 "/org/freedesktop/machine1",
386                                 "org.freedesktop.machine1.Manager",
387                                 "TerminateMachine",
388                                 &error,
389                                 NULL,
390                                 "s", args[i]);
391                 if (r < 0) {
392                         log_error("Could not terminate machine: %s", bus_error_message(&error, -r));
393                         return r;
394                 }
395         }
396
397         return 0;
398 }
399
400 static int openpt_in_namespace(pid_t pid, int flags) {
401         _cleanup_close_ int nsfd = -1, rootfd = -1;
402         _cleanup_free_ char *ns = NULL, *root = NULL;
403         _cleanup_close_pipe_ int sock[2] = { -1, -1 };
404         union {
405                 struct cmsghdr cmsghdr;
406                 uint8_t buf[CMSG_SPACE(sizeof(int))];
407         } control = {};
408         struct msghdr mh = {
409                 .msg_control = &control,
410                 .msg_controllen = sizeof(control),
411         };
412         struct cmsghdr *cmsg;
413         int master = -1, r;
414         pid_t child;
415         siginfo_t si;
416
417         r = asprintf(&ns, "/proc/%lu/ns/mnt", (unsigned long) pid);
418         if (r < 0)
419                 return -ENOMEM;
420
421         nsfd = open(ns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
422         if (nsfd < 0)
423                 return -errno;
424
425         r = asprintf(&root, "/proc/%lu/root", (unsigned long) pid);
426         if (r < 0)
427                 return -ENOMEM;
428
429         rootfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
430         if (rootfd < 0)
431                 return -errno;
432
433         if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sock) < 0)
434                 return -errno;
435
436         child = fork();
437         if (child < 0)
438                 return -errno;
439
440         if (child == 0) {
441                 close_nointr_nofail(sock[0]);
442                 sock[0] = -1;
443
444                 r = setns(nsfd, CLONE_NEWNS);
445                 if (r < 0)
446                         _exit(EXIT_FAILURE);
447
448                 if (fchdir(rootfd) < 0)
449                         _exit(EXIT_FAILURE);
450
451                 if (chroot(".") < 0)
452                         _exit(EXIT_FAILURE);
453
454                 master = posix_openpt(flags);
455                 if (master < 0)
456                         _exit(EXIT_FAILURE);
457
458                 cmsg = CMSG_FIRSTHDR(&mh);
459                 cmsg->cmsg_level = SOL_SOCKET;
460                 cmsg->cmsg_type = SCM_RIGHTS;
461                 cmsg->cmsg_len = CMSG_LEN(sizeof(int));
462                 memcpy(CMSG_DATA(cmsg), &master, sizeof(int));
463
464                 mh.msg_controllen = cmsg->cmsg_len;
465
466                 r = sendmsg(sock[1], &mh, MSG_NOSIGNAL);
467                 close_nointr_nofail(master);
468                 if (r < 0)
469                         _exit(EXIT_FAILURE);
470
471                 _exit(EXIT_SUCCESS);
472         }
473
474         close_nointr_nofail(sock[1]);
475         sock[1] = -1;
476
477         if (recvmsg(sock[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0)
478                 return -errno;
479
480         for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg))
481                 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
482                         int *fds;
483                         unsigned n_fds;
484
485                         fds = (int*) CMSG_DATA(cmsg);
486                         n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
487
488                         if (n_fds != 1) {
489                                 close_many(fds, n_fds);
490                                 return -EIO;
491                         }
492
493                         master = fds[0];
494                 }
495
496         r = wait_for_terminate(child, &si);
497         if (r < 0 || si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS || master < 0) {
498
499                 if (master >= 0)
500                         close_nointr_nofail(master);
501
502                 return r < 0 ? r : -EIO;
503         }
504
505         return master;
506 }
507
508 static int login_machine(sd_bus *bus, char **args, unsigned n) {
509         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL, *reply2 = NULL, *reply3 = NULL;
510         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
511         _cleanup_bus_unref_ sd_bus *container_bus = NULL;
512         _cleanup_close_ int master = -1;
513         _cleanup_free_ char *getty = NULL;
514         const char *path, *pty, *p;
515         uint32_t leader;
516         sigset_t mask;
517         int r;
518
519         assert(bus);
520         assert(args);
521
522         if (arg_transport != BUS_TRANSPORT_LOCAL) {
523                 log_error("Login only support on local machines.");
524                 return -ENOTSUP;
525         }
526
527         r = sd_bus_call_method(
528                         bus,
529                         "org.freedesktop.machine1",
530                         "/org/freedesktop/machine1",
531                         "org.freedesktop.machine1.Manager",
532                         "GetMachine",
533                         &error,
534                         &reply,
535                         "s", args[1]);
536         if (r < 0) {
537                 log_error("Could not get path to machine: %s", bus_error_message(&error, -r));
538                 return r;
539         }
540
541         r = sd_bus_message_read(reply, "o", &path);
542         if (r < 0)
543                 return bus_log_parse_error(r);
544
545         r = sd_bus_get_property(
546                         bus,
547                         "org.freedesktop.machine1",
548                         path,
549                         "org.freedesktop.machine1.Machine",
550                         "Leader",
551                         &error,
552                         &reply2,
553                         "u");
554         if (r < 0) {
555                 log_error("Failed to retrieve PID of leader: %s", strerror(-r));
556                 return r;
557         }
558
559         r = sd_bus_message_read(reply2, "u", &leader);
560         if (r < 0)
561                 return bus_log_parse_error(r);
562
563         master = openpt_in_namespace(leader, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY);
564         if (master < 0) {
565                 log_error("Failed to acquire pseudo tty: %s", strerror(-master));
566                 return master;
567         }
568
569         pty = ptsname(master);
570         if (!pty) {
571                 log_error("Failed to get pty name: %m");
572                 return -errno;
573         }
574
575         p = startswith(pty, "/dev/pts/");
576         if (!p) {
577                 log_error("Invalid pty name %s.", pty);
578                 return -EIO;
579         }
580
581         r = sd_bus_open_system_container(args[1], &container_bus);
582         if (r < 0) {
583                 log_error("Failed to get container bus: %s", strerror(-r));
584                 return r;
585         }
586
587         getty = strjoin("container-getty@", p, ".service", NULL);
588         if (!getty)
589                 return log_oom();
590
591         if (unlockpt(master) < 0) {
592                 log_error("Failed to unlock tty: %m");
593                 return -errno;
594         }
595
596         r = sd_bus_call_method(container_bus,
597                                "org.freedesktop.systemd1",
598                                "/org/freedesktop/systemd1",
599                                "org.freedesktop.systemd1.Manager",
600                                "StartUnit",
601                                &error, &reply3,
602                                "ss", getty, "replace");
603         if (r < 0) {
604                 log_error("Failed to start getty service: %s", bus_error_message(&error, r));
605                 return r;
606         }
607
608         container_bus = sd_bus_unref(container_bus);
609
610         assert_se(sigemptyset(&mask) == 0);
611         sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1);
612         assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
613
614         log_info("Connected to container %s. Press ^] three times within 1s to exit session.", args[1]);
615
616         r = process_pty(master, &mask, 0, 0);
617         if (r < 0) {
618                 log_error("Failed to process pseudo tty: %s", strerror(-r));
619                 return r;
620         }
621
622         fputc('\n', stdout);
623
624         log_info("Connection to container %s terminated.", args[1]);
625
626         return 0;
627 }
628
629 static int help(void) {
630
631         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
632                "Send control commands to or query the virtual machine and container registration manager.\n\n"
633                "  -h --help              Show this help\n"
634                "     --version           Show package version\n"
635                "     --no-pager          Do not pipe output into a pager\n"
636                "     --no-ask-password   Don't prompt for password\n"
637                "  -H --host=[USER@]HOST  Operate on remote host\n"
638                "  -M --machine=CONTAINER Operate on local container\n"
639                "  -p --property=NAME     Show only properties by this name\n"
640                "  -a --all               Show all properties, including empty ones\n"
641                "  -l --full              Do not ellipsize output\n"
642                "     --kill-who=WHO      Who to send signal to\n"
643                "  -s --signal=SIGNAL     Which signal to send\n\n"
644                "Commands:\n"
645                "  list                   List running VMs and containers\n"
646                "  status [NAME...]       Show VM/container status\n"
647                "  show [NAME...]         Show properties of one or more VMs/containers\n"
648                "  terminate [NAME...]    Terminate one or more VMs/containers\n"
649                "  kill [NAME...]         Send signal to processes of a VM/container\n"
650                "  login [NAME]           Get a login prompt on a container\n",
651                program_invocation_short_name);
652
653         return 0;
654 }
655
656 static int parse_argv(int argc, char *argv[]) {
657
658         enum {
659                 ARG_VERSION = 0x100,
660                 ARG_NO_PAGER,
661                 ARG_KILL_WHO,
662                 ARG_NO_ASK_PASSWORD,
663         };
664
665         static const struct option options[] = {
666                 { "help",            no_argument,       NULL, 'h'                 },
667                 { "version",         no_argument,       NULL, ARG_VERSION         },
668                 { "property",        required_argument, NULL, 'p'                 },
669                 { "all",             no_argument,       NULL, 'a'                 },
670                 { "full",            no_argument,       NULL, 'l'                 },
671                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
672                 { "kill-who",        required_argument, NULL, ARG_KILL_WHO        },
673                 { "signal",          required_argument, NULL, 's'                 },
674                 { "host",            required_argument, NULL, 'H'                 },
675                 { "machine",         required_argument, NULL, 'M'                 },
676                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
677                 {}
678         };
679
680         int c, r;
681
682         assert(argc >= 0);
683         assert(argv);
684
685         while ((c = getopt_long(argc, argv, "hp:als:H:M:", options, NULL)) >= 0) {
686
687                 switch (c) {
688
689                 case 'h':
690                         return help();
691
692                 case ARG_VERSION:
693                         puts(PACKAGE_STRING);
694                         puts(SYSTEMD_FEATURES);
695                         return 0;
696
697                 case 'p':
698                         r = strv_extend(&arg_property, optarg);
699                         if (r < 0)
700                                 return log_oom();
701
702                         /* If the user asked for a particular
703                          * property, show it to him, even if it is
704                          * empty. */
705                         arg_all = true;
706                         break;
707
708                 case 'a':
709                         arg_all = true;
710                         break;
711
712                 case 'l':
713                         arg_full = true;
714                         break;
715
716                 case ARG_NO_PAGER:
717                         arg_no_pager = true;
718                         break;
719
720                 case ARG_NO_ASK_PASSWORD:
721                         arg_ask_password = false;
722                         break;
723
724                 case ARG_KILL_WHO:
725                         arg_kill_who = optarg;
726                         break;
727
728                 case 's':
729                         arg_signal = signal_from_string_try_harder(optarg);
730                         if (arg_signal < 0) {
731                                 log_error("Failed to parse signal string %s.", optarg);
732                                 return -EINVAL;
733                         }
734                         break;
735
736                 case 'H':
737                         arg_transport = BUS_TRANSPORT_REMOTE;
738                         arg_host = optarg;
739                         break;
740
741                 case 'M':
742                         arg_transport = BUS_TRANSPORT_CONTAINER;
743                         arg_host = optarg;
744                         break;
745
746                 case '?':
747                         return -EINVAL;
748
749                 default:
750                         assert_not_reached("Unhandled option");
751                 }
752         }
753
754         return 1;
755 }
756
757 static int machinectl_main(sd_bus *bus, int argc, char *argv[]) {
758
759         static const struct {
760                 const char* verb;
761                 const enum {
762                         MORE,
763                         LESS,
764                         EQUAL
765                 } argc_cmp;
766                 const int argc;
767                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
768         } verbs[] = {
769                 { "list",                  LESS,   1, list_machines     },
770                 { "status",                MORE,   2, show              },
771                 { "show",                  MORE,   1, show              },
772                 { "terminate",             MORE,   2, terminate_machine },
773                 { "kill",                  MORE,   2, kill_machine      },
774                 { "login",                 MORE,   2, login_machine     },
775         };
776
777         int left;
778         unsigned i;
779
780         assert(argc >= 0);
781         assert(argv);
782
783         left = argc - optind;
784
785         if (left <= 0)
786                 /* Special rule: no arguments means "list" */
787                 i = 0;
788         else {
789                 if (streq(argv[optind], "help")) {
790                         help();
791                         return 0;
792                 }
793
794                 for (i = 0; i < ELEMENTSOF(verbs); i++)
795                         if (streq(argv[optind], verbs[i].verb))
796                                 break;
797
798                 if (i >= ELEMENTSOF(verbs)) {
799                         log_error("Unknown operation %s", argv[optind]);
800                         return -EINVAL;
801                 }
802         }
803
804         switch (verbs[i].argc_cmp) {
805
806         case EQUAL:
807                 if (left != verbs[i].argc) {
808                         log_error("Invalid number of arguments.");
809                         return -EINVAL;
810                 }
811
812                 break;
813
814         case MORE:
815                 if (left < verbs[i].argc) {
816                         log_error("Too few arguments.");
817                         return -EINVAL;
818                 }
819
820                 break;
821
822         case LESS:
823                 if (left > verbs[i].argc) {
824                         log_error("Too many arguments.");
825                         return -EINVAL;
826                 }
827
828                 break;
829
830         default:
831                 assert_not_reached("Unknown comparison operator.");
832         }
833
834         return verbs[i].dispatch(bus, argv + optind, left);
835 }
836
837 int main(int argc, char*argv[]) {
838         _cleanup_bus_unref_ sd_bus *bus = NULL;
839         int r;
840
841         setlocale(LC_ALL, "");
842         log_parse_environment();
843         log_open();
844
845         r = parse_argv(argc, argv);
846         if (r <= 0)
847                 goto finish;
848
849         r = bus_open_transport(arg_transport, arg_host, false, &bus);
850         if (r < 0) {
851                 log_error("Failed to create bus connection: %s", strerror(-r));
852                 goto finish;
853         }
854
855         r = machinectl_main(bus, argc, argv);
856
857 finish:
858         pager_close();
859
860         strv_free(arg_property);
861
862         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
863 }