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