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 <dbus/dbus.h>
34 #include "dbus-common.h"
37 #include "unit-name.h"
38 #include "cgroup-show.h"
39 #include "cgroup-util.h"
40 #include "spawn-polkit-agent.h"
42 static char **arg_property = NULL;
43 static bool arg_all = false;
44 static bool arg_full = false;
45 static bool arg_no_pager = false;
46 static const char *arg_kill_who = NULL;
47 static int arg_signal = SIGTERM;
48 static enum transport {
52 } arg_transport = TRANSPORT_NORMAL;
53 static bool arg_ask_password = true;
54 static char *arg_host = NULL;
55 static char *arg_user = NULL;
57 static void pager_open_if_enabled(void) {
59 /* Cache result before we open the pager */
66 static int list_machines(DBusConnection *bus, char **args, unsigned n) {
67 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
68 DBusMessageIter iter, sub, sub2;
72 pager_open_if_enabled();
74 r = bus_method_call_with_reply (
76 "org.freedesktop.machine1",
77 "/org/freedesktop/machine1",
78 "org.freedesktop.machine1.Manager",
86 if (!dbus_message_iter_init(reply, &iter) ||
87 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
88 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
89 log_error("Failed to parse reply.");
93 dbus_message_iter_recurse(&iter, &sub);
96 printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE");
98 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
99 const char *name, *class, *service, *object;
101 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
102 log_error("Failed to parse reply.");
106 dbus_message_iter_recurse(&sub, &sub2);
108 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0 ||
109 bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &class, true) < 0 ||
110 bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &service, true) < 0 ||
111 bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
112 log_error("Failed to parse reply.");
116 printf("%-32s %-9s %-16s\n", name, class, service);
120 dbus_message_iter_next(&sub);
124 printf("\n%u machines listed.\n", k);
129 static int show_scope_cgroup(DBusConnection *bus, const char *unit, pid_t leader) {
130 const char *interface = "org.freedesktop.systemd1.Scope";
131 const char *property = "ControlGroup";
132 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
133 _cleanup_free_ char *path = NULL;
134 DBusMessageIter iter, sub;
143 if (arg_transport == TRANSPORT_SSH)
146 path = unit_dbus_path_from_name(unit);
150 r = bus_method_call_with_reply(
152 "org.freedesktop.systemd1",
154 "org.freedesktop.DBus.Properties",
158 DBUS_TYPE_STRING, &interface,
159 DBUS_TYPE_STRING, &property,
162 log_error("Failed to query ControlGroup: %s", bus_error(&error, r));
163 dbus_error_free(&error);
167 if (!dbus_message_iter_init(reply, &iter) ||
168 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
169 log_error("Failed to parse reply.");
173 dbus_message_iter_recurse(&iter, &sub);
174 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
175 log_error("Failed to parse reply.");
179 dbus_message_iter_get_basic(&sub, &cgroup);
184 if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0)
188 arg_all * OUTPUT_SHOW_ALL |
189 arg_full * OUTPUT_FULL_WIDTH;
197 show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, output_flags);
201 typedef struct MachineStatusInfo {
207 const char *root_directory;
212 static void print_machine_status_info(DBusConnection *bus, MachineStatusInfo *i) {
213 char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
214 char since2[FORMAT_TIMESTAMP_MAX], *s2;
217 fputs(strna(i->name), stdout);
219 if (!sd_id128_equal(i->id, SD_ID128_NULL))
220 printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id));
224 s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp);
225 s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
228 printf("\t Since: %s; %s\n", s2, s1);
230 printf("\t Since: %s\n", s2);
233 _cleanup_free_ char *t = NULL;
235 printf("\t Leader: %u", (unsigned) i->leader);
237 get_process_comm(i->leader, &t);
245 printf("\t Service: %s", i->service);
248 printf("; class %s", i->class);
252 printf("\t Class: %s\n", i->class);
254 if (i->root_directory)
255 printf("\t Root: %s\n", i->root_directory);
258 printf("\t Unit: %s\n", i->scope);
259 show_scope_cgroup(bus, i->scope, i->leader);
263 static int status_property_machine(const char *name, DBusMessageIter *iter, MachineStatusInfo *i) {
268 switch (dbus_message_iter_get_arg_type(iter)) {
270 case DBUS_TYPE_STRING: {
273 dbus_message_iter_get_basic(iter, &s);
276 if (streq(name, "Name"))
278 else if (streq(name, "Class"))
280 else if (streq(name, "Service"))
282 else if (streq(name, "Scope"))
284 else if (streq(name, "RootDirectory"))
285 i->root_directory = s;
290 case DBUS_TYPE_UINT32: {
293 dbus_message_iter_get_basic(iter, &u);
295 if (streq(name, "Leader"))
296 i->leader = (pid_t) u;
301 case DBUS_TYPE_UINT64: {
304 dbus_message_iter_get_basic(iter, &u);
306 if (streq(name, "Timestamp"))
307 i->timestamp = (usec_t) u;
312 case DBUS_TYPE_ARRAY: {
315 dbus_message_iter_recurse(iter, &sub);
317 if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_BYTE && streq(name, "Id")) {
321 dbus_message_iter_get_fixed_array(&sub, &v, &n);
323 i->id = SD_ID128_NULL;
325 memcpy(&i->id, v, n);
335 static int print_property(const char *name, DBusMessageIter *iter) {
339 if (arg_property && !strv_find(arg_property, name))
342 if (generic_print_property(name, iter, arg_all) > 0)
346 printf("%s=[unprintable]\n", name);
351 static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) {
352 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
353 const char *interface = "";
355 DBusMessageIter iter, sub, sub2, sub3;
356 MachineStatusInfo machine_info = {};
361 r = bus_method_call_with_reply(
363 "org.freedesktop.machine1",
365 "org.freedesktop.DBus.Properties",
369 DBUS_TYPE_STRING, &interface,
374 if (!dbus_message_iter_init(reply, &iter) ||
375 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
376 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
377 log_error("Failed to parse reply.");
382 dbus_message_iter_recurse(&iter, &sub);
389 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
392 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
393 log_error("Failed to parse reply.");
398 dbus_message_iter_recurse(&sub, &sub2);
400 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
401 log_error("Failed to parse reply.");
406 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
407 log_error("Failed to parse reply.");
412 dbus_message_iter_recurse(&sub2, &sub3);
415 r = print_property(name, &sub3);
417 r = status_property_machine(name, &sub3, &machine_info);
420 log_error("Failed to parse reply.");
424 dbus_message_iter_next(&sub);
427 if (!show_properties)
428 print_machine_status_info(bus, &machine_info);
437 static int show(DBusConnection *bus, char **args, unsigned n) {
438 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
442 bool show_properties, new_line = false;
447 dbus_error_init(&error);
449 show_properties = !strstr(args[0], "status");
451 pager_open_if_enabled();
453 if (show_properties && n <= 1) {
454 /* If not argument is specified inspect the manager
457 ret = show_one(args[0], bus, "/org/freedesktop/machine1", show_properties, &new_line);
461 for (i = 1; i < n; i++) {
462 const char *path = NULL;
464 ret = bus_method_call_with_reply(
466 "org.freedesktop.machine1",
467 "/org/freedesktop/machine1",
468 "org.freedesktop.machine1.Manager",
472 DBUS_TYPE_STRING, &args[i],
477 if (!dbus_message_get_args(reply, &error,
478 DBUS_TYPE_OBJECT_PATH, &path,
479 DBUS_TYPE_INVALID)) {
480 log_error("Failed to parse reply: %s", bus_error_message(&error));
485 r = show_one(args[0], bus, path, show_properties, &new_line);
491 dbus_error_free(&error);
496 static int kill_machine(DBusConnection *bus, char **args, unsigned n) {
502 arg_kill_who = "all";
504 for (i = 1; i < n; i++) {
507 r = bus_method_call_with_reply (
509 "org.freedesktop.machine1",
510 "/org/freedesktop/machine1",
511 "org.freedesktop.machine1.Manager",
515 DBUS_TYPE_STRING, &args[i],
516 DBUS_TYPE_STRING, &arg_kill_who,
517 DBUS_TYPE_INT32, &arg_signal,
526 static int terminate_machine(DBusConnection *bus, char **args, unsigned n) {
531 for (i = 1; i < n; i++) {
534 r = bus_method_call_with_reply (
536 "org.freedesktop.machine1",
537 "/org/freedesktop/machine1",
538 "org.freedesktop.machine1.Manager",
542 DBUS_TYPE_STRING, &args[i],
551 static int help(void) {
553 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
554 "Send control commands to or query the virtual machine and container registration manager.\n\n"
555 " -h --help Show this help\n"
556 " --version Show package version\n"
557 " -p --property=NAME Show only properties by this name\n"
558 " -a --all Show all properties, including empty ones\n"
559 " --kill-who=WHO Who to send signal to\n"
560 " -l --full Do not ellipsize output\n"
561 " -s --signal=SIGNAL Which signal to send\n"
562 " --no-ask-password Don't prompt for password\n"
563 " -H --host=[USER@]HOST Show information for remote host\n"
564 " -P --privileged Acquire privileges before execution\n"
565 " --no-pager Do not pipe output into a pager\n\n"
567 " list List running VMs and containers\n"
568 " status [NAME...] Show VM/container status\n"
569 " show [NAME...] Show properties of one or more VMs/containers\n"
570 " terminate [NAME...] Terminate one or more VMs/containers\n"
571 " kill [NAME...] Send signal to processes of a VM/container\n",
572 program_invocation_short_name);
577 static int parse_argv(int argc, char *argv[]) {
586 static const struct option options[] = {
587 { "help", no_argument, NULL, 'h' },
588 { "version", no_argument, NULL, ARG_VERSION },
589 { "property", required_argument, NULL, 'p' },
590 { "all", no_argument, NULL, 'a' },
591 { "full", no_argument, NULL, 'l' },
592 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
593 { "kill-who", required_argument, NULL, ARG_KILL_WHO },
594 { "signal", required_argument, NULL, 's' },
595 { "host", required_argument, NULL, 'H' },
596 { "privileged", no_argument, NULL, 'P' },
597 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
606 while ((c = getopt_long(argc, argv, "hp:als:H:P", options, NULL)) >= 0) {
615 puts(PACKAGE_STRING);
616 puts(SYSTEMD_FEATURES);
622 l = strv_append(arg_property, optarg);
626 strv_free(arg_property);
629 /* If the user asked for a particular
630 * property, show it to him, even if it is
648 case ARG_NO_ASK_PASSWORD:
649 arg_ask_password = false;
653 arg_kill_who = optarg;
657 arg_signal = signal_from_string_try_harder(optarg);
658 if (arg_signal < 0) {
659 log_error("Failed to parse signal string %s.", optarg);
665 arg_transport = TRANSPORT_POLKIT;
669 arg_transport = TRANSPORT_SSH;
670 parse_user_at_host(optarg, &arg_user, &arg_host);
677 log_error("Unknown option code %c", c);
685 static int machinectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
687 static const struct {
695 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
697 { "list", LESS, 1, list_machines },
698 { "status", MORE, 2, show },
699 { "show", MORE, 1, show },
700 { "terminate", MORE, 2, terminate_machine },
701 { "kill", MORE, 2, kill_machine },
711 left = argc - optind;
714 /* Special rule: no arguments means "list-sessions" */
717 if (streq(argv[optind], "help")) {
722 for (i = 0; i < ELEMENTSOF(verbs); i++)
723 if (streq(argv[optind], verbs[i].verb))
726 if (i >= ELEMENTSOF(verbs)) {
727 log_error("Unknown operation %s", argv[optind]);
732 switch (verbs[i].argc_cmp) {
735 if (left != verbs[i].argc) {
736 log_error("Invalid number of arguments.");
743 if (left < verbs[i].argc) {
744 log_error("Too few arguments.");
751 if (left > verbs[i].argc) {
752 log_error("Too many arguments.");
759 assert_not_reached("Unknown comparison operator.");
763 log_error("Failed to get D-Bus connection: %s", error->message);
767 return verbs[i].dispatch(bus, argv + optind, left);
770 int main(int argc, char*argv[]) {
771 int r, retval = EXIT_FAILURE;
772 DBusConnection *bus = NULL;
775 dbus_error_init(&error);
777 setlocale(LC_ALL, "");
778 log_parse_environment();
781 r = parse_argv(argc, argv);
785 retval = EXIT_SUCCESS;
789 if (arg_transport == TRANSPORT_NORMAL)
790 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
791 else if (arg_transport == TRANSPORT_POLKIT)
792 bus_connect_system_polkit(&bus, &error);
793 else if (arg_transport == TRANSPORT_SSH)
794 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
796 assert_not_reached("Uh, invalid transport...");
798 r = machinectl_main(bus, argc, argv, &error);
799 retval = r < 0 ? EXIT_FAILURE : r;
803 dbus_connection_flush(bus);
804 dbus_connection_close(bus);
805 dbus_connection_unref(bus);
808 dbus_error_free(&error);
811 strv_free(arg_property);