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