]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/machine/machinectl.c
sd-bus: rename sd_bus_open_system_container() to sd_bus_open_system_machine()
[thirdparty/systemd.git] / src / machine / machinectl.c
CommitLineData
1ee306e1
LP
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
8bdbb8d9 22#include <sys/socket.h>
1ee306e1
LP
23#include <unistd.h>
24#include <errno.h>
25#include <string.h>
26#include <getopt.h>
27#include <pwd.h>
28#include <locale.h>
04d39279 29#include <fcntl.h>
878cd7e9
LP
30#include <netinet/in.h>
31#include <arpa/inet.h>
f48e75cb 32#include <net/if.h>
785890ac 33#include <sys/mount.h>
f2cbe59e 34#include <libgen.h>
1ee306e1 35
a1da8583 36#include "sd-bus.h"
1ee306e1
LP
37#include "log.h"
38#include "util.h"
39#include "macro.h"
40#include "pager.h"
a1da8583
TG
41#include "bus-util.h"
42#include "bus-error.h"
1ee306e1
LP
43#include "build.h"
44#include "strv.h"
aa1936ea 45#include "unit-name.h"
1ee306e1 46#include "cgroup-show.h"
9d127096 47#include "cgroup-util.h"
04d39279 48#include "ptyfwd.h"
023fb90b 49#include "event-util.h"
785890ac
LP
50#include "path-util.h"
51#include "mkdir.h"
f2cbe59e 52#include "copy.h"
56159e0d 53#include "verbs.h"
1ee306e1
LP
54
55static char **arg_property = NULL;
56static bool arg_all = false;
57static bool arg_full = false;
58static bool arg_no_pager = false;
e56056e9 59static bool arg_legend = true;
1ee306e1
LP
60static const char *arg_kill_who = NULL;
61static int arg_signal = SIGTERM;
d21ed1ea 62static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
1ee306e1 63static char *arg_host = NULL;
785890ac
LP
64static bool arg_read_only = false;
65static bool arg_mkdir = false;
1ee306e1
LP
66
67static void pager_open_if_enabled(void) {
68
69 /* Cache result before we open the pager */
70 if (arg_no_pager)
71 return;
72
73 pager_open(false);
74}
75
56159e0d
LP
76static int list_machines(int argc, char *argv[], void *userdata) {
77
a1da8583
TG
78 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
79 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
80 const char *name, *class, *service, *object;
56159e0d 81 sd_bus *bus = userdata;
1ee306e1
LP
82 unsigned k = 0;
83 int r;
84
56159e0d
LP
85 assert(bus);
86
1ee306e1
LP
87 pager_open_if_enabled();
88
a1da8583
TG
89 r = sd_bus_call_method(
90 bus,
91 "org.freedesktop.machine1",
92 "/org/freedesktop/machine1",
93 "org.freedesktop.machine1.Manager",
94 "ListMachines",
95 &error,
96 &reply,
97 "");
98 if (r < 0) {
99 log_error("Could not get machines: %s", bus_error_message(&error, -r));
1ee306e1 100 return r;
1ee306e1
LP
101 }
102
e56056e9
TA
103 if (arg_legend)
104 printf("%-32s %-9s %-16s\n", "MACHINE", "CONTAINER", "SERVICE");
1ee306e1 105
a1da8583
TG
106 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssso)");
107 if (r < 0)
5b30bef8 108 return bus_log_parse_error(r);
1ee306e1 109
a1da8583 110 while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) {
1ee306e1
LP
111 printf("%-32s %-9s %-16s\n", name, class, service);
112
113 k++;
1ee306e1 114 }
a1da8583 115 if (r < 0)
5b30bef8 116 return bus_log_parse_error(r);
a1da8583
TG
117
118 r = sd_bus_message_exit_container(reply);
119 if (r < 0)
5b30bef8 120 return bus_log_parse_error(r);
1ee306e1 121
e56056e9
TA
122 if (arg_legend)
123 printf("\n%u machines listed.\n", k);
1ee306e1
LP
124
125 return 0;
126}
127
cd61c3bf
LP
128typedef struct ImageInfo {
129 const char *name;
130 const char *type;
131 bool read_only;
132} ImageInfo;
133
134static int compare_image_info(const void *a, const void *b) {
135 const ImageInfo *x = a, *y = b;
136
137 return strcmp(x->name, y->name);
138}
139
56159e0d 140static int list_images(int argc, char *argv[], void *userdata) {
cd61c3bf
LP
141
142 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
143 size_t max_name = strlen("NAME"), max_type = strlen("TYPE");
144 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
145 _cleanup_free_ ImageInfo *images = NULL;
146 size_t n_images = 0, n_allocated = 0, j;
147 const char *name, *type, *object;
56159e0d 148 sd_bus *bus = userdata;
cd61c3bf
LP
149 int read_only;
150 int r;
151
56159e0d
LP
152 assert(bus);
153
cd61c3bf
LP
154 pager_open_if_enabled();
155
156 r = sd_bus_call_method(
157 bus,
158 "org.freedesktop.machine1",
159 "/org/freedesktop/machine1",
160 "org.freedesktop.machine1.Manager",
161 "ListImages",
162 &error,
163 &reply,
164 "");
165 if (r < 0) {
166 log_error("Could not get images: %s", bus_error_message(&error, -r));
167 return r;
168 }
169
170 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbo)");
171 if (r < 0)
172 return bus_log_parse_error(r);
173
174 while ((r = sd_bus_message_read(reply, "(ssbo)", &name, &type, &read_only, &object)) > 0) {
175
176 if (name[0] == '.' && !arg_all)
177 continue;
178
179 if (!GREEDY_REALLOC(images, n_allocated, n_images + 1))
180 return log_oom();
181
182 images[n_images].name = name;
183 images[n_images].type = type;
184 images[n_images].read_only = read_only;
185
186 if (strlen(name) > max_name)
187 max_name = strlen(name);
188
189 if (strlen(type) > max_type)
190 max_type = strlen(type);
191
192 n_images++;
193 }
194 if (r < 0)
195 return bus_log_parse_error(r);
196
197 r = sd_bus_message_exit_container(reply);
198 if (r < 0)
199 return bus_log_parse_error(r);
200
201 qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info);
202
203 if (arg_legend)
204 printf("%-*s %-*s %-3s\n", (int) max_name, "NAME", (int) max_type, "TYPE", "RO");
205
206 for (j = 0; j < n_images; j++) {
207 printf("%-*s %-*s %-3s\n",
208 (int) max_name, images[j].name,
209 (int) max_type, images[j].type,
210 yes_no(images[j].read_only));
211 }
212
213 if (r < 0)
214 return bus_log_parse_error(r);
215
216
217 if (arg_legend)
218 printf("\n%zu images listed.\n", n_images);
219
220 return 0;
221}
222
89f7c846 223static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) {
a1da8583
TG
224 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
225 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
aa1936ea 226 _cleanup_free_ char *path = NULL;
aa1936ea 227 const char *cgroup;
aa1936ea
LP
228 int r, output_flags;
229 unsigned c;
230
231 assert(bus);
232 assert(unit);
233
d21ed1ea 234 if (arg_transport == BUS_TRANSPORT_REMOTE)
aa1936ea
LP
235 return 0;
236
237 path = unit_dbus_path_from_name(unit);
238 if (!path)
239 return log_oom();
240
a7893c6b 241 r = sd_bus_get_property(
aa1936ea
LP
242 bus,
243 "org.freedesktop.systemd1",
244 path,
89f7c846 245 endswith(unit, ".scope") ? "org.freedesktop.systemd1.Scope" : "org.freedesktop.systemd1.Service",
a7893c6b 246 "ControlGroup",
aa1936ea 247 &error,
a1da8583 248 &reply,
a7893c6b 249 "s");
aa1936ea 250 if (r < 0) {
a1da8583 251 log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r));
aa1936ea
LP
252 return r;
253 }
254
a7893c6b 255 r = sd_bus_message_read(reply, "s", &cgroup);
5b30bef8
LP
256 if (r < 0)
257 return bus_log_parse_error(r);
aa1936ea 258
9d127096
LP
259 if (isempty(cgroup))
260 return 0;
261
262 if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0)
263 return 0;
264
aa1936ea
LP
265 output_flags =
266 arg_all * OUTPUT_SHOW_ALL |
267 arg_full * OUTPUT_FULL_WIDTH;
268
269 c = columns();
270 if (c > 18)
271 c -= 18;
272 else
273 c = 0;
274
9d127096 275 show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, output_flags);
aa1936ea
LP
276 return 0;
277}
278
f48e75cb 279static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) {
878cd7e9
LP
280 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
281 int r;
282
283 assert(bus);
284 assert(name);
285 assert(prefix);
286 assert(prefix2);
287
288 r = sd_bus_call_method(bus,
289 "org.freedesktop.machine1",
290 "/org/freedesktop/machine1",
291 "org.freedesktop.machine1.Manager",
292 "GetMachineAddresses",
293 NULL,
294 &reply,
295 "s", name);
296 if (r < 0)
297 return r;
298
0dd25fb9 299 r = sd_bus_message_enter_container(reply, 'a', "(iay)");
878cd7e9
LP
300 if (r < 0)
301 return bus_log_parse_error(r);
302
0dd25fb9
LP
303 while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) {
304 int family;
878cd7e9
LP
305 const void *a;
306 size_t sz;
307 char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)];
308
0dd25fb9 309 r = sd_bus_message_read(reply, "i", &family);
878cd7e9
LP
310 if (r < 0)
311 return bus_log_parse_error(r);
312
313 r = sd_bus_message_read_array(reply, 'y', &a, &sz);
314 if (r < 0)
315 return bus_log_parse_error(r);
316
f48e75cb
LP
317 fputs(prefix, stdout);
318 fputs(inet_ntop(family, a, buffer, sizeof(buffer)), stdout);
319 if (family == AF_INET6 && ifi > 0)
320 printf("%%%i", ifi);
321 fputc('\n', stdout);
878cd7e9
LP
322
323 r = sd_bus_message_exit_container(reply);
324 if (r < 0)
325 return bus_log_parse_error(r);
326
327 if (prefix != prefix2)
328 prefix = prefix2;
329 }
330 if (r < 0)
331 return bus_log_parse_error(r);
332
333 r = sd_bus_message_exit_container(reply);
334 if (r < 0)
335 return bus_log_parse_error(r);
336
337 return 0;
338}
339
717603e3
LP
340static int print_os_release(sd_bus *bus, const char *name, const char *prefix) {
341 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
342 const char *k, *v, *pretty = NULL;
343 int r;
344
345 assert(bus);
346 assert(name);
347 assert(prefix);
348
349 r = sd_bus_call_method(bus,
350 "org.freedesktop.machine1",
351 "/org/freedesktop/machine1",
352 "org.freedesktop.machine1.Manager",
353 "GetMachineOSRelease",
354 NULL,
355 &reply,
356 "s", name);
357 if (r < 0)
358 return r;
359
360 r = sd_bus_message_enter_container(reply, 'a', "{ss}");
361 if (r < 0)
362 return bus_log_parse_error(r);
363
364 while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) {
365 if (streq(k, "PRETTY_NAME"))
366 pretty = v;
367
368 }
369 if (r < 0)
370 return bus_log_parse_error(r);
371
372 r = sd_bus_message_exit_container(reply);
373 if (r < 0)
374 return bus_log_parse_error(r);
375
376 if (pretty)
377 printf("%s%s\n", prefix, pretty);
378
379 return 0;
380}
381
1ee306e1 382typedef struct MachineStatusInfo {
9f6eb1cd 383 char *name;
1ee306e1 384 sd_id128_t id;
9f6eb1cd
KS
385 char *class;
386 char *service;
89f7c846 387 char *unit;
9f6eb1cd 388 char *root_directory;
1ee306e1
LP
389 pid_t leader;
390 usec_t timestamp;
f48e75cb
LP
391 int *netif;
392 unsigned n_netif;
1ee306e1
LP
393} MachineStatusInfo;
394
a1da8583 395static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
1ee306e1
LP
396 char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
397 char since2[FORMAT_TIMESTAMP_MAX], *s2;
f48e75cb
LP
398 int ifi = -1;
399
56159e0d 400 assert(bus);
1ee306e1
LP
401 assert(i);
402
403 fputs(strna(i->name), stdout);
404
405 if (!sd_id128_equal(i->id, SD_ID128_NULL))
406 printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id));
407 else
408 putchar('\n');
409
410 s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp);
411 s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
412
413 if (s1)
414 printf("\t Since: %s; %s\n", s2, s1);
415 else if (s2)
416 printf("\t Since: %s\n", s2);
417
418 if (i->leader > 0) {
419 _cleanup_free_ char *t = NULL;
420
421 printf("\t Leader: %u", (unsigned) i->leader);
422
423 get_process_comm(i->leader, &t);
424 if (t)
425 printf(" (%s)", t);
426
427 putchar('\n');
428 }
429
430 if (i->service) {
431 printf("\t Service: %s", i->service);
432
433 if (i->class)
434 printf("; class %s", i->class);
435
436 putchar('\n');
437 } else if (i->class)
438 printf("\t Class: %s\n", i->class);
439
1ee306e1
LP
440 if (i->root_directory)
441 printf("\t Root: %s\n", i->root_directory);
442
f48e75cb
LP
443 if (i->n_netif > 0) {
444 unsigned c;
445
446 fputs("\t Iface:", stdout);
447
448 for (c = 0; c < i->n_netif; c++) {
449 char name[IF_NAMESIZE+1] = "";
450
451 if (if_indextoname(i->netif[c], name)) {
452 fputc(' ', stdout);
453 fputs(name, stdout);
454
455 if (ifi < 0)
456 ifi = i->netif[c];
457 else
458 ifi = 0;
459 } else
460 printf(" %i", i->netif[c]);
461 }
462
463 fputc('\n', stdout);
464 }
465
466 print_addresses(bus, i->name, ifi,
878cd7e9
LP
467 "\t Address: ",
468 "\t ");
469
717603e3
LP
470 print_os_release(bus, i->name, "\t OS: ");
471
89f7c846
LP
472 if (i->unit) {
473 printf("\t Unit: %s\n", i->unit);
474 show_unit_cgroup(bus, i->unit, i->leader);
1ee306e1
LP
475 }
476}
477
f48e75cb
LP
478static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
479 MachineStatusInfo *i = userdata;
480 size_t l;
481 const void *v;
482 int r;
483
484 assert_cc(sizeof(int32_t) == sizeof(int));
485 r = sd_bus_message_read_array(m, SD_BUS_TYPE_INT32, &v, &l);
486 if (r < 0)
487 return r;
e7e9b6bb
ZJS
488 if (r == 0)
489 return -EBADMSG;
f48e75cb
LP
490
491 i->n_netif = l / sizeof(int32_t);
492 i->netif = memdup(v, l);
493 if (!i->netif)
494 return -ENOMEM;
495
496 return 0;
497}
498
9f6eb1cd 499static int show_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
a6c61602 500
9f6eb1cd 501 static const struct bus_properties_map map[] = {
f48e75cb
LP
502 { "Name", "s", NULL, offsetof(MachineStatusInfo, name) },
503 { "Class", "s", NULL, offsetof(MachineStatusInfo, class) },
504 { "Service", "s", NULL, offsetof(MachineStatusInfo, service) },
505 { "Unit", "s", NULL, offsetof(MachineStatusInfo, unit) },
506 { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) },
507 { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) },
508 { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp) },
509 { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) },
510 { "NetworkInterfaces", "ai", map_netif, 0 },
9f6eb1cd
KS
511 {}
512 };
a6c61602
LP
513
514 MachineStatusInfo info = {};
a1da8583
TG
515 int r;
516
56159e0d
LP
517 assert(verb);
518 assert(bus);
1ee306e1
LP
519 assert(path);
520 assert(new_line);
521
9f6eb1cd
KS
522 r = bus_map_all_properties(bus,
523 "org.freedesktop.machine1",
524 path,
525 map,
526 &info);
f647962d
MS
527 if (r < 0)
528 return log_error_errno(r, "Could not get properties: %m");
1ee306e1 529
1ee306e1
LP
530 if (*new_line)
531 printf("\n");
1ee306e1
LP
532 *new_line = true;
533
9f6eb1cd 534 print_machine_status_info(bus, &info);
1ee306e1 535
9f6eb1cd
KS
536 free(info.name);
537 free(info.class);
538 free(info.service);
89f7c846 539 free(info.unit);
9f6eb1cd 540 free(info.root_directory);
f48e75cb 541 free(info.netif);
1ee306e1 542
9f6eb1cd
KS
543 return r;
544}
1ee306e1 545
9f6eb1cd
KS
546static int show_properties(sd_bus *bus, const char *path, bool *new_line) {
547 int r;
1ee306e1 548
56159e0d
LP
549 assert(bus);
550 assert(path);
551 assert(new_line);
552
9f6eb1cd
KS
553 if (*new_line)
554 printf("\n");
1ee306e1 555
9f6eb1cd 556 *new_line = true;
a1da8583 557
27e72d6b 558 r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all);
a1da8583 559 if (r < 0)
da927ba9 560 log_error_errno(r, "Could not get properties: %m");
1ee306e1 561
a7893c6b 562 return r;
1ee306e1
LP
563}
564
56159e0d
LP
565static int show(int argc, char *argv[], void *userdata) {
566
a1da8583 567 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
56159e0d 568 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
9f6eb1cd 569 bool properties, new_line = false;
56159e0d
LP
570 sd_bus *bus = userdata;
571 int r = 0, i;
1ee306e1
LP
572
573 assert(bus);
1ee306e1 574
56159e0d 575 properties = !strstr(argv[0], "status");
1ee306e1
LP
576
577 pager_open_if_enabled();
578
56159e0d 579 if (properties && argc <= 1) {
a1da8583 580
9f6eb1cd 581 /* If no argument is specified, inspect the manager
1ee306e1 582 * itself */
9f6eb1cd 583 r = show_properties(bus, "/org/freedesktop/machine1", &new_line);
8c841f21 584 if (r < 0)
9f6eb1cd 585 return r;
1ee306e1
LP
586 }
587
56159e0d 588 for (i = 1; i < argc; i++) {
1ee306e1
LP
589 const char *path = NULL;
590
a1da8583
TG
591 r = sd_bus_call_method(
592 bus,
593 "org.freedesktop.machine1",
594 "/org/freedesktop/machine1",
595 "org.freedesktop.machine1.Manager",
596 "GetMachine",
597 &error,
598 &reply,
56159e0d 599 "s", argv[i]);
a1da8583
TG
600 if (r < 0) {
601 log_error("Could not get path to machine: %s", bus_error_message(&error, -r));
602 return r;
603 }
604
605 r = sd_bus_message_read(reply, "o", &path);
5b30bef8
LP
606 if (r < 0)
607 return bus_log_parse_error(r);
1ee306e1 608
9f6eb1cd
KS
609 if (properties)
610 r = show_properties(bus, path, &new_line);
611 else
56159e0d 612 r = show_info(argv[0], bus, path, &new_line);
1ee306e1
LP
613 }
614
9f6eb1cd 615 return r;
1ee306e1
LP
616}
617
56159e0d 618static int kill_machine(int argc, char *argv[], void *userdata) {
a1da8583 619 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
56159e0d
LP
620 sd_bus *bus = userdata;
621 int i;
1ee306e1 622
56159e0d 623 assert(bus);
1ee306e1
LP
624
625 if (!arg_kill_who)
626 arg_kill_who = "all";
627
56159e0d 628 for (i = 1; i < argc; i++) {
1ee306e1
LP
629 int r;
630
a1da8583 631 r = sd_bus_call_method(
56159e0d
LP
632 bus,
633 "org.freedesktop.machine1",
634 "/org/freedesktop/machine1",
635 "org.freedesktop.machine1.Manager",
636 "KillMachine",
637 &error,
638 NULL,
639 "ssi", argv[i], arg_kill_who, arg_signal);
a1da8583
TG
640 if (r < 0) {
641 log_error("Could not kill machine: %s", bus_error_message(&error, -r));
1ee306e1 642 return r;
a1da8583 643 }
1ee306e1
LP
644 }
645
646 return 0;
647}
648
56159e0d 649static int reboot_machine(int argc, char *argv[], void *userdata) {
1dba654b
LP
650 arg_kill_who = "leader";
651 arg_signal = SIGINT; /* sysvinit + systemd */
1ee306e1 652
56159e0d 653 return kill_machine(argc, argv, userdata);
1dba654b 654}
1ee306e1 655
56159e0d 656static int poweroff_machine(int argc, char *argv[], void *userdata) {
1dba654b
LP
657 arg_kill_who = "leader";
658 arg_signal = SIGRTMIN+4; /* only systemd */
1ee306e1 659
56159e0d 660 return kill_machine(argc, argv, userdata);
1ee306e1
LP
661}
662
56159e0d 663static int terminate_machine(int argc, char *argv[], void *userdata) {
923d8fd3 664 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
56159e0d
LP
665 sd_bus *bus = userdata;
666 int i;
923d8fd3 667
56159e0d 668 assert(bus);
923d8fd3 669
56159e0d 670 for (i = 1; i < argc; i++) {
1dba654b 671 int r;
923d8fd3
LP
672
673 r = sd_bus_call_method(
674 bus,
675 "org.freedesktop.machine1",
676 "/org/freedesktop/machine1",
677 "org.freedesktop.machine1.Manager",
1dba654b 678 "TerminateMachine",
923d8fd3 679 &error,
1dba654b 680 NULL,
56159e0d 681 "s", argv[i]);
923d8fd3 682 if (r < 0) {
1dba654b 683 log_error("Could not terminate machine: %s", bus_error_message(&error, -r));
923d8fd3
LP
684 return r;
685 }
923d8fd3
LP
686 }
687
688 return 0;
689}
690
785890ac
LP
691static int machine_get_leader(sd_bus *bus, const char *name, pid_t *ret) {
692 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
693 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL, *reply2 = NULL;
694 const char *object;
695 uint32_t leader;
696 int r;
697
698 assert(bus);
699 assert(name);
700 assert(ret);
701
702 r = sd_bus_call_method(
703 bus,
704 "org.freedesktop.machine1",
705 "/org/freedesktop/machine1",
706 "org.freedesktop.machine1.Manager",
707 "GetMachine",
708 &error,
709 &reply,
710 "s", name);
711 if (r < 0) {
712 log_error("Could not get path to machine: %s", bus_error_message(&error, -r));
713 return r;
714 }
715
716 r = sd_bus_message_read(reply, "o", &object);
717 if (r < 0)
718 return bus_log_parse_error(r);
719
720 r = sd_bus_get_property(
721 bus,
722 "org.freedesktop.machine1",
723 object,
724 "org.freedesktop.machine1.Machine",
725 "Leader",
726 &error,
727 &reply2,
728 "u");
729 if (r < 0)
730 return log_error_errno(r, "Failed to retrieve PID of leader: %m");
731
732 r = sd_bus_message_read(reply2, "u", &leader);
733 if (r < 0)
734 return bus_log_parse_error(r);
735
736 *ret = leader;
737 return 0;
738}
739
56159e0d 740static int copy_files(int argc, char *argv[], void *userdata) {
f2cbe59e
LP
741 char *dest, *host_path, *container_path, *host_dirname, *host_basename, *container_dirname, *container_basename, *t;
742 _cleanup_close_ int hostfd = -1;
56159e0d 743 sd_bus *bus = userdata;
f2cbe59e
LP
744 pid_t child, leader;
745 bool copy_from;
746 siginfo_t si;
747 int r;
748
56159e0d 749 assert(bus);
f2cbe59e 750
56159e0d
LP
751 copy_from = streq(argv[0], "copy-from");
752 dest = argv[3] ?: argv[2];
753 host_path = strdupa(copy_from ? dest : argv[2]);
754 container_path = strdupa(copy_from ? argv[2] : dest);
f2cbe59e
LP
755
756 if (!path_is_absolute(container_path)) {
757 log_error("Container path not absolute.");
758 return -EINVAL;
759 }
760
761 t = strdup(host_path);
762 host_basename = basename(t);
763 host_dirname = dirname(host_path);
764
765 t = strdup(container_path);
766 container_basename = basename(t);
767 container_dirname = dirname(container_path);
768
56159e0d 769 r = machine_get_leader(bus, argv[1], &leader);
f2cbe59e
LP
770 if (r < 0)
771 return r;
772
773 hostfd = open(host_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY);
774 if (r < 0)
775 return log_error_errno(errno, "Failed to open source directory: %m");
776
777 child = fork();
778 if (child < 0)
779 return log_error_errno(errno, "Failed to fork(): %m");
780
781 if (child == 0) {
782 int containerfd;
783 const char *q;
784 int mntfd;
785
786 q = procfs_file_alloca(leader, "ns/mnt");
787 mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC);
788 if (mntfd < 0) {
789 log_error_errno(errno, "Failed to open mount namespace of leader: %m");
790 _exit(EXIT_FAILURE);
791 }
792
793 if (setns(mntfd, CLONE_NEWNS) < 0) {
794 log_error_errno(errno, "Failed to join namespace of leader: %m");
795 _exit(EXIT_FAILURE);
796 }
797
798 containerfd = open(container_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY);
799 if (containerfd < 0) {
800 log_error_errno(errno, "Failed top open destination directory: %m");
801 _exit(EXIT_FAILURE);
802 }
803
804 if (copy_from)
805 r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, true);
806 else
807 r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, true);
808 if (r < 0) {
809 log_error_errno(errno, "Failed to copy tree: %m");
810 _exit(EXIT_FAILURE);
811 }
812
813 _exit(EXIT_SUCCESS);
814 }
815
816 r = wait_for_terminate(child, &si);
817 if (r < 0)
818 return log_error_errno(r, "Failed to wait for client: %m");
819 if (si.si_code != CLD_EXITED) {
820 log_error("Client died abnormally.");
821 return -EIO;
822 }
823 if (si.si_status != EXIT_SUCCESS)
824 return -EIO;
825
826 return 0;
827}
828
56159e0d 829static int bind_mount(int argc, char *argv[], void *userdata) {
785890ac 830 char mount_slave[] = "/tmp/propagate.XXXXXX", *mount_tmp, *mount_outside, *p;
56159e0d 831 sd_bus *bus = userdata;
785890ac
LP
832 pid_t child, leader;
833 const char *dest;
834 siginfo_t si;
835 bool mount_slave_created = false, mount_slave_mounted = false,
836 mount_tmp_created = false, mount_tmp_mounted = false,
837 mount_outside_created = false, mount_outside_mounted = false;
838 int r;
839
56159e0d
LP
840 assert(bus);
841
785890ac
LP
842 /* One day, when bind mounting /proc/self/fd/n works across
843 * namespace boundaries we should rework this logic to make
844 * use of it... */
845
56159e0d 846 dest = argv[3] ?: argv[2];
785890ac
LP
847 if (!path_is_absolute(dest)) {
848 log_error("Destination path not absolute.");
849 return -EINVAL;
850 }
851
56159e0d 852 p = strappenda("/run/systemd/nspawn/propagate/", argv[1], "/");
785890ac
LP
853 if (access(p, F_OK) < 0) {
854 log_error("Container does not allow propagation of mount points.");
855 return -ENOTSUP;
856 }
857
56159e0d 858 r = machine_get_leader(bus, argv[1], &leader);
785890ac
LP
859 if (r < 0)
860 return r;
861
862 /* Our goal is to install a new bind mount into the container,
863 possibly read-only. This is irritatingly complex
864 unfortunately, currently.
865
866 First, we start by creating a private playground in /tmp,
867 that we can mount MS_SLAVE. (Which is necessary, since
868 MS_MOUNT cannot be applied to mounts with MS_SHARED parent
869 mounts.) */
870
871 if (!mkdtemp(mount_slave))
872 return log_error_errno(errno, "Failed to create playground: %m");
873
874 mount_slave_created = true;
875
876 if (mount(mount_slave, mount_slave, NULL, MS_BIND, NULL) < 0) {
877 r = log_error_errno(errno, "Failed to make bind mount: %m");
878 goto finish;
879 }
880
881 mount_slave_mounted = true;
882
883 if (mount(NULL, mount_slave, NULL, MS_SLAVE, NULL) < 0) {
884 r = log_error_errno(errno, "Failed to remount slave: %m");
885 goto finish;
886 }
887
888 /* Second, we mount the source directory to a directory inside
889 of our MS_SLAVE playground. */
890 mount_tmp = strappenda(mount_slave, "/mount");
891 if (mkdir(mount_tmp, 0700) < 0) {
892 r = log_error_errno(errno, "Failed to create temporary mount: %m");
893 goto finish;
894 }
895
896 mount_tmp_created = true;
897
56159e0d 898 if (mount(argv[2], mount_tmp, NULL, MS_BIND, NULL) < 0) {
785890ac
LP
899 r = log_error_errno(errno, "Failed to overmount: %m");
900 goto finish;
901 }
902
903 mount_tmp_mounted = true;
904
905 /* Third, we remount the new bind mount read-only if requested. */
906 if (arg_read_only)
907 if (mount(NULL, mount_tmp, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) {
908 r = log_error_errno(errno, "Failed to mark read-only: %m");
909 goto finish;
910 }
911
912 /* Fourth, we move the new bind mount into the propagation
913 * directory. This way it will appear there read-only
914 * right-away. */
915
56159e0d 916 mount_outside = strappenda("/run/systemd/nspawn/propagate/", argv[1], "/XXXXXX");
785890ac
LP
917 if (!mkdtemp(mount_outside)) {
918 r = log_error_errno(errno, "Cannot create propagation directory: %m");
919 goto finish;
920 }
921
922 mount_outside_created = true;
923
924 if (mount(mount_tmp, mount_outside, NULL, MS_MOVE, NULL) < 0) {
925 r = log_error_errno(errno, "Failed to move: %m");
926 goto finish;
927 }
928
929 mount_outside_mounted = true;
930 mount_tmp_mounted = false;
931
932 (void) rmdir(mount_tmp);
933 mount_tmp_created = false;
934
935 (void) umount(mount_slave);
936 mount_slave_mounted = false;
937
938 (void) rmdir(mount_slave);
939 mount_slave_created = false;
940
941 child = fork();
942 if (child < 0) {
943 r = log_error_errno(errno, "Failed to fork(): %m");
944 goto finish;
945 }
946
947 if (child == 0) {
948 const char *mount_inside;
949 int mntfd;
950 const char *q;
951
952 q = procfs_file_alloca(leader, "ns/mnt");
953 mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC);
954 if (mntfd < 0) {
955 log_error_errno(errno, "Failed to open mount namespace of leader: %m");
956 _exit(EXIT_FAILURE);
957 }
958
959 if (setns(mntfd, CLONE_NEWNS) < 0) {
960 log_error_errno(errno, "Failed to join namespace of leader: %m");
961 _exit(EXIT_FAILURE);
962 }
963
964 if (arg_mkdir)
965 mkdir_p(dest, 0755);
966
967 /* Fifth, move the mount to the right place inside */
968 mount_inside = strappenda("/run/systemd/nspawn/incoming/", basename(mount_outside));
969 if (mount(mount_inside, dest, NULL, MS_MOVE, NULL) < 0) {
970 log_error_errno(errno, "Failed to mount: %m");
971 _exit(EXIT_FAILURE);
972 }
973
974 _exit(EXIT_SUCCESS);
975 }
976
977 r = wait_for_terminate(child, &si);
978 if (r < 0) {
979 log_error_errno(r, "Failed to wait for client: %m");
980 goto finish;
981 }
982 if (si.si_code != CLD_EXITED) {
983 log_error("Client died abnormally.");
984 r = -EIO;
985 goto finish;
986 }
987 if (si.si_status != EXIT_SUCCESS) {
988 r = -EIO;
989 goto finish;
990 }
991
992 r = 0;
993
994finish:
995 if (mount_outside_mounted)
996 umount(mount_outside);
997 if (mount_outside_created)
998 rmdir(mount_outside);
999
1000 if (mount_tmp_mounted)
1001 umount(mount_tmp);
1002 if (mount_tmp_created)
1003 umount(mount_tmp);
1004
1005 if (mount_slave_mounted)
1006 umount(mount_slave);
1007 if (mount_slave_created)
1008 umount(mount_slave);
1009
1010 return r;
1011}
1012
56159e0d 1013static int login_machine(int argc, char *argv[], void *userdata) {
04d39279 1014 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
d04c1fb8 1015 _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
023fb90b
LP
1016 _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
1017 _cleanup_event_unref_ sd_event *event = NULL;
40205d70 1018 int master = -1, r, ret = 0;
56159e0d 1019 sd_bus *bus = userdata;
bf441e3d 1020 const char *pty;
04d39279 1021 sigset_t mask;
c7b7d449 1022 char last_char = 0;
04d39279
LP
1023
1024 assert(bus);
04d39279 1025
bf441e3d 1026 if (arg_transport != BUS_TRANSPORT_LOCAL &&
de33fc62 1027 arg_transport != BUS_TRANSPORT_MACHINE) {
923d8fd3 1028 log_error("Login only supported on local machines.");
04d39279
LP
1029 return -ENOTSUP;
1030 }
1031
023fb90b 1032 r = sd_event_default(&event);
f647962d
MS
1033 if (r < 0)
1034 return log_error_errno(r, "Failed to get event loop: %m");
023fb90b
LP
1035
1036 r = sd_bus_attach_event(bus, event, 0);
f647962d
MS
1037 if (r < 0)
1038 return log_error_errno(r, "Failed to attach bus to event loop: %m");
023fb90b 1039
d04c1fb8
LP
1040 r = sd_bus_message_new_method_call(bus,
1041 &m,
1042 "org.freedesktop.machine1",
1043 "/org/freedesktop/machine1",
1044 "org.freedesktop.machine1.Manager",
1045 "OpenMachineLogin");
1046 if (r < 0)
1047 return bus_log_create_error(r);
1048
1049 r = sd_bus_message_set_allow_interactive_authorization(m, true);
1050 if (r < 0)
1051 return bus_log_create_error(r);
1052
1053 r = sd_bus_message_append(m, "s", argv[1]);
1054 if (r < 0)
1055 return bus_log_create_error(r);
1056
1057 r = sd_bus_call(bus, m, 0, &error, &reply);
40205d70
LP
1058 if (r < 0) {
1059 log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r));
785890ac 1060 return r;
40205d70 1061 }
04d39279 1062
40205d70
LP
1063 r = sd_bus_message_read(reply, "hs", &master, &pty);
1064 if (r < 0)
ee451d76 1065 return bus_log_parse_error(r);
04d39279 1066
04d39279
LP
1067 assert_se(sigemptyset(&mask) == 0);
1068 sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1);
1069 assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
1070
56159e0d 1071 log_info("Connected to container %s. Press ^] three times within 1s to exit session.", argv[1]);
04d39279 1072
023fb90b
LP
1073 sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
1074 sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
1075
9b15b784 1076 r = pty_forward_new(event, master, true, &forward);
f647962d
MS
1077 if (r < 0)
1078 return log_error_errno(r, "Failed to create PTY forwarder: %m");
023fb90b
LP
1079
1080 r = sd_event_loop(event);
f647962d
MS
1081 if (r < 0)
1082 return log_error_errno(r, "Failed to run event loop: %m");
04d39279 1083
c7b7d449
LP
1084 pty_forward_last_char(forward, &last_char);
1085
023fb90b
LP
1086 forward = pty_forward_free(forward);
1087
c7b7d449
LP
1088 if (last_char != '\n')
1089 fputc('\n', stdout);
04d39279 1090
56159e0d 1091 log_info("Connection to container %s terminated.", argv[1]);
04d39279 1092
023fb90b
LP
1093 sd_event_get_exit_code(event, &ret);
1094 return ret;
04d39279
LP
1095}
1096
56159e0d
LP
1097static int help(int argc, char *argv[], void *userdata) {
1098
1ee306e1 1099 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
f2cbe59e
LP
1100 "Send control commands to or query the virtual machine and container\n"
1101 "registration manager.\n\n"
1102 " -h --help Show this help\n"
1103 " --version Show package version\n"
1104 " --no-pager Do not pipe output into a pager\n"
1105 " --no-legend Do not show the headers and footers\n"
1106 " -H --host=[USER@]HOST Operate on remote host\n"
1107 " -M --machine=CONTAINER Operate on local container\n"
1108 " -p --property=NAME Show only properties by this name\n"
1109 " -a --all Show all properties, including empty ones\n"
1110 " -l --full Do not ellipsize output\n"
1111 " --kill-who=WHO Who to send signal to\n"
1112 " -s --signal=SIGNAL Which signal to send\n"
1113 " --read-only Create read-only bind mount\n"
1114 " --mkdir Create directory before bind mounting, if missing\n\n"
cd61c3bf 1115 "Machine Commands:\n"
f2cbe59e
LP
1116 " list List running VMs and containers\n"
1117 " status NAME... Show VM/container status\n"
1118 " show NAME... Show properties of one or more VMs/containers\n"
1119 " login NAME Get a login prompt on a container\n"
1120 " poweroff NAME... Power off one or more containers\n"
1121 " reboot NAME... Reboot one or more containers\n"
1122 " kill NAME... Send signal to processes of a VM/container\n"
1123 " terminate NAME... Terminate one or more VMs/containers\n"
1124 " bind NAME PATH [PATH] Bind mount a path from the host into a container\n"
1125 " copy-to NAME PATH [PATH] Copy files from the host to a container\n"
cd61c3bf
LP
1126 " copy-from NAME PATH [PATH] Copy files from a container to the host\n\n"
1127 "Image commands:\n"
1128 " list-images Show available images\n",
1ee306e1 1129 program_invocation_short_name);
56159e0d
LP
1130
1131 return 0;
1ee306e1
LP
1132}
1133
1134static int parse_argv(int argc, char *argv[]) {
1135
1136 enum {
1137 ARG_VERSION = 0x100,
1138 ARG_NO_PAGER,
e56056e9 1139 ARG_NO_LEGEND,
1ee306e1 1140 ARG_KILL_WHO,
785890ac
LP
1141 ARG_READ_ONLY,
1142 ARG_MKDIR,
1ee306e1
LP
1143 };
1144
1145 static const struct option options[] = {
1146 { "help", no_argument, NULL, 'h' },
1147 { "version", no_argument, NULL, ARG_VERSION },
1148 { "property", required_argument, NULL, 'p' },
1149 { "all", no_argument, NULL, 'a' },
1150 { "full", no_argument, NULL, 'l' },
1151 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
e56056e9 1152 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
1ee306e1
LP
1153 { "kill-who", required_argument, NULL, ARG_KILL_WHO },
1154 { "signal", required_argument, NULL, 's' },
1155 { "host", required_argument, NULL, 'H' },
a7893c6b 1156 { "machine", required_argument, NULL, 'M' },
785890ac
LP
1157 { "read-only", no_argument, NULL, ARG_READ_ONLY },
1158 { "mkdir", no_argument, NULL, ARG_MKDIR },
eb9da376 1159 {}
1ee306e1
LP
1160 };
1161
a7893c6b 1162 int c, r;
1ee306e1
LP
1163
1164 assert(argc >= 0);
1165 assert(argv);
1166
601185b4 1167 while ((c = getopt_long(argc, argv, "hp:als:H:M:", options, NULL)) >= 0)
1ee306e1
LP
1168
1169 switch (c) {
1170
1171 case 'h':
56159e0d 1172 return help(0, NULL, NULL);
1ee306e1
LP
1173
1174 case ARG_VERSION:
1175 puts(PACKAGE_STRING);
1176 puts(SYSTEMD_FEATURES);
1177 return 0;
1178
a7893c6b
LP
1179 case 'p':
1180 r = strv_extend(&arg_property, optarg);
1181 if (r < 0)
1182 return log_oom();
1ee306e1
LP
1183
1184 /* If the user asked for a particular
1185 * property, show it to him, even if it is
1186 * empty. */
1187 arg_all = true;
1188 break;
1ee306e1
LP
1189
1190 case 'a':
1191 arg_all = true;
1192 break;
1193
1194 case 'l':
1195 arg_full = true;
1196 break;
1197
1198 case ARG_NO_PAGER:
1199 arg_no_pager = true;
1200 break;
1201
e56056e9
TA
1202 case ARG_NO_LEGEND:
1203 arg_legend = false;
1204 break;
1205
1ee306e1
LP
1206 case ARG_KILL_WHO:
1207 arg_kill_who = optarg;
1208 break;
1209
1210 case 's':
1211 arg_signal = signal_from_string_try_harder(optarg);
1212 if (arg_signal < 0) {
1213 log_error("Failed to parse signal string %s.", optarg);
1214 return -EINVAL;
1215 }
1216 break;
1217
1ee306e1 1218 case 'H':
d21ed1ea 1219 arg_transport = BUS_TRANSPORT_REMOTE;
a7893c6b
LP
1220 arg_host = optarg;
1221 break;
1222
1223 case 'M':
de33fc62 1224 arg_transport = BUS_TRANSPORT_MACHINE;
a7893c6b 1225 arg_host = optarg;
1ee306e1
LP
1226 break;
1227
785890ac
LP
1228 case ARG_READ_ONLY:
1229 arg_read_only = true;
1230 break;
1231
1232 case ARG_MKDIR:
1233 arg_mkdir = true;
1234 break;
1235
1ee306e1
LP
1236 case '?':
1237 return -EINVAL;
1238
1239 default:
eb9da376 1240 assert_not_reached("Unhandled option");
1ee306e1 1241 }
1ee306e1
LP
1242
1243 return 1;
1244}
1245
56159e0d
LP
1246static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
1247
1248 static const Verb verbs[] = {
1249 { "help", VERB_ANY, VERB_ANY, 0, help },
1250 { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines },
1251 { "list-images", VERB_ANY, 1, 0, list_images },
1252 { "status", 2, VERB_ANY, 0, show },
1253 { "show", VERB_ANY, VERB_ANY, 0, show },
1254 { "terminate", 2, VERB_ANY, 0, terminate_machine },
1255 { "reboot", 2, VERB_ANY, 0, reboot_machine },
1256 { "poweroff", 2, VERB_ANY, 0, poweroff_machine },
1257 { "kill", 2, VERB_ANY, 0, kill_machine },
1258 { "login", 2, 2, 0, login_machine },
1259 { "bind", 3, 4, 0, bind_mount },
1260 { "copy-to", 3, 4, 0, copy_files },
1261 { "copy-from", 3, 4, 0, copy_files },
1262 {}
1ee306e1
LP
1263 };
1264
56159e0d 1265 return dispatch_verb(argc, argv, verbs, bus);
1ee306e1
LP
1266}
1267
1268int main(int argc, char*argv[]) {
24996861 1269 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
84f6181c 1270 int r;
1ee306e1
LP
1271
1272 setlocale(LC_ALL, "");
1273 log_parse_environment();
1274 log_open();
1275
1276 r = parse_argv(argc, argv);
84f6181c 1277 if (r <= 0)
1ee306e1 1278 goto finish;
1ee306e1 1279
d21ed1ea 1280 r = bus_open_transport(arg_transport, arg_host, false, &bus);
a1da8583 1281 if (r < 0) {
da927ba9 1282 log_error_errno(r, "Failed to create bus connection: %m");
a1da8583
TG
1283 goto finish;
1284 }
1ee306e1 1285
56159e0d 1286 r = machinectl_main(argc, argv, bus);
1ee306e1
LP
1287
1288finish:
1ee306e1
LP
1289 pager_close();
1290
84f6181c
LP
1291 strv_free(arg_property);
1292
1293 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1ee306e1 1294}