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