chiark / gitweb /
machinectl: privileged option is gone
[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                 { "no-ask-password", no_argument,       NULL, ARG_NO_ASK_PASSWORD },
576                 { NULL,              0,                 NULL, 0                   }
577         };
578
579         int c;
580
581         assert(argc >= 0);
582         assert(argv);
583
584         while ((c = getopt_long(argc, argv, "hp:als:H:P", options, NULL)) >= 0) {
585
586                 switch (c) {
587
588                 case 'h':
589                         help();
590                         return 0;
591
592                 case ARG_VERSION:
593                         puts(PACKAGE_STRING);
594                         puts(SYSTEMD_FEATURES);
595                         return 0;
596
597                 case 'p': {
598                         char **l;
599
600                         l = strv_append(arg_property, optarg);
601                         if (!l)
602                                 return -ENOMEM;
603
604                         strv_free(arg_property);
605                         arg_property = l;
606
607                         /* If the user asked for a particular
608                          * property, show it to him, even if it is
609                          * empty. */
610                         arg_all = true;
611                         break;
612                 }
613
614                 case 'a':
615                         arg_all = true;
616                         break;
617
618                 case 'l':
619                         arg_full = true;
620                         break;
621
622                 case ARG_NO_PAGER:
623                         arg_no_pager = true;
624                         break;
625
626                 case ARG_NO_ASK_PASSWORD:
627                         arg_ask_password = false;
628                         break;
629
630                 case ARG_KILL_WHO:
631                         arg_kill_who = optarg;
632                         break;
633
634                 case 's':
635                         arg_signal = signal_from_string_try_harder(optarg);
636                         if (arg_signal < 0) {
637                                 log_error("Failed to parse signal string %s.", optarg);
638                                 return -EINVAL;
639                         }
640                         break;
641
642                 case 'H':
643                         arg_transport = TRANSPORT_SSH;
644                         parse_user_at_host(optarg, &arg_user, &arg_host);
645                         break;
646
647                 case '?':
648                         return -EINVAL;
649
650                 default:
651                         log_error("Unknown option code %c", c);
652                         return -EINVAL;
653                 }
654         }
655
656         return 1;
657 }
658
659 static int machinectl_main(sd_bus *bus, int argc, char *argv[], const int r) {
660
661         static const struct {
662                 const char* verb;
663                 const enum {
664                         MORE,
665                         LESS,
666                         EQUAL
667                 } argc_cmp;
668                 const int argc;
669                 int (* const dispatch)(sd_bus *bus, char **args, unsigned n);
670         } verbs[] = {
671                 { "list",                  LESS,   1, list_machines     },
672                 { "status",                MORE,   2, show              },
673                 { "show",                  MORE,   1, show              },
674                 { "terminate",             MORE,   2, terminate_machine },
675                 { "kill",                  MORE,   2, kill_machine      },
676         };
677
678         int left;
679         unsigned i;
680
681         assert(argc >= 0);
682         assert(argv);
683
684         left = argc - optind;
685
686         if (left <= 0)
687                 /* Special rule: no arguments means "list-sessions" */
688                 i = 0;
689         else {
690                 if (streq(argv[optind], "help")) {
691                         help();
692                         return 0;
693                 }
694
695                 for (i = 0; i < ELEMENTSOF(verbs); i++)
696                         if (streq(argv[optind], verbs[i].verb))
697                                 break;
698
699                 if (i >= ELEMENTSOF(verbs)) {
700                         log_error("Unknown operation %s", argv[optind]);
701                         return -EINVAL;
702                 }
703         }
704
705         switch (verbs[i].argc_cmp) {
706
707         case EQUAL:
708                 if (left != verbs[i].argc) {
709                         log_error("Invalid number of arguments.");
710                         return -EINVAL;
711                 }
712
713                 break;
714
715         case MORE:
716                 if (left < verbs[i].argc) {
717                         log_error("Too few arguments.");
718                         return -EINVAL;
719                 }
720
721                 break;
722
723         case LESS:
724                 if (left > verbs[i].argc) {
725                         log_error("Too many arguments.");
726                         return -EINVAL;
727                 }
728
729                 break;
730
731         default:
732                 assert_not_reached("Unknown comparison operator.");
733         }
734
735         if (r < 0) {
736                 log_error("Failed to get D-Bus connection: %s", strerror(-r));
737                 return -EIO;
738         }
739
740         return verbs[i].dispatch(bus, argv + optind, left);
741 }
742
743 int main(int argc, char*argv[]) {
744         int r, retval = EXIT_FAILURE;
745         _cleanup_bus_unref_ sd_bus *bus = NULL;
746
747         setlocale(LC_ALL, "");
748         log_parse_environment();
749         log_open();
750
751         r = parse_argv(argc, argv);
752         if (r < 0)
753                 goto finish;
754         else if (r == 0) {
755                 retval = EXIT_SUCCESS;
756                 goto finish;
757         }
758
759         if (arg_transport == TRANSPORT_NORMAL)
760                 r = sd_bus_open_system(&bus);
761         else if (arg_transport == TRANSPORT_SSH)
762                 r = bus_connect_system_ssh(arg_host, &bus);
763         else
764                 assert_not_reached("Uh, invalid transport...");
765         if (r < 0) {
766                 retval = EXIT_FAILURE;
767                 goto finish;
768         }
769
770         r = machinectl_main(bus, argc, argv, r);
771         retval = r < 0 ? EXIT_FAILURE : r;
772
773 finish:
774         strv_free(arg_property);
775
776         pager_close();
777
778         return retval;
779 }