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