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