chiark / gitweb /
man: document machinectl and systemd-machined
[elogind.git] / src / machine / machinectl.c
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
22 #include <dbus/dbus.h>
23 #include <unistd.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <getopt.h>
27 #include <pwd.h>
28 #include <locale.h>
29
30 #include "log.h"
31 #include "util.h"
32 #include "macro.h"
33 #include "pager.h"
34 #include "dbus-common.h"
35 #include "build.h"
36 #include "strv.h"
37 #include "cgroup-show.h"
38 #include "spawn-polkit-agent.h"
39
40 static char **arg_property = NULL;
41 static bool arg_all = false;
42 static bool arg_full = false;
43 static bool arg_no_pager = false;
44 static const char *arg_kill_who = NULL;
45 static int arg_signal = SIGTERM;
46 static enum transport {
47         TRANSPORT_NORMAL,
48         TRANSPORT_SSH,
49         TRANSPORT_POLKIT
50 } arg_transport = TRANSPORT_NORMAL;
51 static bool arg_ask_password = true;
52 static char *arg_host = NULL;
53 static char *arg_user = NULL;
54
55 static 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
64 static int list_machines(DBusConnection *bus, char **args, unsigned n) {
65         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
66         DBusMessageIter iter, sub, sub2;
67         unsigned k = 0;
68         int r;
69
70         pager_open_if_enabled();
71
72         r = bus_method_call_with_reply (
73                         bus,
74                         "org.freedesktop.machine1",
75                         "/org/freedesktop/machine1",
76                         "org.freedesktop.machine1.Manager",
77                         "ListMachines",
78                         &reply,
79                         NULL,
80                         DBUS_TYPE_INVALID);
81         if (r)
82                 return r;
83
84         if (!dbus_message_iter_init(reply, &iter) ||
85             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
86             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
87                 log_error("Failed to parse reply.");
88                 return -EIO;
89         }
90
91         dbus_message_iter_recurse(&iter, &sub);
92
93         if (on_tty())
94                 printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE");
95
96         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
97                 const char *name, *class, *service, *object;
98
99                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
100                         log_error("Failed to parse reply.");
101                         return -EIO;
102                 }
103
104                 dbus_message_iter_recurse(&sub, &sub2);
105
106                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0 ||
107                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &class, true) < 0 ||
108                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &service, true) < 0 ||
109                     bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_OBJECT_PATH, &object, false) < 0) {
110                         log_error("Failed to parse reply.");
111                         return -EIO;
112                 }
113
114                 printf("%-32s %-9s %-16s\n", name, class, service);
115
116                 k++;
117
118                 dbus_message_iter_next(&sub);
119         }
120
121         if (on_tty())
122                 printf("\n%u machines listed.\n", k);
123
124         return 0;
125 }
126
127 typedef struct MachineStatusInfo {
128         const char *name;
129         sd_id128_t id;
130         const char *default_control_group;
131         const char *class;
132         const char *service;
133         const char *slice;
134         const char *root_directory;
135         pid_t leader;
136         usec_t timestamp;
137 } MachineStatusInfo;
138
139 static void print_machine_status_info(MachineStatusInfo *i) {
140         char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
141         char since2[FORMAT_TIMESTAMP_MAX], *s2;
142         assert(i);
143
144         fputs(strna(i->name), stdout);
145
146         if (!sd_id128_equal(i->id, SD_ID128_NULL))
147                 printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id));
148         else
149                 putchar('\n');
150
151         s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp);
152         s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
153
154         if (s1)
155                 printf("\t   Since: %s; %s\n", s2, s1);
156         else if (s2)
157                 printf("\t   Since: %s\n", s2);
158
159         if (i->leader > 0) {
160                 _cleanup_free_ char *t = NULL;
161
162                 printf("\t  Leader: %u", (unsigned) i->leader);
163
164                 get_process_comm(i->leader, &t);
165                 if (t)
166                         printf(" (%s)", t);
167
168                 putchar('\n');
169         }
170
171         if (i->service) {
172                 printf("\t Service: %s", i->service);
173
174                 if (i->class)
175                         printf("; class %s", i->class);
176
177                 putchar('\n');
178         } else if (i->class)
179                 printf("\t   Class: %s\n", i->class);
180
181         if (i->slice)
182                 printf("\t   Slice: %s\n", i->slice);
183         if (i->root_directory)
184                 printf("\t    Root: %s\n", i->root_directory);
185
186         if (i->default_control_group) {
187                 unsigned c;
188                 int output_flags =
189                         arg_all * OUTPUT_SHOW_ALL |
190                         arg_full * OUTPUT_FULL_WIDTH;
191
192                 printf("\t  CGroup: %s\n", i->default_control_group);
193
194                 if (arg_transport != TRANSPORT_SSH) {
195                         c = columns();
196                         if (c > 18)
197                                 c -= 18;
198                         else
199                                 c = 0;
200
201                         show_cgroup_and_extra_by_spec(i->default_control_group,
202                                                       "\t\t  ", c, false, &i->leader,
203                                                       i->leader > 0 ? 1 : 0,
204                                                       output_flags);
205                 }
206         }
207 }
208
209 static int status_property_machine(const char *name, DBusMessageIter *iter, MachineStatusInfo *i) {
210         assert(name);
211         assert(iter);
212         assert(i);
213
214         switch (dbus_message_iter_get_arg_type(iter)) {
215
216         case DBUS_TYPE_STRING: {
217                 const char *s;
218
219                 dbus_message_iter_get_basic(iter, &s);
220
221                 if (!isempty(s)) {
222                         if (streq(name, "Name"))
223                                 i->name = s;
224                         else if (streq(name, "DefaultControlGroup"))
225                                 i->default_control_group = s;
226                         else if (streq(name, "Class"))
227                                 i->class = s;
228                         else if (streq(name, "Service"))
229                                 i->service = s;
230                         else if (streq(name, "Slice"))
231                                 i->slice = s;
232                         else if (streq(name, "RootDirectory"))
233                                 i->root_directory = s;
234                 }
235                 break;
236         }
237
238         case DBUS_TYPE_UINT32: {
239                 uint32_t u;
240
241                 dbus_message_iter_get_basic(iter, &u);
242
243                 if (streq(name, "Leader"))
244                         i->leader = (pid_t) u;
245
246                 break;
247         }
248
249         case DBUS_TYPE_UINT64: {
250                 uint64_t u;
251
252                 dbus_message_iter_get_basic(iter, &u);
253
254                 if (streq(name, "Timestamp"))
255                         i->timestamp = (usec_t) u;
256
257                 break;
258         }
259
260         case DBUS_TYPE_ARRAY: {
261                 DBusMessageIter sub;
262
263                 dbus_message_iter_recurse(iter, &sub);
264
265                 if (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_BYTE && streq(name, "Id")) {
266                         void *v;
267                         int n;
268
269                         dbus_message_iter_get_fixed_array(&sub, &v, &n);
270                         if (n == 0)
271                                 i->id = SD_ID128_NULL;
272                         else if (n == 16)
273                                 memcpy(&i->id, v, n);
274                 }
275
276                 break;
277         }
278         }
279
280         return 0;
281 }
282
283 static int print_property(const char *name, DBusMessageIter *iter) {
284         assert(name);
285         assert(iter);
286
287         if (arg_property && !strv_find(arg_property, name))
288                 return 0;
289
290         if (generic_print_property(name, iter, arg_all) > 0)
291                 return 0;
292
293         if (arg_all)
294                 printf("%s=[unprintable]\n", name);
295
296         return 0;
297 }
298
299 static int show_one(const char *verb, DBusConnection *bus, const char *path, bool show_properties, bool *new_line) {
300         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
301         const char *interface = "";
302         int r;
303         DBusMessageIter iter, sub, sub2, sub3;
304         MachineStatusInfo machine_info = {};
305
306         assert(path);
307         assert(new_line);
308
309         r = bus_method_call_with_reply(
310                         bus,
311                         "org.freedesktop.machine1",
312                         path,
313                         "org.freedesktop.DBus.Properties",
314                         "GetAll",
315                         &reply,
316                         NULL,
317                         DBUS_TYPE_STRING, &interface,
318                         DBUS_TYPE_INVALID);
319         if (r < 0)
320                 goto finish;
321
322         if (!dbus_message_iter_init(reply, &iter) ||
323             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
324             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
325                 log_error("Failed to parse reply.");
326                 r = -EIO;
327                 goto finish;
328         }
329
330         dbus_message_iter_recurse(&iter, &sub);
331
332         if (*new_line)
333                 printf("\n");
334
335         *new_line = true;
336
337         while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
338                 const char *name;
339
340                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_DICT_ENTRY) {
341                         log_error("Failed to parse reply.");
342                         r = -EIO;
343                         goto finish;
344                 }
345
346                 dbus_message_iter_recurse(&sub, &sub2);
347
348                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &name, true) < 0) {
349                         log_error("Failed to parse reply.");
350                         r = -EIO;
351                         goto finish;
352                 }
353
354                 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT)  {
355                         log_error("Failed to parse reply.");
356                         r = -EIO;
357                         goto finish;
358                 }
359
360                 dbus_message_iter_recurse(&sub2, &sub3);
361
362                 if (show_properties)
363                         r = print_property(name, &sub3);
364                 else
365                         r = status_property_machine(name, &sub3, &machine_info);
366
367                 if (r < 0) {
368                         log_error("Failed to parse reply.");
369                         goto finish;
370                 }
371
372                 dbus_message_iter_next(&sub);
373         }
374
375         if (!show_properties)
376                 print_machine_status_info(&machine_info);
377
378         r = 0;
379
380 finish:
381
382         return r;
383 }
384
385 static int show(DBusConnection *bus, char **args, unsigned n) {
386         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
387         int r, ret = 0;
388         DBusError error;
389         unsigned i;
390         bool show_properties, new_line = false;
391
392         assert(bus);
393         assert(args);
394
395         dbus_error_init(&error);
396
397         show_properties = !strstr(args[0], "status");
398
399         pager_open_if_enabled();
400
401         if (show_properties && n <= 1) {
402                 /* If not argument is specified inspect the manager
403                  * itself */
404
405                 ret = show_one(args[0], bus, "/org/freedesktop/machine1", show_properties, &new_line);
406                 goto finish;
407         }
408
409         for (i = 1; i < n; i++) {
410                 const char *path = NULL;
411
412                 ret = bus_method_call_with_reply(
413                                 bus,
414                                 "org.freedesktop.machine1",
415                                 "/org/freedesktop/machine1",
416                                 "org.freedesktop.machine1.Manager",
417                                 "GetMachine",
418                                 &reply,
419                                 NULL,
420                                 DBUS_TYPE_STRING, &args[i],
421                                 DBUS_TYPE_INVALID);
422                 if (ret < 0)
423                         goto finish;
424
425                 if (!dbus_message_get_args(reply, &error,
426                                            DBUS_TYPE_OBJECT_PATH, &path,
427                                            DBUS_TYPE_INVALID)) {
428                         log_error("Failed to parse reply: %s", bus_error_message(&error));
429                         ret = -EIO;
430                         goto finish;
431                 }
432
433                 r = show_one(args[0], bus, path, show_properties, &new_line);
434                 if (r != 0)
435                         ret = r;
436         }
437
438 finish:
439         dbus_error_free(&error);
440
441         return ret;
442 }
443
444 static int kill_machine(DBusConnection *bus, char **args, unsigned n) {
445         unsigned i;
446
447         assert(args);
448
449         if (!arg_kill_who)
450                 arg_kill_who = "all";
451
452         for (i = 1; i < n; i++) {
453                 int r;
454
455                 r = bus_method_call_with_reply (
456                         bus,
457                         "org.freedesktop.machine1",
458                         "/org/freedesktop/machine1",
459                         "org.freedesktop.machine1.Manager",
460                         "KillMachine",
461                         NULL,
462                         NULL,
463                         DBUS_TYPE_STRING, &args[i],
464                         DBUS_TYPE_STRING, &arg_kill_who,
465                         DBUS_TYPE_INT32, &arg_signal,
466                         DBUS_TYPE_INVALID);
467                 if (r)
468                         return r;
469         }
470
471         return 0;
472 }
473
474 static int terminate_machine(DBusConnection *bus, char **args, unsigned n) {
475         unsigned i;
476
477         assert(args);
478
479         for (i = 1; i < n; i++) {
480                 int r;
481
482                 r = bus_method_call_with_reply (
483                         bus,
484                         "org.freedesktop.machine1",
485                         "/org/freedesktop/machine1",
486                         "org.freedesktop.machine1.Manager",
487                         "TerminateMachine",
488                         NULL,
489                         NULL,
490                         DBUS_TYPE_STRING, &args[i],
491                         DBUS_TYPE_INVALID);
492                 if (r)
493                         return r;
494         }
495
496         return 0;
497 }
498
499 static int help(void) {
500
501         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
502                "Send control commands to or query the virtual machine and container registration manager.\n\n"
503                "  -h --help              Show this help\n"
504                "     --version           Show package version\n"
505                "  -p --property=NAME     Show only properties by this name\n"
506                "  -a --all               Show all properties, including empty ones\n"
507                "     --kill-who=WHO      Who to send signal to\n"
508                "  -l --full              Do not ellipsize output\n"
509                "  -s --signal=SIGNAL     Which signal to send\n"
510                "     --no-ask-password   Don't prompt for password\n"
511                "  -H --host=[USER@]HOST  Show information for remote host\n"
512                "  -P --privileged        Acquire privileges before execution\n"
513                "     --no-pager          Do not pipe output into a pager\n\n"
514                "Commands:\n"
515                "  list                   List running VMs and containers\n"
516                "  status [NAME...]       Show VM/container status\n"
517                "  show [NAME...]         Show properties of one or more VMs/containers\n"
518                "  terminate [NAME...]    Terminate one or more VMs/containers\n"
519                "  kill [NAME...]         Send signal to processes of a VM/container\n",
520                program_invocation_short_name);
521
522         return 0;
523 }
524
525 static int parse_argv(int argc, char *argv[]) {
526
527         enum {
528                 ARG_VERSION = 0x100,
529                 ARG_NO_PAGER,
530                 ARG_KILL_WHO,
531                 ARG_NO_ASK_PASSWORD,
532         };
533
534         static const struct option options[] = {
535                 { "help",            no_argument,       NULL, 'h'                 },
536                 { "version",         no_argument,       NULL, ARG_VERSION         },
537                 { "property",        required_argument, NULL, 'p'                 },
538                 { "all",             no_argument,       NULL, 'a'                 },
539                 { "full",            no_argument,       NULL, 'l'                 },
540                 { "no-pager",        no_argument,       NULL, ARG_NO_PAGER        },
541                 { "kill-who",        required_argument, NULL, ARG_KILL_WHO        },
542                 { "signal",          required_argument, NULL, 's'                 },
543                 { "host",            required_argument, NULL, 'H'                 },
544                 { "privileged",      no_argument,       NULL, 'P'                 },
545                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
546                 { NULL,              0,                 NULL, 0                   }
547         };
548
549         int c;
550
551         assert(argc >= 0);
552         assert(argv);
553
554         while ((c = getopt_long(argc, argv, "hp:als:H:P", options, NULL)) >= 0) {
555
556                 switch (c) {
557
558                 case 'h':
559                         help();
560                         return 0;
561
562                 case ARG_VERSION:
563                         puts(PACKAGE_STRING);
564                         puts(SYSTEMD_FEATURES);
565                         return 0;
566
567                 case 'p': {
568                         char **l;
569
570                         l = strv_append(arg_property, optarg);
571                         if (!l)
572                                 return -ENOMEM;
573
574                         strv_free(arg_property);
575                         arg_property = l;
576
577                         /* If the user asked for a particular
578                          * property, show it to him, even if it is
579                          * empty. */
580                         arg_all = true;
581                         break;
582                 }
583
584                 case 'a':
585                         arg_all = true;
586                         break;
587
588                 case 'l':
589                         arg_full = true;
590                         break;
591
592                 case ARG_NO_PAGER:
593                         arg_no_pager = true;
594                         break;
595
596                 case ARG_NO_ASK_PASSWORD:
597                         arg_ask_password = false;
598                         break;
599
600                 case ARG_KILL_WHO:
601                         arg_kill_who = optarg;
602                         break;
603
604                 case 's':
605                         arg_signal = signal_from_string_try_harder(optarg);
606                         if (arg_signal < 0) {
607                                 log_error("Failed to parse signal string %s.", optarg);
608                                 return -EINVAL;
609                         }
610                         break;
611
612                 case 'P':
613                         arg_transport = TRANSPORT_POLKIT;
614                         break;
615
616                 case 'H':
617                         arg_transport = TRANSPORT_SSH;
618                         parse_user_at_host(optarg, &arg_user, &arg_host);
619                         break;
620
621                 case '?':
622                         return -EINVAL;
623
624                 default:
625                         log_error("Unknown option code %c", c);
626                         return -EINVAL;
627                 }
628         }
629
630         return 1;
631 }
632
633 static int machinectl_main(DBusConnection *bus, int argc, char *argv[], DBusError *error) {
634
635         static const struct {
636                 const char* verb;
637                 const enum {
638                         MORE,
639                         LESS,
640                         EQUAL
641                 } argc_cmp;
642                 const int argc;
643                 int (* const dispatch)(DBusConnection *bus, char **args, unsigned n);
644         } verbs[] = {
645                 { "list",                  LESS,   1, list_machines     },
646                 { "status",                MORE,   2, show              },
647                 { "show",                  MORE,   1, show              },
648                 { "terminate",             MORE,   2, terminate_machine },
649                 { "kill",                  MORE,   2, kill_machine      },
650         };
651
652         int left;
653         unsigned i;
654
655         assert(argc >= 0);
656         assert(argv);
657         assert(error);
658
659         left = argc - optind;
660
661         if (left <= 0)
662                 /* Special rule: no arguments means "list-sessions" */
663                 i = 0;
664         else {
665                 if (streq(argv[optind], "help")) {
666                         help();
667                         return 0;
668                 }
669
670                 for (i = 0; i < ELEMENTSOF(verbs); i++)
671                         if (streq(argv[optind], verbs[i].verb))
672                                 break;
673
674                 if (i >= ELEMENTSOF(verbs)) {
675                         log_error("Unknown operation %s", argv[optind]);
676                         return -EINVAL;
677                 }
678         }
679
680         switch (verbs[i].argc_cmp) {
681
682         case EQUAL:
683                 if (left != verbs[i].argc) {
684                         log_error("Invalid number of arguments.");
685                         return -EINVAL;
686                 }
687
688                 break;
689
690         case MORE:
691                 if (left < verbs[i].argc) {
692                         log_error("Too few arguments.");
693                         return -EINVAL;
694                 }
695
696                 break;
697
698         case LESS:
699                 if (left > verbs[i].argc) {
700                         log_error("Too many arguments.");
701                         return -EINVAL;
702                 }
703
704                 break;
705
706         default:
707                 assert_not_reached("Unknown comparison operator.");
708         }
709
710         if (!bus) {
711                 log_error("Failed to get D-Bus connection: %s", error->message);
712                 return -EIO;
713         }
714
715         return verbs[i].dispatch(bus, argv + optind, left);
716 }
717
718 int main(int argc, char*argv[]) {
719         int r, retval = EXIT_FAILURE;
720         DBusConnection *bus = NULL;
721         DBusError error;
722
723         dbus_error_init(&error);
724
725         setlocale(LC_ALL, "");
726         log_parse_environment();
727         log_open();
728
729         r = parse_argv(argc, argv);
730         if (r < 0)
731                 goto finish;
732         else if (r == 0) {
733                 retval = EXIT_SUCCESS;
734                 goto finish;
735         }
736
737         if (arg_transport == TRANSPORT_NORMAL)
738                 bus = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
739         else if (arg_transport == TRANSPORT_POLKIT)
740                 bus_connect_system_polkit(&bus, &error);
741         else if (arg_transport == TRANSPORT_SSH)
742                 bus_connect_system_ssh(NULL, arg_host, &bus, &error);
743         else
744                 assert_not_reached("Uh, invalid transport...");
745
746         r = machinectl_main(bus, argc, argv, &error);
747         retval = r < 0 ? EXIT_FAILURE : r;
748
749 finish:
750         if (bus) {
751                 dbus_connection_flush(bus);
752                 dbus_connection_close(bus);
753                 dbus_connection_unref(bus);
754         }
755
756         dbus_error_free(&error);
757         dbus_shutdown();
758
759         strv_free(arg_property);
760
761         pager_close();
762
763         return retval;
764 }