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