chiark / gitweb /
7feb7be3da69fb7f5f27052dd479901acd3eb996
[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         if (on_tty())
88                 printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE");
89
90         r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssso)");
91         if (r < 0)
92                 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 (on_tty())
107                 printf("\n%u machines listed.\n", k);
108
109         return 0;
110 }
111
112 static int show_scope_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                         "org.freedesktop.systemd1.Scope",
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 *scope;
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->scope) {
225                 printf("\t    Unit: %s\n", i->scope);
226                 show_scope_cgroup(bus, i->scope, i->leader);
227         }
228 }
229
230 static int show_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
231         MachineStatusInfo info = {};
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                 { "Scope",         "s",  NULL,          offsetof(MachineStatusInfo, scope) },
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         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         assert_se(sigemptyset(&mask) == 0);
609         sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1);
610         assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
611
612         log_info("Connected to container %s. Press ^] three times within 1s to exit session.", args[1]);
613
614         r = process_pty(master, &mask, 0, 0);
615         if (r < 0) {
616                 log_error("Failed to process pseudo tty: %s", strerror(-r));
617                 return r;
618         }
619
620         fputc('\n', stdout);
621
622         log_info("Connection to container %s terminated.", args[1]);
623
624         return 0;
625 }
626
627 static int help(void) {
628
629         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
630                "Send control commands to or query the virtual machine and container registration manager.\n\n"
631                "  -h --help              Show this help\n"
632                "     --version           Show package version\n"
633                "     --no-pager          Do not pipe output into a pager\n"
634                "     --no-ask-password   Don't prompt for password\n"
635                "  -H --host=[USER@]HOST  Operate on remote host\n"
636                "  -M --machine=CONTAINER Operate on local container\n"
637                "  -p --property=NAME     Show only properties by this name\n"
638                "  -a --all               Show all properties, including empty ones\n"
639                "  -l --full              Do not ellipsize output\n"
640                "     --kill-who=WHO      Who to send signal to\n"
641                "  -s --signal=SIGNAL     Which signal to send\n\n"
642                "Commands:\n"
643                "  list                   List running VMs and containers\n"
644                "  status [NAME...]       Show VM/container status\n"
645                "  show [NAME...]         Show properties of one or more VMs/containers\n"
646                "  terminate [NAME...]    Terminate one or more VMs/containers\n"
647                "  kill [NAME...]         Send signal to processes of a VM/container\n"
648                "  login [NAME]           Get a login prompt on a container\n",
649                program_invocation_short_name);
650
651         return 0;
652 }
653
654 static int parse_argv(int argc, char *argv[]) {
655
656         enum {
657                 ARG_VERSION = 0x100,
658                 ARG_NO_PAGER,
659                 ARG_KILL_WHO,
660                 ARG_NO_ASK_PASSWORD,
661         };
662
663         static const struct option options[] = {
664                 { "help",            no_argument,       NULL, 'h'                 },
665                 { "version",         no_argument,       NULL, ARG_VERSION         },
666                 { "property",        required_argument, NULL, 'p'                 },
667                 { "all",             no_argument,       NULL, 'a'                 },
668                 { "full",            no_argument,       NULL, 'l'                 },
669                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
670                 { "kill-who",        required_argument, NULL, ARG_KILL_WHO        },
671                 { "signal",          required_argument, NULL, 's'                 },
672                 { "host",            required_argument, NULL, 'H'                 },
673                 { "machine",         required_argument, NULL, 'M'                 },
674                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
675                 {}
676         };
677
678         int c, r;
679
680         assert(argc >= 0);
681         assert(argv);
682
683         while ((c = getopt_long(argc, argv, "hp:als:H:M:", options, NULL)) >= 0) {
684
685                 switch (c) {
686
687                 case 'h':
688                         return help();
689
690                 case ARG_VERSION:
691                         puts(PACKAGE_STRING);
692                         puts(SYSTEMD_FEATURES);
693                         return 0;
694
695                 case 'p':
696                         r = strv_extend(&arg_property, optarg);
697                         if (r < 0)
698                                 return log_oom();
699
700                         /* If the user asked for a particular
701                          * property, show it to him, even if it is
702                          * empty. */
703                         arg_all = true;
704                         break;
705
706                 case 'a':
707                         arg_all = true;
708                         break;
709
710                 case 'l':
711                         arg_full = true;
712                         break;
713
714                 case ARG_NO_PAGER:
715                         arg_no_pager = true;
716                         break;
717
718                 case ARG_NO_ASK_PASSWORD:
719                         arg_ask_password = false;
720                         break;
721
722                 case ARG_KILL_WHO:
723                         arg_kill_who = optarg;
724                         break;
725
726                 case 's':
727                         arg_signal = signal_from_string_try_harder(optarg);
728                         if (arg_signal < 0) {
729                                 log_error("Failed to parse signal string %s.", optarg);
730                                 return -EINVAL;
731                         }
732                         break;
733
734                 case 'H':
735                         arg_transport = BUS_TRANSPORT_REMOTE;
736                         arg_host = optarg;
737                         break;
738
739                 case 'M':
740                         arg_transport = BUS_TRANSPORT_CONTAINER;
741                         arg_host = optarg;
742                         break;
743
744                 case '?':
745                         return -EINVAL;
746
747                 default:
748                         assert_not_reached("Unhandled option");
749                 }
750         }
751
752         return 1;
753 }
754
755 static int machinectl_main(sd_bus *bus, int argc, char *argv[]) {
756
757         static const struct {
758                 const char* verb;
759                 const enum {
760                         MORE,
761                         LESS,
762                         EQUAL
763                 } argc_cmp;
764                 const int argc;
765                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
766         } verbs[] = {
767                 { "list",                  LESS,   1, list_machines     },
768                 { "status",                MORE,   2, show              },
769                 { "show",                  MORE,   1, show              },
770                 { "terminate",             MORE,   2, terminate_machine },
771                 { "kill",                  MORE,   2, kill_machine      },
772                 { "login",                 MORE,   2, login_machine     },
773         };
774
775         int left;
776         unsigned i;
777
778         assert(argc >= 0);
779         assert(argv);
780
781         left = argc - optind;
782
783         if (left <= 0)
784                 /* Special rule: no arguments means "list" */
785                 i = 0;
786         else {
787                 if (streq(argv[optind], "help")) {
788                         help();
789                         return 0;
790                 }
791
792                 for (i = 0; i < ELEMENTSOF(verbs); i++)
793                         if (streq(argv[optind], verbs[i].verb))
794                                 break;
795
796                 if (i >= ELEMENTSOF(verbs)) {
797                         log_error("Unknown operation %s", argv[optind]);
798                         return -EINVAL;
799                 }
800         }
801
802         switch (verbs[i].argc_cmp) {
803
804         case EQUAL:
805                 if (left != verbs[i].argc) {
806                         log_error("Invalid number of arguments.");
807                         return -EINVAL;
808                 }
809
810                 break;
811
812         case MORE:
813                 if (left < verbs[i].argc) {
814                         log_error("Too few arguments.");
815                         return -EINVAL;
816                 }
817
818                 break;
819
820         case LESS:
821                 if (left > verbs[i].argc) {
822                         log_error("Too many arguments.");
823                         return -EINVAL;
824                 }
825
826                 break;
827
828         default:
829                 assert_not_reached("Unknown comparison operator.");
830         }
831
832         return verbs[i].dispatch(bus, argv + optind, left);
833 }
834
835 int main(int argc, char*argv[]) {
836         _cleanup_bus_unref_ sd_bus *bus = NULL;
837         int r;
838
839         setlocale(LC_ALL, "");
840         log_parse_environment();
841         log_open();
842
843         r = parse_argv(argc, argv);
844         if (r <= 0)
845                 goto finish;
846
847         r = bus_open_transport(arg_transport, arg_host, false, &bus);
848         if (r < 0) {
849                 log_error("Failed to create bus connection: %s", strerror(-r));
850                 goto finish;
851         }
852
853         r = machinectl_main(bus, argc, argv);
854
855 finish:
856         pager_close();
857
858         strv_free(arg_property);
859
860         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
861 }