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