]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/machine/machinectl.c
bus: log message parsing errors everywhere with a generalized bus_log_parse_error()
[thirdparty/systemd.git] / src / machine / machinectl.c
CommitLineData
1ee306e1
LP
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
8bdbb8d9 22#include <sys/socket.h>
1ee306e1
LP
23#include <unistd.h>
24#include <errno.h>
25#include <string.h>
26#include <getopt.h>
27#include <pwd.h>
28#include <locale.h>
04d39279 29#include <fcntl.h>
1ee306e1 30
a1da8583 31#include "sd-bus.h"
1ee306e1
LP
32#include "log.h"
33#include "util.h"
34#include "macro.h"
35#include "pager.h"
a1da8583
TG
36#include "bus-util.h"
37#include "bus-error.h"
1ee306e1
LP
38#include "build.h"
39#include "strv.h"
aa1936ea 40#include "unit-name.h"
1ee306e1 41#include "cgroup-show.h"
9d127096 42#include "cgroup-util.h"
04d39279 43#include "ptyfwd.h"
1ee306e1
LP
44
45static char **arg_property = NULL;
46static bool arg_all = false;
47static bool arg_full = false;
48static bool arg_no_pager = false;
49static const char *arg_kill_who = NULL;
50static int arg_signal = SIGTERM;
1ee306e1 51static bool arg_ask_password = true;
d21ed1ea 52static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
1ee306e1 53static char *arg_host = NULL;
1ee306e1
LP
54
55static 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
a1da8583
TG
64static 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;
1ee306e1
LP
68 unsigned k = 0;
69 int r;
70
71 pager_open_if_enabled();
72
a1da8583
TG
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));
1ee306e1 84 return r;
1ee306e1
LP
85 }
86
1ee306e1
LP
87 if (on_tty())
88 printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE");
89
a1da8583
TG
90 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssso)");
91 if (r < 0)
5b30bef8 92 return bus_log_parse_error(r);
1ee306e1 93
a1da8583 94 while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) {
1ee306e1
LP
95 printf("%-32s %-9s %-16s\n", name, class, service);
96
97 k++;
1ee306e1 98 }
a1da8583 99 if (r < 0)
5b30bef8 100 return bus_log_parse_error(r);
a1da8583
TG
101
102 r = sd_bus_message_exit_container(reply);
103 if (r < 0)
5b30bef8 104 return bus_log_parse_error(r);
1ee306e1
LP
105
106 if (on_tty())
107 printf("\n%u machines listed.\n", k);
108
109 return 0;
110}
111
a1da8583
TG
112static 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;
aa1936ea 115 _cleanup_free_ char *path = NULL;
aa1936ea 116 const char *cgroup;
aa1936ea
LP
117 int r, output_flags;
118 unsigned c;
119
120 assert(bus);
121 assert(unit);
122
d21ed1ea 123 if (arg_transport == BUS_TRANSPORT_REMOTE)
aa1936ea
LP
124 return 0;
125
126 path = unit_dbus_path_from_name(unit);
127 if (!path)
128 return log_oom();
129
a7893c6b 130 r = sd_bus_get_property(
aa1936ea
LP
131 bus,
132 "org.freedesktop.systemd1",
133 path,
a7893c6b
LP
134 "org.freedesktop.systemd1.Scope",
135 "ControlGroup",
aa1936ea 136 &error,
a1da8583 137 &reply,
a7893c6b 138 "s");
aa1936ea 139 if (r < 0) {
a1da8583 140 log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r));
aa1936ea
LP
141 return r;
142 }
143
a7893c6b 144 r = sd_bus_message_read(reply, "s", &cgroup);
5b30bef8
LP
145 if (r < 0)
146 return bus_log_parse_error(r);
aa1936ea 147
9d127096
LP
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
aa1936ea
LP
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
9d127096 164 show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, output_flags);
aa1936ea
LP
165 return 0;
166}
167
1ee306e1 168typedef struct MachineStatusInfo {
9f6eb1cd 169 char *name;
1ee306e1 170 sd_id128_t id;
9f6eb1cd
KS
171 char *class;
172 char *service;
173 char *scope;
174 char *root_directory;
1ee306e1
LP
175 pid_t leader;
176 usec_t timestamp;
177} MachineStatusInfo;
178
a1da8583 179static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
1ee306e1
LP
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
1ee306e1
LP
221 if (i->root_directory)
222 printf("\t Root: %s\n", i->root_directory);
223
aa1936ea
LP
224 if (i->scope) {
225 printf("\t Unit: %s\n", i->scope);
9d127096 226 show_scope_cgroup(bus, i->scope, i->leader);
1ee306e1
LP
227 }
228}
229
9f6eb1cd
KS
230static 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 };
a1da8583
TG
243 int r;
244
1ee306e1
LP
245 assert(path);
246 assert(new_line);
247
9f6eb1cd
KS
248 r = bus_map_all_properties(bus,
249 "org.freedesktop.machine1",
250 path,
251 map,
252 &info);
a1da8583 253 if (r < 0) {
9f6eb1cd 254 log_error("Could not get properties: %s", strerror(-r));
a1da8583 255 return r;
1ee306e1
LP
256 }
257
1ee306e1
LP
258 if (*new_line)
259 printf("\n");
1ee306e1
LP
260 *new_line = true;
261
9f6eb1cd 262 print_machine_status_info(bus, &info);
1ee306e1 263
9f6eb1cd
KS
264 free(info.name);
265 free(info.class);
266 free(info.service);
267 free(info.scope);
268 free(info.root_directory);
1ee306e1 269
9f6eb1cd
KS
270 return r;
271}
1ee306e1 272
9f6eb1cd
KS
273static int show_properties(sd_bus *bus, const char *path, bool *new_line) {
274 int r;
1ee306e1 275
9f6eb1cd
KS
276 if (*new_line)
277 printf("\n");
1ee306e1 278
9f6eb1cd 279 *new_line = true;
a1da8583 280
27e72d6b 281 r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all);
a1da8583 282 if (r < 0)
9f6eb1cd 283 log_error("Could not get properties: %s", strerror(-r));
1ee306e1 284
a7893c6b 285 return r;
1ee306e1
LP
286}
287
a1da8583
TG
288static 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;
9f6eb1cd 291 int r = 0;
1ee306e1 292 unsigned i;
9f6eb1cd 293 bool properties, new_line = false;
1ee306e1
LP
294
295 assert(bus);
296 assert(args);
297
9f6eb1cd 298 properties = !strstr(args[0], "status");
1ee306e1
LP
299
300 pager_open_if_enabled();
301
9f6eb1cd 302 if (properties && n <= 1) {
a1da8583 303
9f6eb1cd 304 /* If no argument is specified, inspect the manager
1ee306e1 305 * itself */
9f6eb1cd
KS
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 }
1ee306e1
LP
311 }
312
313 for (i = 1; i < n; i++) {
314 const char *path = NULL;
315
a1da8583
TG
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);
5b30bef8
LP
331 if (r < 0)
332 return bus_log_parse_error(r);
1ee306e1 333
9f6eb1cd
KS
334 if (properties)
335 r = show_properties(bus, path, &new_line);
336 else
337 r = show_info(args[0], bus, path, &new_line);
1ee306e1
LP
338 }
339
9f6eb1cd 340 return r;
1ee306e1
LP
341}
342
a1da8583
TG
343static int kill_machine(sd_bus *bus, char **args, unsigned n) {
344 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1ee306e1
LP
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
a1da8583
TG
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));
1ee306e1 366 return r;
a1da8583 367 }
1ee306e1
LP
368 }
369
370 return 0;
371}
372
a1da8583
TG
373static int terminate_machine(sd_bus *bus, char **args, unsigned n) {
374 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1ee306e1
LP
375 unsigned i;
376
377 assert(args);
378
379 for (i = 1; i < n; i++) {
380 int r;
381
a1da8583
TG
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));
1ee306e1 393 return r;
a1da8583 394 }
1ee306e1
LP
395 }
396
397 return 0;
398}
399
04d39279
LP
400static 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 };
04d39279
LP
404 union {
405 struct cmsghdr cmsghdr;
406 uint8_t buf[CMSG_SPACE(sizeof(int))];
34a6778f
ZJS
407 } control = {};
408 struct msghdr mh = {
409 .msg_control = &control,
410 .msg_controllen = sizeof(control),
411 };
04d39279 412 struct cmsghdr *cmsg;
f69157a6 413 int master = -1, r;
04d39279
LP
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
04d39279
LP
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
508static 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);
5b30bef8
LP
542 if (r < 0)
543 return bus_log_parse_error(r);
04d39279
LP
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);
5b30bef8
LP
560 if (r < 0)
561 return bus_log_parse_error(r);
04d39279
LP
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
1ee306e1
LP
627static 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"
a7893c6b
LP
633 " --no-pager Do not pipe output into a pager\n"
634 " --no-ask-password Don't prompt for password\n"
53755121
LP
635 " -H --host=[USER@]HOST Operate on remote host\n"
636 " -M --machine=CONTAINER Operate on local container\n"
1ee306e1
LP
637 " -p --property=NAME Show only properties by this name\n"
638 " -a --all Show all properties, including empty ones\n"
1ee306e1 639 " -l --full Do not ellipsize output\n"
a7893c6b
LP
640 " --kill-who=WHO Who to send signal to\n"
641 " -s --signal=SIGNAL Which signal to send\n\n"
1ee306e1
LP
642 "Commands:\n"
643 " list List running VMs and containers\n"
644 " status [NAME...] Show VM/container status\n"
19887cd0 645 " show [NAME...] Show properties of one or more VMs/containers\n"
1ee306e1 646 " terminate [NAME...] Terminate one or more VMs/containers\n"
04d39279
LP
647 " kill [NAME...] Send signal to processes of a VM/container\n"
648 " login [NAME] Get a login prompt on a container\n",
1ee306e1
LP
649 program_invocation_short_name);
650
651 return 0;
652}
653
654static 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' },
a7893c6b 673 { "machine", required_argument, NULL, 'M' },
1ee306e1 674 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
eb9da376 675 {}
1ee306e1
LP
676 };
677
a7893c6b 678 int c, r;
1ee306e1
LP
679
680 assert(argc >= 0);
681 assert(argv);
682
a7893c6b 683 while ((c = getopt_long(argc, argv, "hp:als:H:M:", options, NULL)) >= 0) {
1ee306e1
LP
684
685 switch (c) {
686
687 case 'h':
eb9da376 688 return help();
1ee306e1
LP
689
690 case ARG_VERSION:
691 puts(PACKAGE_STRING);
692 puts(SYSTEMD_FEATURES);
693 return 0;
694
a7893c6b
LP
695 case 'p':
696 r = strv_extend(&arg_property, optarg);
697 if (r < 0)
698 return log_oom();
1ee306e1
LP
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;
1ee306e1
LP
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
1ee306e1 734 case 'H':
d21ed1ea 735 arg_transport = BUS_TRANSPORT_REMOTE;
a7893c6b
LP
736 arg_host = optarg;
737 break;
738
739 case 'M':
d21ed1ea 740 arg_transport = BUS_TRANSPORT_CONTAINER;
a7893c6b 741 arg_host = optarg;
1ee306e1
LP
742 break;
743
744 case '?':
745 return -EINVAL;
746
747 default:
eb9da376 748 assert_not_reached("Unhandled option");
1ee306e1
LP
749 }
750 }
751
752 return 1;
753}
754
04d39279 755static int machinectl_main(sd_bus *bus, int argc, char *argv[]) {
1ee306e1
LP
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;
a1da8583 765 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
1ee306e1
LP
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 },
04d39279 772 { "login", MORE, 2, login_machine },
1ee306e1
LP
773 };
774
775 int left;
776 unsigned i;
777
778 assert(argc >= 0);
779 assert(argv);
1ee306e1
LP
780
781 left = argc - optind;
782
783 if (left <= 0)
46e65dcc 784 /* Special rule: no arguments means "list" */
1ee306e1
LP
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
1ee306e1
LP
832 return verbs[i].dispatch(bus, argv + optind, left);
833}
834
835int main(int argc, char*argv[]) {
a1da8583 836 _cleanup_bus_unref_ sd_bus *bus = NULL;
84f6181c 837 int r;
1ee306e1
LP
838
839 setlocale(LC_ALL, "");
840 log_parse_environment();
841 log_open();
842
843 r = parse_argv(argc, argv);
84f6181c 844 if (r <= 0)
1ee306e1 845 goto finish;
1ee306e1 846
d21ed1ea 847 r = bus_open_transport(arg_transport, arg_host, false, &bus);
a1da8583 848 if (r < 0) {
d21ed1ea 849 log_error("Failed to create bus connection: %s", strerror(-r));
a1da8583
TG
850 goto finish;
851 }
1ee306e1 852
04d39279 853 r = machinectl_main(bus, argc, argv);
1ee306e1
LP
854
855finish:
1ee306e1
LP
856 pager_close();
857
84f6181c
LP
858 strv_free(arg_property);
859
860 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1ee306e1 861}