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