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