1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2013 Lennart Poettering
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.
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.
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/>.
22 #include <sys/socket.h>
30 #include <netinet/in.h>
31 #include <arpa/inet.h>
40 #include "bus-error.h"
43 #include "unit-name.h"
44 #include "cgroup-show.h"
45 #include "cgroup-util.h"
47 #include "event-util.h"
49 static char **arg_property = NULL;
50 static bool arg_all = false;
51 static bool arg_full = false;
52 static bool arg_no_pager = false;
53 static bool arg_legend = true;
54 static const char *arg_kill_who = NULL;
55 static int arg_signal = SIGTERM;
56 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
57 static char *arg_host = NULL;
59 static void pager_open_if_enabled(void) {
61 /* Cache result before we open the pager */
68 static int list_machines(sd_bus *bus, char **args, unsigned n) {
69 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
70 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
71 const char *name, *class, *service, *object;
75 pager_open_if_enabled();
77 r = sd_bus_call_method(
79 "org.freedesktop.machine1",
80 "/org/freedesktop/machine1",
81 "org.freedesktop.machine1.Manager",
87 log_error("Could not get machines: %s", bus_error_message(&error, -r));
92 printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE");
94 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssso)");
96 return bus_log_parse_error(r);
98 while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) {
99 printf("%-32s %-9s %-16s\n", name, class, service);
104 return bus_log_parse_error(r);
106 r = sd_bus_message_exit_container(reply);
108 return bus_log_parse_error(r);
111 printf("\n%u machines listed.\n", k);
116 static int show_unit_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;
119 _cleanup_free_ char *path = NULL;
127 if (arg_transport == BUS_TRANSPORT_REMOTE)
130 path = unit_dbus_path_from_name(unit);
134 r = sd_bus_get_property(
136 "org.freedesktop.systemd1",
138 endswith(unit, ".scope") ? "org.freedesktop.systemd1.Scope" : "org.freedesktop.systemd1.Service",
144 log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r));
148 r = sd_bus_message_read(reply, "s", &cgroup);
150 return bus_log_parse_error(r);
155 if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0)
159 arg_all * OUTPUT_SHOW_ALL |
160 arg_full * OUTPUT_FULL_WIDTH;
168 show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, output_flags);
172 static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) {
173 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
181 r = sd_bus_call_method(bus,
182 "org.freedesktop.machine1",
183 "/org/freedesktop/machine1",
184 "org.freedesktop.machine1.Manager",
185 "GetMachineAddresses",
192 r = sd_bus_message_enter_container(reply, 'a', "(iay)");
194 return bus_log_parse_error(r);
196 while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) {
200 char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)];
202 r = sd_bus_message_read(reply, "i", &family);
204 return bus_log_parse_error(r);
206 r = sd_bus_message_read_array(reply, 'y', &a, &sz);
208 return bus_log_parse_error(r);
210 fputs(prefix, stdout);
211 fputs(inet_ntop(family, a, buffer, sizeof(buffer)), stdout);
212 if (family == AF_INET6 && ifi > 0)
216 r = sd_bus_message_exit_container(reply);
218 return bus_log_parse_error(r);
220 if (prefix != prefix2)
224 return bus_log_parse_error(r);
226 r = sd_bus_message_exit_container(reply);
228 return bus_log_parse_error(r);
233 static int print_os_release(sd_bus *bus, const char *name, const char *prefix) {
234 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
235 const char *k, *v, *pretty = NULL;
242 r = sd_bus_call_method(bus,
243 "org.freedesktop.machine1",
244 "/org/freedesktop/machine1",
245 "org.freedesktop.machine1.Manager",
246 "GetMachineOSRelease",
253 r = sd_bus_message_enter_container(reply, 'a', "{ss}");
255 return bus_log_parse_error(r);
257 while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) {
258 if (streq(k, "PRETTY_NAME"))
263 return bus_log_parse_error(r);
265 r = sd_bus_message_exit_container(reply);
267 return bus_log_parse_error(r);
270 printf("%s%s\n", prefix, pretty);
275 typedef struct MachineStatusInfo {
281 char *root_directory;
288 static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
289 char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
290 char since2[FORMAT_TIMESTAMP_MAX], *s2;
295 fputs(strna(i->name), stdout);
297 if (!sd_id128_equal(i->id, SD_ID128_NULL))
298 printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id));
302 s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp);
303 s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
306 printf("\t Since: %s; %s\n", s2, s1);
308 printf("\t Since: %s\n", s2);
311 _cleanup_free_ char *t = NULL;
313 printf("\t Leader: %u", (unsigned) i->leader);
315 get_process_comm(i->leader, &t);
323 printf("\t Service: %s", i->service);
326 printf("; class %s", i->class);
330 printf("\t Class: %s\n", i->class);
332 if (i->root_directory)
333 printf("\t Root: %s\n", i->root_directory);
335 if (i->n_netif > 0) {
338 fputs("\t Iface:", stdout);
340 for (c = 0; c < i->n_netif; c++) {
341 char name[IF_NAMESIZE+1] = "";
343 if (if_indextoname(i->netif[c], name)) {
352 printf(" %i", i->netif[c]);
358 print_addresses(bus, i->name, ifi,
362 print_os_release(bus, i->name, "\t OS: ");
365 printf("\t Unit: %s\n", i->unit);
366 show_unit_cgroup(bus, i->unit, i->leader);
370 static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
371 MachineStatusInfo *i = userdata;
376 assert_cc(sizeof(int32_t) == sizeof(int));
377 r = sd_bus_message_read_array(m, SD_BUS_TYPE_INT32, &v, &l);
383 i->n_netif = l / sizeof(int32_t);
384 i->netif = memdup(v, l);
391 static int show_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
393 static const struct bus_properties_map map[] = {
394 { "Name", "s", NULL, offsetof(MachineStatusInfo, name) },
395 { "Class", "s", NULL, offsetof(MachineStatusInfo, class) },
396 { "Service", "s", NULL, offsetof(MachineStatusInfo, service) },
397 { "Unit", "s", NULL, offsetof(MachineStatusInfo, unit) },
398 { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) },
399 { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) },
400 { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp) },
401 { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) },
402 { "NetworkInterfaces", "ai", map_netif, 0 },
406 MachineStatusInfo info = {};
412 r = bus_map_all_properties(bus,
413 "org.freedesktop.machine1",
418 return log_error_errno(r, "Could not get properties: %m");
424 print_machine_status_info(bus, &info);
430 free(info.root_directory);
436 static int show_properties(sd_bus *bus, const char *path, bool *new_line) {
444 r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all);
446 log_error_errno(r, "Could not get properties: %m");
451 static int show(sd_bus *bus, char **args, unsigned n) {
452 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
453 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
456 bool properties, new_line = false;
461 properties = !strstr(args[0], "status");
463 pager_open_if_enabled();
465 if (properties && n <= 1) {
467 /* If no argument is specified, inspect the manager
469 r = show_properties(bus, "/org/freedesktop/machine1", &new_line);
474 for (i = 1; i < n; i++) {
475 const char *path = NULL;
477 r = sd_bus_call_method(
479 "org.freedesktop.machine1",
480 "/org/freedesktop/machine1",
481 "org.freedesktop.machine1.Manager",
487 log_error("Could not get path to machine: %s", bus_error_message(&error, -r));
491 r = sd_bus_message_read(reply, "o", &path);
493 return bus_log_parse_error(r);
496 r = show_properties(bus, path, &new_line);
498 r = show_info(args[0], bus, path, &new_line);
504 static int kill_machine(sd_bus *bus, char **args, unsigned n) {
505 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
511 arg_kill_who = "all";
513 for (i = 1; i < n; i++) {
516 r = sd_bus_call_method(
518 "org.freedesktop.machine1",
519 "/org/freedesktop/machine1",
520 "org.freedesktop.machine1.Manager",
524 "ssi", args[i], arg_kill_who, arg_signal);
526 log_error("Could not kill machine: %s", bus_error_message(&error, -r));
534 static int reboot_machine(sd_bus *bus, char **args, unsigned n) {
535 arg_kill_who = "leader";
536 arg_signal = SIGINT; /* sysvinit + systemd */
538 return kill_machine(bus, args, n);
541 static int poweroff_machine(sd_bus *bus, char **args, unsigned n) {
542 arg_kill_who = "leader";
543 arg_signal = SIGRTMIN+4; /* only systemd */
545 return kill_machine(bus, args, n);
548 static int terminate_machine(sd_bus *bus, char **args, unsigned n) {
549 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
554 for (i = 1; i < n; i++) {
557 r = sd_bus_call_method(
559 "org.freedesktop.machine1",
560 "/org/freedesktop/machine1",
561 "org.freedesktop.machine1.Manager",
567 log_error("Could not terminate machine: %s", bus_error_message(&error, -r));
575 static int openpt_in_namespace(pid_t pid, int flags) {
576 _cleanup_close_pair_ int pair[2] = { -1, -1 };
577 _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1;
579 struct cmsghdr cmsghdr;
580 uint8_t buf[CMSG_SPACE(sizeof(int))];
583 .msg_control = &control,
584 .msg_controllen = sizeof(control),
586 struct cmsghdr *cmsg;
591 r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &rootfd);
595 if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0)
603 pair[0] = safe_close(pair[0]);
605 r = namespace_enter(pidnsfd, mntnsfd, -1, rootfd);
609 master = posix_openpt(flags);
613 cmsg = CMSG_FIRSTHDR(&mh);
614 cmsg->cmsg_level = SOL_SOCKET;
615 cmsg->cmsg_type = SCM_RIGHTS;
616 cmsg->cmsg_len = CMSG_LEN(sizeof(int));
617 memcpy(CMSG_DATA(cmsg), &master, sizeof(int));
619 mh.msg_controllen = cmsg->cmsg_len;
621 if (sendmsg(pair[1], &mh, MSG_NOSIGNAL) < 0)
627 pair[1] = safe_close(pair[1]);
629 r = wait_for_terminate(child, &si);
632 if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS)
635 if (recvmsg(pair[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC) < 0)
638 for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg))
639 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
643 fds = (int*) CMSG_DATA(cmsg);
644 n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
647 close_many(fds, n_fds);
660 static int login_machine(sd_bus *bus, char **args, unsigned n) {
661 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL, *reply2 = NULL, *reply3 = NULL;
662 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
663 _cleanup_bus_close_unref_ sd_bus *container_bus = NULL;
664 _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
665 _cleanup_event_unref_ sd_event *event = NULL;
666 _cleanup_close_ int master = -1;
667 _cleanup_free_ char *getty = NULL;
668 const char *path, *pty, *p;
676 if (arg_transport != BUS_TRANSPORT_LOCAL) {
677 log_error("Login only supported on local machines.");
681 r = sd_event_default(&event);
683 return log_error_errno(r, "Failed to get event loop: %m");
685 r = sd_bus_attach_event(bus, event, 0);
687 return log_error_errno(r, "Failed to attach bus to event loop: %m");
689 r = sd_bus_call_method(
691 "org.freedesktop.machine1",
692 "/org/freedesktop/machine1",
693 "org.freedesktop.machine1.Manager",
699 log_error("Could not get path to machine: %s", bus_error_message(&error, -r));
703 r = sd_bus_message_read(reply, "o", &path);
705 return bus_log_parse_error(r);
707 r = sd_bus_get_property(
709 "org.freedesktop.machine1",
711 "org.freedesktop.machine1.Machine",
717 return log_error_errno(r, "Failed to retrieve PID of leader: %m");
719 r = sd_bus_message_read(reply2, "u", &leader);
721 return bus_log_parse_error(r);
723 master = openpt_in_namespace(leader, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY);
725 return log_error_errno(master, "Failed to acquire pseudo tty: %m");
727 pty = ptsname(master);
729 return log_error_errno(errno, "Failed to get pty name: %m");
731 p = startswith(pty, "/dev/pts/");
733 log_error("Invalid pty name %s.", pty);
737 r = sd_bus_open_system_container(&container_bus, args[1]);
739 return log_error_errno(r, "Failed to get container bus: %m");
741 getty = strjoin("container-getty@", p, ".service", NULL);
745 if (unlockpt(master) < 0)
746 return log_error_errno(errno, "Failed to unlock tty: %m");
748 r = sd_bus_call_method(container_bus,
749 "org.freedesktop.systemd1",
750 "/org/freedesktop/systemd1",
751 "org.freedesktop.systemd1.Manager",
754 "ss", getty, "replace");
756 log_error("Failed to start getty service: %s", bus_error_message(&error, r));
760 container_bus = sd_bus_unref(container_bus);
762 assert_se(sigemptyset(&mask) == 0);
763 sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1);
764 assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
766 log_info("Connected to container %s. Press ^] three times within 1s to exit session.", args[1]);
768 sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
769 sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
771 r = pty_forward_new(event, master, &forward);
773 return log_error_errno(r, "Failed to create PTY forwarder: %m");
775 r = sd_event_loop(event);
777 return log_error_errno(r, "Failed to run event loop: %m");
779 forward = pty_forward_free(forward);
783 log_info("Connection to container %s terminated.", args[1]);
785 sd_event_get_exit_code(event, &ret);
789 static void help(void) {
790 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
791 "Send control commands to or query the virtual machine and container registration manager.\n\n"
792 " -h --help Show this help\n"
793 " --version Show package version\n"
794 " --no-pager Do not pipe output into a pager\n"
795 " --no-legend Do not show the headers and footers\n"
796 " -H --host=[USER@]HOST Operate on remote host\n"
797 " -M --machine=CONTAINER Operate on local container\n"
798 " -p --property=NAME Show only properties by this name\n"
799 " -a --all Show all properties, including empty ones\n"
800 " -l --full Do not ellipsize output\n"
801 " --kill-who=WHO Who to send signal to\n"
802 " -s --signal=SIGNAL Which signal to send\n\n"
804 " list List running VMs and containers\n"
805 " status NAME... Show VM/container status\n"
806 " show NAME... Show properties of one or more VMs/containers\n"
807 " login NAME Get a login prompt on a container\n"
808 " poweroff NAME... Power off one or more containers\n"
809 " reboot NAME... Reboot one or more containers\n"
810 " kill NAME... Send signal to processes of a VM/container\n"
811 " terminate NAME... Terminate one or more VMs/containers\n",
812 program_invocation_short_name);
815 static int parse_argv(int argc, char *argv[]) {
824 static const struct option options[] = {
825 { "help", no_argument, NULL, 'h' },
826 { "version", no_argument, NULL, ARG_VERSION },
827 { "property", required_argument, NULL, 'p' },
828 { "all", no_argument, NULL, 'a' },
829 { "full", no_argument, NULL, 'l' },
830 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
831 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
832 { "kill-who", required_argument, NULL, ARG_KILL_WHO },
833 { "signal", required_argument, NULL, 's' },
834 { "host", required_argument, NULL, 'H' },
835 { "machine", required_argument, NULL, 'M' },
844 while ((c = getopt_long(argc, argv, "hp:als:H:M:", options, NULL)) >= 0)
853 puts(PACKAGE_STRING);
854 puts(SYSTEMD_FEATURES);
858 r = strv_extend(&arg_property, optarg);
862 /* If the user asked for a particular
863 * property, show it to him, even if it is
885 arg_kill_who = optarg;
889 arg_signal = signal_from_string_try_harder(optarg);
890 if (arg_signal < 0) {
891 log_error("Failed to parse signal string %s.", optarg);
897 arg_transport = BUS_TRANSPORT_REMOTE;
902 arg_transport = BUS_TRANSPORT_CONTAINER;
910 assert_not_reached("Unhandled option");
916 static int machinectl_main(sd_bus *bus, int argc, char *argv[]) {
918 static const struct {
926 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
928 { "list", LESS, 1, list_machines },
929 { "status", MORE, 2, show },
930 { "show", MORE, 1, show },
931 { "terminate", MORE, 2, terminate_machine },
932 { "reboot", MORE, 2, reboot_machine },
933 { "poweroff", MORE, 2, poweroff_machine },
934 { "kill", MORE, 2, kill_machine },
935 { "login", MORE, 2, login_machine },
944 left = argc - optind;
947 /* Special rule: no arguments means "list" */
950 if (streq(argv[optind], "help")) {
955 for (i = 0; i < ELEMENTSOF(verbs); i++)
956 if (streq(argv[optind], verbs[i].verb))
959 if (i >= ELEMENTSOF(verbs)) {
960 log_error("Unknown operation %s", argv[optind]);
965 switch (verbs[i].argc_cmp) {
968 if (left != verbs[i].argc) {
969 log_error("Invalid number of arguments.");
976 if (left < verbs[i].argc) {
977 log_error("Too few arguments.");
984 if (left > verbs[i].argc) {
985 log_error("Too many arguments.");
992 assert_not_reached("Unknown comparison operator.");
995 return verbs[i].dispatch(bus, argv + optind, left);
998 int main(int argc, char*argv[]) {
999 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
1002 setlocale(LC_ALL, "");
1003 log_parse_environment();
1006 r = parse_argv(argc, argv);
1010 r = bus_open_transport(arg_transport, arg_host, false, &bus);
1012 log_error_errno(r, "Failed to create bus connection: %m");
1016 r = machinectl_main(bus, argc, argv);
1021 strv_free(arg_property);
1023 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;