]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/machine/machinectl.c
bus: let's simplify things by getting rid of unnecessary bus parameters
[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
74c7b1ed 87 printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE");
1ee306e1 88
a1da8583
TG
89 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssso)");
90 if (r < 0)
5b30bef8 91 return bus_log_parse_error(r);
1ee306e1 92
a1da8583 93 while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) {
1ee306e1
LP
94 printf("%-32s %-9s %-16s\n", name, class, service);
95
96 k++;
1ee306e1 97 }
a1da8583 98 if (r < 0)
5b30bef8 99 return bus_log_parse_error(r);
a1da8583
TG
100
101 r = sd_bus_message_exit_container(reply);
102 if (r < 0)
5b30bef8 103 return bus_log_parse_error(r);
1ee306e1 104
74c7b1ed 105 printf("\n%u machines listed.\n", k);
1ee306e1
LP
106
107 return 0;
108}
109
a1da8583
TG
110static 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;
aa1936ea 113 _cleanup_free_ char *path = NULL;
aa1936ea 114 const char *cgroup;
aa1936ea
LP
115 int r, output_flags;
116 unsigned c;
117
118 assert(bus);
119 assert(unit);
120
d21ed1ea 121 if (arg_transport == BUS_TRANSPORT_REMOTE)
aa1936ea
LP
122 return 0;
123
124 path = unit_dbus_path_from_name(unit);
125 if (!path)
126 return log_oom();
127
a7893c6b 128 r = sd_bus_get_property(
aa1936ea
LP
129 bus,
130 "org.freedesktop.systemd1",
131 path,
a7893c6b
LP
132 "org.freedesktop.systemd1.Scope",
133 "ControlGroup",
aa1936ea 134 &error,
a1da8583 135 &reply,
a7893c6b 136 "s");
aa1936ea 137 if (r < 0) {
a1da8583 138 log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r));
aa1936ea
LP
139 return r;
140 }
141
a7893c6b 142 r = sd_bus_message_read(reply, "s", &cgroup);
5b30bef8
LP
143 if (r < 0)
144 return bus_log_parse_error(r);
aa1936ea 145
9d127096
LP
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
aa1936ea
LP
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
9d127096 162 show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, output_flags);
aa1936ea
LP
163 return 0;
164}
165
1ee306e1 166typedef struct MachineStatusInfo {
9f6eb1cd 167 char *name;
1ee306e1 168 sd_id128_t id;
9f6eb1cd
KS
169 char *class;
170 char *service;
171 char *scope;
172 char *root_directory;
1ee306e1
LP
173 pid_t leader;
174 usec_t timestamp;
175} MachineStatusInfo;
176
a1da8583 177static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
1ee306e1
LP
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
1ee306e1
LP
219 if (i->root_directory)
220 printf("\t Root: %s\n", i->root_directory);
221
aa1936ea
LP
222 if (i->scope) {
223 printf("\t Unit: %s\n", i->scope);
9d127096 224 show_scope_cgroup(bus, i->scope, i->leader);
1ee306e1
LP
225 }
226}
227
9f6eb1cd 228static int show_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
a6c61602 229
9f6eb1cd
KS
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 };
a6c61602
LP
241
242 MachineStatusInfo info = {};
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
a6c61602
LP
608 container_bus = sd_bus_unref(container_bus);
609
04d39279
LP
610 assert_se(sigemptyset(&mask) == 0);
611 sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1);
612 assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
613
614 log_info("Connected to container %s. Press ^] three times within 1s to exit session.", args[1]);
615
616 r = process_pty(master, &mask, 0, 0);
617 if (r < 0) {
618 log_error("Failed to process pseudo tty: %s", strerror(-r));
619 return r;
620 }
621
622 fputc('\n', stdout);
623
624 log_info("Connection to container %s terminated.", args[1]);
625
626 return 0;
627}
628
1ee306e1
LP
629static int help(void) {
630
631 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
632 "Send control commands to or query the virtual machine and container registration manager.\n\n"
633 " -h --help Show this help\n"
634 " --version Show package version\n"
a7893c6b
LP
635 " --no-pager Do not pipe output into a pager\n"
636 " --no-ask-password Don't prompt for password\n"
53755121
LP
637 " -H --host=[USER@]HOST Operate on remote host\n"
638 " -M --machine=CONTAINER Operate on local container\n"
1ee306e1
LP
639 " -p --property=NAME Show only properties by this name\n"
640 " -a --all Show all properties, including empty ones\n"
1ee306e1 641 " -l --full Do not ellipsize output\n"
a7893c6b
LP
642 " --kill-who=WHO Who to send signal to\n"
643 " -s --signal=SIGNAL Which signal to send\n\n"
1ee306e1
LP
644 "Commands:\n"
645 " list List running VMs and containers\n"
646 " status [NAME...] Show VM/container status\n"
19887cd0 647 " show [NAME...] Show properties of one or more VMs/containers\n"
1ee306e1 648 " terminate [NAME...] Terminate one or more VMs/containers\n"
04d39279
LP
649 " kill [NAME...] Send signal to processes of a VM/container\n"
650 " login [NAME] Get a login prompt on a container\n",
1ee306e1
LP
651 program_invocation_short_name);
652
653 return 0;
654}
655
656static int parse_argv(int argc, char *argv[]) {
657
658 enum {
659 ARG_VERSION = 0x100,
660 ARG_NO_PAGER,
661 ARG_KILL_WHO,
662 ARG_NO_ASK_PASSWORD,
663 };
664
665 static const struct option options[] = {
666 { "help", no_argument, NULL, 'h' },
667 { "version", no_argument, NULL, ARG_VERSION },
668 { "property", required_argument, NULL, 'p' },
669 { "all", no_argument, NULL, 'a' },
670 { "full", no_argument, NULL, 'l' },
671 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
672 { "kill-who", required_argument, NULL, ARG_KILL_WHO },
673 { "signal", required_argument, NULL, 's' },
674 { "host", required_argument, NULL, 'H' },
a7893c6b 675 { "machine", required_argument, NULL, 'M' },
1ee306e1 676 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
eb9da376 677 {}
1ee306e1
LP
678 };
679
a7893c6b 680 int c, r;
1ee306e1
LP
681
682 assert(argc >= 0);
683 assert(argv);
684
a7893c6b 685 while ((c = getopt_long(argc, argv, "hp:als:H:M:", options, NULL)) >= 0) {
1ee306e1
LP
686
687 switch (c) {
688
689 case 'h':
eb9da376 690 return help();
1ee306e1
LP
691
692 case ARG_VERSION:
693 puts(PACKAGE_STRING);
694 puts(SYSTEMD_FEATURES);
695 return 0;
696
a7893c6b
LP
697 case 'p':
698 r = strv_extend(&arg_property, optarg);
699 if (r < 0)
700 return log_oom();
1ee306e1
LP
701
702 /* If the user asked for a particular
703 * property, show it to him, even if it is
704 * empty. */
705 arg_all = true;
706 break;
1ee306e1
LP
707
708 case 'a':
709 arg_all = true;
710 break;
711
712 case 'l':
713 arg_full = true;
714 break;
715
716 case ARG_NO_PAGER:
717 arg_no_pager = true;
718 break;
719
720 case ARG_NO_ASK_PASSWORD:
721 arg_ask_password = false;
722 break;
723
724 case ARG_KILL_WHO:
725 arg_kill_who = optarg;
726 break;
727
728 case 's':
729 arg_signal = signal_from_string_try_harder(optarg);
730 if (arg_signal < 0) {
731 log_error("Failed to parse signal string %s.", optarg);
732 return -EINVAL;
733 }
734 break;
735
1ee306e1 736 case 'H':
d21ed1ea 737 arg_transport = BUS_TRANSPORT_REMOTE;
a7893c6b
LP
738 arg_host = optarg;
739 break;
740
741 case 'M':
d21ed1ea 742 arg_transport = BUS_TRANSPORT_CONTAINER;
a7893c6b 743 arg_host = optarg;
1ee306e1
LP
744 break;
745
746 case '?':
747 return -EINVAL;
748
749 default:
eb9da376 750 assert_not_reached("Unhandled option");
1ee306e1
LP
751 }
752 }
753
754 return 1;
755}
756
04d39279 757static int machinectl_main(sd_bus *bus, int argc, char *argv[]) {
1ee306e1
LP
758
759 static const struct {
760 const char* verb;
761 const enum {
762 MORE,
763 LESS,
764 EQUAL
765 } argc_cmp;
766 const int argc;
a1da8583 767 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
1ee306e1
LP
768 } verbs[] = {
769 { "list", LESS, 1, list_machines },
770 { "status", MORE, 2, show },
771 { "show", MORE, 1, show },
772 { "terminate", MORE, 2, terminate_machine },
773 { "kill", MORE, 2, kill_machine },
04d39279 774 { "login", MORE, 2, login_machine },
1ee306e1
LP
775 };
776
777 int left;
778 unsigned i;
779
780 assert(argc >= 0);
781 assert(argv);
1ee306e1
LP
782
783 left = argc - optind;
784
785 if (left <= 0)
46e65dcc 786 /* Special rule: no arguments means "list" */
1ee306e1
LP
787 i = 0;
788 else {
789 if (streq(argv[optind], "help")) {
790 help();
791 return 0;
792 }
793
794 for (i = 0; i < ELEMENTSOF(verbs); i++)
795 if (streq(argv[optind], verbs[i].verb))
796 break;
797
798 if (i >= ELEMENTSOF(verbs)) {
799 log_error("Unknown operation %s", argv[optind]);
800 return -EINVAL;
801 }
802 }
803
804 switch (verbs[i].argc_cmp) {
805
806 case EQUAL:
807 if (left != verbs[i].argc) {
808 log_error("Invalid number of arguments.");
809 return -EINVAL;
810 }
811
812 break;
813
814 case MORE:
815 if (left < verbs[i].argc) {
816 log_error("Too few arguments.");
817 return -EINVAL;
818 }
819
820 break;
821
822 case LESS:
823 if (left > verbs[i].argc) {
824 log_error("Too many arguments.");
825 return -EINVAL;
826 }
827
828 break;
829
830 default:
831 assert_not_reached("Unknown comparison operator.");
832 }
833
1ee306e1
LP
834 return verbs[i].dispatch(bus, argv + optind, left);
835}
836
837int main(int argc, char*argv[]) {
a1da8583 838 _cleanup_bus_unref_ sd_bus *bus = NULL;
84f6181c 839 int r;
1ee306e1
LP
840
841 setlocale(LC_ALL, "");
842 log_parse_environment();
843 log_open();
844
845 r = parse_argv(argc, argv);
84f6181c 846 if (r <= 0)
1ee306e1 847 goto finish;
1ee306e1 848
d21ed1ea 849 r = bus_open_transport(arg_transport, arg_host, false, &bus);
a1da8583 850 if (r < 0) {
d21ed1ea 851 log_error("Failed to create bus connection: %s", strerror(-r));
a1da8583
TG
852 goto finish;
853 }
1ee306e1 854
04d39279 855 r = machinectl_main(bus, argc, argv);
1ee306e1
LP
856
857finish:
1ee306e1
LP
858 pager_close();
859
84f6181c
LP
860 strv_free(arg_property);
861
862 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1ee306e1 863}