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