]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/machine/machinectl.c
bus: log message parsing errors everywhere with a generalized bus_log_parse_error()
[thirdparty/systemd.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 if (on_tty())
88 printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE");
89
90 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssso)");
91 if (r < 0)
92 return bus_log_parse_error(r);
93
94 while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) {
95 printf("%-32s %-9s %-16s\n", name, class, service);
96
97 k++;
98 }
99 if (r < 0)
100 return bus_log_parse_error(r);
101
102 r = sd_bus_message_exit_container(reply);
103 if (r < 0)
104 return bus_log_parse_error(r);
105
106 if (on_tty())
107 printf("\n%u machines listed.\n", k);
108
109 return 0;
110 }
111
112 static int show_scope_cgroup(sd_bus *bus, const char *unit, pid_t leader) {
113 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
114 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
115 _cleanup_free_ char *path = NULL;
116 const char *cgroup;
117 int r, output_flags;
118 unsigned c;
119
120 assert(bus);
121 assert(unit);
122
123 if (arg_transport == BUS_TRANSPORT_REMOTE)
124 return 0;
125
126 path = unit_dbus_path_from_name(unit);
127 if (!path)
128 return log_oom();
129
130 r = sd_bus_get_property(
131 bus,
132 "org.freedesktop.systemd1",
133 path,
134 "org.freedesktop.systemd1.Scope",
135 "ControlGroup",
136 &error,
137 &reply,
138 "s");
139 if (r < 0) {
140 log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r));
141 return r;
142 }
143
144 r = sd_bus_message_read(reply, "s", &cgroup);
145 if (r < 0)
146 return bus_log_parse_error(r);
147
148 if (isempty(cgroup))
149 return 0;
150
151 if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0)
152 return 0;
153
154 output_flags =
155 arg_all * OUTPUT_SHOW_ALL |
156 arg_full * OUTPUT_FULL_WIDTH;
157
158 c = columns();
159 if (c > 18)
160 c -= 18;
161 else
162 c = 0;
163
164 show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, output_flags);
165 return 0;
166 }
167
168 typedef struct MachineStatusInfo {
169 char *name;
170 sd_id128_t id;
171 char *class;
172 char *service;
173 char *scope;
174 char *root_directory;
175 pid_t leader;
176 usec_t timestamp;
177 } MachineStatusInfo;
178
179 static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
180 char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
181 char since2[FORMAT_TIMESTAMP_MAX], *s2;
182 assert(i);
183
184 fputs(strna(i->name), stdout);
185
186 if (!sd_id128_equal(i->id, SD_ID128_NULL))
187 printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id));
188 else
189 putchar('\n');
190
191 s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp);
192 s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
193
194 if (s1)
195 printf("\t Since: %s; %s\n", s2, s1);
196 else if (s2)
197 printf("\t Since: %s\n", s2);
198
199 if (i->leader > 0) {
200 _cleanup_free_ char *t = NULL;
201
202 printf("\t Leader: %u", (unsigned) i->leader);
203
204 get_process_comm(i->leader, &t);
205 if (t)
206 printf(" (%s)", t);
207
208 putchar('\n');
209 }
210
211 if (i->service) {
212 printf("\t Service: %s", i->service);
213
214 if (i->class)
215 printf("; class %s", i->class);
216
217 putchar('\n');
218 } else if (i->class)
219 printf("\t Class: %s\n", i->class);
220
221 if (i->root_directory)
222 printf("\t Root: %s\n", i->root_directory);
223
224 if (i->scope) {
225 printf("\t Unit: %s\n", i->scope);
226 show_scope_cgroup(bus, i->scope, i->leader);
227 }
228 }
229
230 static int show_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
231 MachineStatusInfo info = {};
232 static const struct bus_properties_map map[] = {
233 { "Name", "s", NULL, offsetof(MachineStatusInfo, name) },
234 { "Class", "s", NULL, offsetof(MachineStatusInfo, class) },
235 { "Service", "s", NULL, offsetof(MachineStatusInfo, service) },
236 { "Scope", "s", NULL, offsetof(MachineStatusInfo, scope) },
237 { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) },
238 { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) },
239 { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp) },
240 { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) },
241 {}
242 };
243 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_free_ char *ns = NULL, *root = NULL;
403 _cleanup_close_pipe_ int sock[2] = { -1, -1 };
404 union {
405 struct cmsghdr cmsghdr;
406 uint8_t buf[CMSG_SPACE(sizeof(int))];
407 } control = {};
408 struct msghdr mh = {
409 .msg_control = &control,
410 .msg_controllen = sizeof(control),
411 };
412 struct cmsghdr *cmsg;
413 int master = -1, r;
414 pid_t child;
415 siginfo_t si;
416
417 r = asprintf(&ns, "/proc/%lu/ns/mnt", (unsigned long) pid);
418 if (r < 0)
419 return -ENOMEM;
420
421 nsfd = open(ns, O_RDONLY|O_NOCTTY|O_CLOEXEC);
422 if (nsfd < 0)
423 return -errno;
424
425 r = asprintf(&root, "/proc/%lu/root", (unsigned long) pid);
426 if (r < 0)
427 return -ENOMEM;
428
429 rootfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY);
430 if (rootfd < 0)
431 return -errno;
432
433 if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sock) < 0)
434 return -errno;
435
436 child = fork();
437 if (child < 0)
438 return -errno;
439
440 if (child == 0) {
441 close_nointr_nofail(sock[0]);
442 sock[0] = -1;
443
444 r = setns(nsfd, CLONE_NEWNS);
445 if (r < 0)
446 _exit(EXIT_FAILURE);
447
448 if (fchdir(rootfd) < 0)
449 _exit(EXIT_FAILURE);
450
451 if (chroot(".") < 0)
452 _exit(EXIT_FAILURE);
453
454 master = posix_openpt(flags);
455 if (master < 0)
456 _exit(EXIT_FAILURE);
457
458 cmsg = CMSG_FIRSTHDR(&mh);
459 cmsg->cmsg_level = SOL_SOCKET;
460 cmsg->cmsg_type = SCM_RIGHTS;
461 cmsg->cmsg_len = CMSG_LEN(sizeof(int));
462 memcpy(CMSG_DATA(cmsg), &master, sizeof(int));
463
464 mh.msg_controllen = cmsg->cmsg_len;
465
466 r = sendmsg(sock[1], &mh, MSG_NOSIGNAL);
467 close_nointr_nofail(master);
468 if (r < 0)
469 _exit(EXIT_FAILURE);
470
471 _exit(EXIT_SUCCESS);
472 }
473
474 close_nointr_nofail(sock[1]);
475 sock[1] = -1;
476
477 if (recvmsg(sock[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0)
478 return -errno;
479
480 for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg))
481 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
482 int *fds;
483 unsigned n_fds;
484
485 fds = (int*) CMSG_DATA(cmsg);
486 n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
487
488 if (n_fds != 1) {
489 close_many(fds, n_fds);
490 return -EIO;
491 }
492
493 master = fds[0];
494 }
495
496 r = wait_for_terminate(child, &si);
497 if (r < 0 || si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS || master < 0) {
498
499 if (master >= 0)
500 close_nointr_nofail(master);
501
502 return r < 0 ? r : -EIO;
503 }
504
505 return master;
506 }
507
508 static int login_machine(sd_bus *bus, char **args, unsigned n) {
509 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL, *reply2 = NULL, *reply3 = NULL;
510 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
511 _cleanup_bus_unref_ sd_bus *container_bus = NULL;
512 _cleanup_close_ int master = -1;
513 _cleanup_free_ char *getty = NULL;
514 const char *path, *pty, *p;
515 uint32_t leader;
516 sigset_t mask;
517 int r;
518
519 assert(bus);
520 assert(args);
521
522 if (arg_transport != BUS_TRANSPORT_LOCAL) {
523 log_error("Login only support on local machines.");
524 return -ENOTSUP;
525 }
526
527 r = sd_bus_call_method(
528 bus,
529 "org.freedesktop.machine1",
530 "/org/freedesktop/machine1",
531 "org.freedesktop.machine1.Manager",
532 "GetMachine",
533 &error,
534 &reply,
535 "s", args[1]);
536 if (r < 0) {
537 log_error("Could not get path to machine: %s", bus_error_message(&error, -r));
538 return r;
539 }
540
541 r = sd_bus_message_read(reply, "o", &path);
542 if (r < 0)
543 return bus_log_parse_error(r);
544
545 r = sd_bus_get_property(
546 bus,
547 "org.freedesktop.machine1",
548 path,
549 "org.freedesktop.machine1.Machine",
550 "Leader",
551 &error,
552 &reply2,
553 "u");
554 if (r < 0) {
555 log_error("Failed to retrieve PID of leader: %s", strerror(-r));
556 return r;
557 }
558
559 r = sd_bus_message_read(reply2, "u", &leader);
560 if (r < 0)
561 return bus_log_parse_error(r);
562
563 master = openpt_in_namespace(leader, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY);
564 if (master < 0) {
565 log_error("Failed to acquire pseudo tty: %s", strerror(-master));
566 return master;
567 }
568
569 pty = ptsname(master);
570 if (!pty) {
571 log_error("Failed to get pty name: %m");
572 return -errno;
573 }
574
575 p = startswith(pty, "/dev/pts/");
576 if (!p) {
577 log_error("Invalid pty name %s.", pty);
578 return -EIO;
579 }
580
581 r = sd_bus_open_system_container(args[1], &container_bus);
582 if (r < 0) {
583 log_error("Failed to get container bus: %s", strerror(-r));
584 return r;
585 }
586
587 getty = strjoin("container-getty@", p, ".service", NULL);
588 if (!getty)
589 return log_oom();
590
591 if (unlockpt(master) < 0) {
592 log_error("Failed to unlock tty: %m");
593 return -errno;
594 }
595
596 r = sd_bus_call_method(container_bus,
597 "org.freedesktop.systemd1",
598 "/org/freedesktop/systemd1",
599 "org.freedesktop.systemd1.Manager",
600 "StartUnit",
601 &error, &reply3,
602 "ss", getty, "replace");
603 if (r < 0) {
604 log_error("Failed to start getty service: %s", bus_error_message(&error, r));
605 return r;
606 }
607
608 assert_se(sigemptyset(&mask) == 0);
609 sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1);
610 assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
611
612 log_info("Connected to container %s. Press ^] three times within 1s to exit session.", args[1]);
613
614 r = process_pty(master, &mask, 0, 0);
615 if (r < 0) {
616 log_error("Failed to process pseudo tty: %s", strerror(-r));
617 return r;
618 }
619
620 fputc('\n', stdout);
621
622 log_info("Connection to container %s terminated.", args[1]);
623
624 return 0;
625 }
626
627 static int help(void) {
628
629 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
630 "Send control commands to or query the virtual machine and container registration manager.\n\n"
631 " -h --help Show this help\n"
632 " --version Show package version\n"
633 " --no-pager Do not pipe output into a pager\n"
634 " --no-ask-password Don't prompt for password\n"
635 " -H --host=[USER@]HOST Operate on remote host\n"
636 " -M --machine=CONTAINER Operate on local container\n"
637 " -p --property=NAME Show only properties by this name\n"
638 " -a --all Show all properties, including empty ones\n"
639 " -l --full Do not ellipsize output\n"
640 " --kill-who=WHO Who to send signal to\n"
641 " -s --signal=SIGNAL Which signal to send\n\n"
642 "Commands:\n"
643 " list List running VMs and containers\n"
644 " status [NAME...] Show VM/container status\n"
645 " show [NAME...] Show properties of one or more VMs/containers\n"
646 " terminate [NAME...] Terminate one or more VMs/containers\n"
647 " kill [NAME...] Send signal to processes of a VM/container\n"
648 " login [NAME] Get a login prompt on a container\n",
649 program_invocation_short_name);
650
651 return 0;
652 }
653
654 static int parse_argv(int argc, char *argv[]) {
655
656 enum {
657 ARG_VERSION = 0x100,
658 ARG_NO_PAGER,
659 ARG_KILL_WHO,
660 ARG_NO_ASK_PASSWORD,
661 };
662
663 static const struct option options[] = {
664 { "help", no_argument, NULL, 'h' },
665 { "version", no_argument, NULL, ARG_VERSION },
666 { "property", required_argument, NULL, 'p' },
667 { "all", no_argument, NULL, 'a' },
668 { "full", no_argument, NULL, 'l' },
669 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
670 { "kill-who", required_argument, NULL, ARG_KILL_WHO },
671 { "signal", required_argument, NULL, 's' },
672 { "host", required_argument, NULL, 'H' },
673 { "machine", required_argument, NULL, 'M' },
674 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
675 {}
676 };
677
678 int c, r;
679
680 assert(argc >= 0);
681 assert(argv);
682
683 while ((c = getopt_long(argc, argv, "hp:als:H:M:", options, NULL)) >= 0) {
684
685 switch (c) {
686
687 case 'h':
688 return help();
689
690 case ARG_VERSION:
691 puts(PACKAGE_STRING);
692 puts(SYSTEMD_FEATURES);
693 return 0;
694
695 case 'p':
696 r = strv_extend(&arg_property, optarg);
697 if (r < 0)
698 return log_oom();
699
700 /* If the user asked for a particular
701 * property, show it to him, even if it is
702 * empty. */
703 arg_all = true;
704 break;
705
706 case 'a':
707 arg_all = true;
708 break;
709
710 case 'l':
711 arg_full = true;
712 break;
713
714 case ARG_NO_PAGER:
715 arg_no_pager = true;
716 break;
717
718 case ARG_NO_ASK_PASSWORD:
719 arg_ask_password = false;
720 break;
721
722 case ARG_KILL_WHO:
723 arg_kill_who = optarg;
724 break;
725
726 case 's':
727 arg_signal = signal_from_string_try_harder(optarg);
728 if (arg_signal < 0) {
729 log_error("Failed to parse signal string %s.", optarg);
730 return -EINVAL;
731 }
732 break;
733
734 case 'H':
735 arg_transport = BUS_TRANSPORT_REMOTE;
736 arg_host = optarg;
737 break;
738
739 case 'M':
740 arg_transport = BUS_TRANSPORT_CONTAINER;
741 arg_host = optarg;
742 break;
743
744 case '?':
745 return -EINVAL;
746
747 default:
748 assert_not_reached("Unhandled option");
749 }
750 }
751
752 return 1;
753 }
754
755 static int machinectl_main(sd_bus *bus, int argc, char *argv[]) {
756
757 static const struct {
758 const char* verb;
759 const enum {
760 MORE,
761 LESS,
762 EQUAL
763 } argc_cmp;
764 const int argc;
765 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
766 } verbs[] = {
767 { "list", LESS, 1, list_machines },
768 { "status", MORE, 2, show },
769 { "show", MORE, 1, show },
770 { "terminate", MORE, 2, terminate_machine },
771 { "kill", MORE, 2, kill_machine },
772 { "login", MORE, 2, login_machine },
773 };
774
775 int left;
776 unsigned i;
777
778 assert(argc >= 0);
779 assert(argv);
780
781 left = argc - optind;
782
783 if (left <= 0)
784 /* Special rule: no arguments means "list" */
785 i = 0;
786 else {
787 if (streq(argv[optind], "help")) {
788 help();
789 return 0;
790 }
791
792 for (i = 0; i < ELEMENTSOF(verbs); i++)
793 if (streq(argv[optind], verbs[i].verb))
794 break;
795
796 if (i >= ELEMENTSOF(verbs)) {
797 log_error("Unknown operation %s", argv[optind]);
798 return -EINVAL;
799 }
800 }
801
802 switch (verbs[i].argc_cmp) {
803
804 case EQUAL:
805 if (left != verbs[i].argc) {
806 log_error("Invalid number of arguments.");
807 return -EINVAL;
808 }
809
810 break;
811
812 case MORE:
813 if (left < verbs[i].argc) {
814 log_error("Too few arguments.");
815 return -EINVAL;
816 }
817
818 break;
819
820 case LESS:
821 if (left > verbs[i].argc) {
822 log_error("Too many arguments.");
823 return -EINVAL;
824 }
825
826 break;
827
828 default:
829 assert_not_reached("Unknown comparison operator.");
830 }
831
832 return verbs[i].dispatch(bus, argv + optind, left);
833 }
834
835 int main(int argc, char*argv[]) {
836 _cleanup_bus_unref_ sd_bus *bus = NULL;
837 int r;
838
839 setlocale(LC_ALL, "");
840 log_parse_environment();
841 log_open();
842
843 r = parse_argv(argc, argv);
844 if (r <= 0)
845 goto finish;
846
847 r = bus_open_transport(arg_transport, arg_host, false, &bus);
848 if (r < 0) {
849 log_error("Failed to create bus connection: %s", strerror(-r));
850 goto finish;
851 }
852
853 r = machinectl_main(bus, argc, argv);
854
855 finish:
856 pager_close();
857
858 strv_free(arg_property);
859
860 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
861 }