]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/machine/machinectl.c
loginctl: show the 10 most recent log user/session log lines in "loginctl user-status...
[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;
d8f52ed2 66static bool arg_quiet = false;
1ee306e1
LP
67
68static void pager_open_if_enabled(void) {
69
70 /* Cache result before we open the pager */
71 if (arg_no_pager)
72 return;
73
74 pager_open(false);
75}
76
0b63e278
LP
77typedef struct MachineInfo {
78 const char *name;
79 const char *class;
80 const char *service;
81} MachineInfo;
82
83static int compare_machine_info(const void *a, const void *b) {
84 const MachineInfo *x = a, *y = b;
85
86 return strcmp(x->name, y->name);
87}
88
56159e0d
LP
89static int list_machines(int argc, char *argv[], void *userdata) {
90
0b63e278 91 size_t max_name = strlen("MACHINE"), max_class = strlen("CLASS"), max_service = strlen("SERVICE");
a1da8583 92 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
0b63e278
LP
93 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
94 _cleanup_free_ MachineInfo *machines = NULL;
a1da8583 95 const char *name, *class, *service, *object;
0b63e278 96 size_t n_machines = 0, n_allocated = 0, j;
56159e0d 97 sd_bus *bus = userdata;
1ee306e1
LP
98 int r;
99
56159e0d
LP
100 assert(bus);
101
1ee306e1
LP
102 pager_open_if_enabled();
103
a1da8583
TG
104 r = sd_bus_call_method(
105 bus,
106 "org.freedesktop.machine1",
107 "/org/freedesktop/machine1",
108 "org.freedesktop.machine1.Manager",
109 "ListMachines",
110 &error,
111 &reply,
112 "");
113 if (r < 0) {
114 log_error("Could not get machines: %s", bus_error_message(&error, -r));
1ee306e1 115 return r;
1ee306e1
LP
116 }
117
a1da8583
TG
118 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssso)");
119 if (r < 0)
5b30bef8 120 return bus_log_parse_error(r);
1ee306e1 121
a1da8583 122 while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) {
0b63e278
LP
123 size_t l;
124
125 if (!GREEDY_REALLOC(machines, n_allocated, n_machines + 1))
126 return log_oom();
1ee306e1 127
0b63e278
LP
128 machines[n_machines].name = name;
129 machines[n_machines].class = class;
130 machines[n_machines].service = service;
131
132 l = strlen(name);
133 if (l > max_name)
134 max_name = l;
135
136 l = strlen(class);
137 if (l > max_class)
138 max_class = l;
139
140 l = strlen(service);
141 if (l > max_service)
142 max_service = l;
143
144 n_machines ++;
1ee306e1 145 }
a1da8583 146 if (r < 0)
5b30bef8 147 return bus_log_parse_error(r);
a1da8583
TG
148
149 r = sd_bus_message_exit_container(reply);
150 if (r < 0)
5b30bef8 151 return bus_log_parse_error(r);
1ee306e1 152
0b63e278
LP
153 qsort_safe(machines, n_machines, sizeof(MachineInfo), compare_machine_info);
154
155 if (arg_legend)
156 printf("%-*s %-*s %-*s\n",
157 (int) max_name, "MACHINE",
158 (int) max_class, "CLASS",
159 (int) max_service, "SERVICE");
160
161 for (j = 0; j < n_machines; j++)
162 printf("%-*s %-*s %-*s\n",
163 (int) max_name, machines[j].name,
164 (int) max_class, machines[j].class,
165 (int) max_service, machines[j].service);
166
e56056e9 167 if (arg_legend)
0b63e278 168 printf("\n%zu machines listed.\n", n_machines);
1ee306e1
LP
169
170 return 0;
171}
172
cd61c3bf
LP
173typedef struct ImageInfo {
174 const char *name;
175 const char *type;
176 bool read_only;
10f9c755
LP
177 usec_t crtime;
178 usec_t mtime;
b6b18498 179 uint64_t size;
cd61c3bf
LP
180} ImageInfo;
181
182static int compare_image_info(const void *a, const void *b) {
183 const ImageInfo *x = a, *y = b;
184
185 return strcmp(x->name, y->name);
186}
187
56159e0d 188static int list_images(int argc, char *argv[], void *userdata) {
cd61c3bf
LP
189
190 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
b6b18498 191 size_t max_name = strlen("NAME"), max_type = strlen("TYPE"), max_size = strlen("SIZE"), max_crtime = strlen("CREATED"), max_mtime = strlen("MODIFIED");
cd61c3bf
LP
192 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
193 _cleanup_free_ ImageInfo *images = NULL;
194 size_t n_images = 0, n_allocated = 0, j;
195 const char *name, *type, *object;
56159e0d 196 sd_bus *bus = userdata;
b6b18498 197 uint64_t crtime, mtime, size;
10f9c755 198 int read_only, r;
cd61c3bf 199
56159e0d
LP
200 assert(bus);
201
cd61c3bf
LP
202 pager_open_if_enabled();
203
204 r = sd_bus_call_method(
205 bus,
206 "org.freedesktop.machine1",
207 "/org/freedesktop/machine1",
208 "org.freedesktop.machine1.Manager",
209 "ListImages",
210 &error,
211 &reply,
212 "");
213 if (r < 0) {
214 log_error("Could not get images: %s", bus_error_message(&error, -r));
215 return r;
216 }
217
b6b18498 218 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)");
cd61c3bf
LP
219 if (r < 0)
220 return bus_log_parse_error(r);
221
b6b18498
LP
222 while ((r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &read_only, &crtime, &mtime, &size, &object)) > 0) {
223 char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_BYTES_MAX)];
10f9c755 224 size_t l;
cd61c3bf
LP
225
226 if (name[0] == '.' && !arg_all)
227 continue;
228
229 if (!GREEDY_REALLOC(images, n_allocated, n_images + 1))
230 return log_oom();
231
232 images[n_images].name = name;
233 images[n_images].type = type;
234 images[n_images].read_only = read_only;
10f9c755
LP
235 images[n_images].crtime = crtime;
236 images[n_images].mtime = mtime;
b6b18498 237 images[n_images].size = size;
10f9c755
LP
238
239 l = strlen(name);
240 if (l > max_name)
241 max_name = l;
cd61c3bf 242
10f9c755
LP
243 l = strlen(type);
244 if (l > max_type)
245 max_type = l;
cd61c3bf 246
10f9c755 247 if (crtime != 0) {
b6b18498 248 l = strlen(strna(format_timestamp(buf, sizeof(buf), crtime)));
10f9c755
LP
249 if (l > max_crtime)
250 max_crtime = l;
251 }
252
253 if (mtime != 0) {
b6b18498 254 l = strlen(strna(format_timestamp(buf, sizeof(buf), mtime)));
10f9c755
LP
255 if (l > max_mtime)
256 max_mtime = l;
257 }
cd61c3bf 258
b6b18498
LP
259 if (size != (uint64_t) -1) {
260 l = strlen(strna(format_bytes(buf, sizeof(buf), size)));
261 if (l > max_size)
262 max_size = l;
263 }
264
cd61c3bf
LP
265 n_images++;
266 }
267 if (r < 0)
268 return bus_log_parse_error(r);
269
270 r = sd_bus_message_exit_container(reply);
271 if (r < 0)
272 return bus_log_parse_error(r);
273
274 qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info);
275
276 if (arg_legend)
b6b18498 277 printf("%-*s %-*s %-3s %-*s %-*s %-*s\n",
10f9c755
LP
278 (int) max_name, "NAME",
279 (int) max_type, "TYPE",
280 "RO",
b6b18498 281 (int) max_size, "SIZE",
10f9c755
LP
282 (int) max_crtime, "CREATED",
283 (int) max_mtime, "MODIFIED");
cd61c3bf
LP
284
285 for (j = 0; j < n_images; j++) {
b6b18498 286 char crtime_buf[FORMAT_TIMESTAMP_MAX], mtime_buf[FORMAT_TIMESTAMP_MAX], size_buf[FORMAT_BYTES_MAX];
10f9c755 287
b6b18498 288 printf("%-*s %-*s %s%-3s%s %-*s %-*s %-*s\n",
cd61c3bf
LP
289 (int) max_name, images[j].name,
290 (int) max_type, images[j].type,
8937e7b6 291 images[j].read_only ? ansi_highlight_red() : "", yes_no(images[j].read_only), images[j].read_only ? ansi_highlight_off() : "",
b6b18498
LP
292 (int) max_size, strna(format_bytes(size_buf, sizeof(size_buf), images[j].size)),
293 (int) max_crtime, strna(format_timestamp(crtime_buf, sizeof(crtime_buf), images[j].crtime)),
294 (int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime)));
cd61c3bf
LP
295 }
296
cd61c3bf
LP
297 if (arg_legend)
298 printf("\n%zu images listed.\n", n_images);
299
300 return 0;
301}
302
89f7c846 303static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) {
a1da8583
TG
304 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
305 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
aa1936ea 306 _cleanup_free_ char *path = NULL;
aa1936ea 307 const char *cgroup;
aa1936ea
LP
308 int r, output_flags;
309 unsigned c;
310
311 assert(bus);
312 assert(unit);
313
d21ed1ea 314 if (arg_transport == BUS_TRANSPORT_REMOTE)
aa1936ea
LP
315 return 0;
316
317 path = unit_dbus_path_from_name(unit);
318 if (!path)
319 return log_oom();
320
a7893c6b 321 r = sd_bus_get_property(
aa1936ea
LP
322 bus,
323 "org.freedesktop.systemd1",
324 path,
89f7c846 325 endswith(unit, ".scope") ? "org.freedesktop.systemd1.Scope" : "org.freedesktop.systemd1.Service",
a7893c6b 326 "ControlGroup",
aa1936ea 327 &error,
a1da8583 328 &reply,
a7893c6b 329 "s");
aa1936ea 330 if (r < 0) {
a1da8583 331 log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r));
aa1936ea
LP
332 return r;
333 }
334
a7893c6b 335 r = sd_bus_message_read(reply, "s", &cgroup);
5b30bef8
LP
336 if (r < 0)
337 return bus_log_parse_error(r);
aa1936ea 338
9d127096
LP
339 if (isempty(cgroup))
340 return 0;
341
342 if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0)
343 return 0;
344
aa1936ea
LP
345 output_flags =
346 arg_all * OUTPUT_SHOW_ALL |
347 arg_full * OUTPUT_FULL_WIDTH;
348
349 c = columns();
350 if (c > 18)
351 c -= 18;
352 else
353 c = 0;
354
9d127096 355 show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, output_flags);
aa1936ea
LP
356 return 0;
357}
358
f48e75cb 359static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) {
878cd7e9
LP
360 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
361 int r;
362
363 assert(bus);
364 assert(name);
365 assert(prefix);
366 assert(prefix2);
367
368 r = sd_bus_call_method(bus,
369 "org.freedesktop.machine1",
370 "/org/freedesktop/machine1",
371 "org.freedesktop.machine1.Manager",
372 "GetMachineAddresses",
373 NULL,
374 &reply,
375 "s", name);
376 if (r < 0)
377 return r;
378
0dd25fb9 379 r = sd_bus_message_enter_container(reply, 'a', "(iay)");
878cd7e9
LP
380 if (r < 0)
381 return bus_log_parse_error(r);
382
0dd25fb9
LP
383 while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) {
384 int family;
878cd7e9
LP
385 const void *a;
386 size_t sz;
387 char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)];
388
0dd25fb9 389 r = sd_bus_message_read(reply, "i", &family);
878cd7e9
LP
390 if (r < 0)
391 return bus_log_parse_error(r);
392
393 r = sd_bus_message_read_array(reply, 'y', &a, &sz);
394 if (r < 0)
395 return bus_log_parse_error(r);
396
f48e75cb
LP
397 fputs(prefix, stdout);
398 fputs(inet_ntop(family, a, buffer, sizeof(buffer)), stdout);
399 if (family == AF_INET6 && ifi > 0)
400 printf("%%%i", ifi);
401 fputc('\n', stdout);
878cd7e9
LP
402
403 r = sd_bus_message_exit_container(reply);
404 if (r < 0)
405 return bus_log_parse_error(r);
406
407 if (prefix != prefix2)
408 prefix = prefix2;
409 }
410 if (r < 0)
411 return bus_log_parse_error(r);
412
413 r = sd_bus_message_exit_container(reply);
414 if (r < 0)
415 return bus_log_parse_error(r);
416
417 return 0;
418}
419
717603e3
LP
420static int print_os_release(sd_bus *bus, const char *name, const char *prefix) {
421 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
422 const char *k, *v, *pretty = NULL;
423 int r;
424
425 assert(bus);
426 assert(name);
427 assert(prefix);
428
429 r = sd_bus_call_method(bus,
430 "org.freedesktop.machine1",
431 "/org/freedesktop/machine1",
432 "org.freedesktop.machine1.Manager",
433 "GetMachineOSRelease",
434 NULL,
435 &reply,
436 "s", name);
437 if (r < 0)
438 return r;
439
440 r = sd_bus_message_enter_container(reply, 'a', "{ss}");
441 if (r < 0)
442 return bus_log_parse_error(r);
443
444 while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) {
445 if (streq(k, "PRETTY_NAME"))
446 pretty = v;
447
448 }
449 if (r < 0)
450 return bus_log_parse_error(r);
451
452 r = sd_bus_message_exit_container(reply);
453 if (r < 0)
454 return bus_log_parse_error(r);
455
456 if (pretty)
457 printf("%s%s\n", prefix, pretty);
458
459 return 0;
460}
461
1ee306e1 462typedef struct MachineStatusInfo {
9f6eb1cd 463 char *name;
1ee306e1 464 sd_id128_t id;
9f6eb1cd
KS
465 char *class;
466 char *service;
89f7c846 467 char *unit;
9f6eb1cd 468 char *root_directory;
1ee306e1
LP
469 pid_t leader;
470 usec_t timestamp;
f48e75cb
LP
471 int *netif;
472 unsigned n_netif;
1ee306e1
LP
473} MachineStatusInfo;
474
a1da8583 475static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
1ee306e1
LP
476 char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
477 char since2[FORMAT_TIMESTAMP_MAX], *s2;
f48e75cb
LP
478 int ifi = -1;
479
56159e0d 480 assert(bus);
1ee306e1
LP
481 assert(i);
482
483 fputs(strna(i->name), stdout);
484
485 if (!sd_id128_equal(i->id, SD_ID128_NULL))
486 printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id));
487 else
488 putchar('\n');
489
490 s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp);
491 s2 = format_timestamp(since2, sizeof(since2), i->timestamp);
492
493 if (s1)
494 printf("\t Since: %s; %s\n", s2, s1);
495 else if (s2)
496 printf("\t Since: %s\n", s2);
497
498 if (i->leader > 0) {
499 _cleanup_free_ char *t = NULL;
500
501 printf("\t Leader: %u", (unsigned) i->leader);
502
503 get_process_comm(i->leader, &t);
504 if (t)
505 printf(" (%s)", t);
506
507 putchar('\n');
508 }
509
510 if (i->service) {
511 printf("\t Service: %s", i->service);
512
513 if (i->class)
514 printf("; class %s", i->class);
515
516 putchar('\n');
517 } else if (i->class)
518 printf("\t Class: %s\n", i->class);
519
1ee306e1
LP
520 if (i->root_directory)
521 printf("\t Root: %s\n", i->root_directory);
522
f48e75cb
LP
523 if (i->n_netif > 0) {
524 unsigned c;
525
526 fputs("\t Iface:", stdout);
527
528 for (c = 0; c < i->n_netif; c++) {
529 char name[IF_NAMESIZE+1] = "";
530
531 if (if_indextoname(i->netif[c], name)) {
532 fputc(' ', stdout);
533 fputs(name, stdout);
534
535 if (ifi < 0)
536 ifi = i->netif[c];
537 else
538 ifi = 0;
539 } else
540 printf(" %i", i->netif[c]);
541 }
542
543 fputc('\n', stdout);
544 }
545
546 print_addresses(bus, i->name, ifi,
878cd7e9
LP
547 "\t Address: ",
548 "\t ");
549
717603e3
LP
550 print_os_release(bus, i->name, "\t OS: ");
551
89f7c846
LP
552 if (i->unit) {
553 printf("\t Unit: %s\n", i->unit);
554 show_unit_cgroup(bus, i->unit, i->leader);
1ee306e1
LP
555 }
556}
557
f48e75cb
LP
558static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
559 MachineStatusInfo *i = userdata;
560 size_t l;
561 const void *v;
562 int r;
563
564 assert_cc(sizeof(int32_t) == sizeof(int));
565 r = sd_bus_message_read_array(m, SD_BUS_TYPE_INT32, &v, &l);
566 if (r < 0)
567 return r;
e7e9b6bb
ZJS
568 if (r == 0)
569 return -EBADMSG;
f48e75cb
LP
570
571 i->n_netif = l / sizeof(int32_t);
572 i->netif = memdup(v, l);
573 if (!i->netif)
574 return -ENOMEM;
575
576 return 0;
577}
578
fefdc04b 579static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
a6c61602 580
9f6eb1cd 581 static const struct bus_properties_map map[] = {
f48e75cb
LP
582 { "Name", "s", NULL, offsetof(MachineStatusInfo, name) },
583 { "Class", "s", NULL, offsetof(MachineStatusInfo, class) },
584 { "Service", "s", NULL, offsetof(MachineStatusInfo, service) },
585 { "Unit", "s", NULL, offsetof(MachineStatusInfo, unit) },
586 { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) },
587 { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) },
588 { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp) },
589 { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) },
590 { "NetworkInterfaces", "ai", map_netif, 0 },
9f6eb1cd
KS
591 {}
592 };
a6c61602
LP
593
594 MachineStatusInfo info = {};
a1da8583
TG
595 int r;
596
56159e0d
LP
597 assert(verb);
598 assert(bus);
1ee306e1
LP
599 assert(path);
600 assert(new_line);
601
9f6eb1cd
KS
602 r = bus_map_all_properties(bus,
603 "org.freedesktop.machine1",
604 path,
605 map,
606 &info);
f647962d
MS
607 if (r < 0)
608 return log_error_errno(r, "Could not get properties: %m");
1ee306e1 609
1ee306e1
LP
610 if (*new_line)
611 printf("\n");
1ee306e1
LP
612 *new_line = true;
613
9f6eb1cd 614 print_machine_status_info(bus, &info);
1ee306e1 615
9f6eb1cd
KS
616 free(info.name);
617 free(info.class);
618 free(info.service);
89f7c846 619 free(info.unit);
9f6eb1cd 620 free(info.root_directory);
f48e75cb 621 free(info.netif);
1ee306e1 622
9f6eb1cd
KS
623 return r;
624}
1ee306e1 625
fefdc04b 626static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line) {
9f6eb1cd 627 int r;
1ee306e1 628
56159e0d
LP
629 assert(bus);
630 assert(path);
631 assert(new_line);
632
9f6eb1cd
KS
633 if (*new_line)
634 printf("\n");
1ee306e1 635
9f6eb1cd 636 *new_line = true;
a1da8583 637
27e72d6b 638 r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all);
a1da8583 639 if (r < 0)
da927ba9 640 log_error_errno(r, "Could not get properties: %m");
1ee306e1 641
a7893c6b 642 return r;
1ee306e1
LP
643}
644
fefdc04b 645static int show_machine(int argc, char *argv[], void *userdata) {
56159e0d 646
a1da8583 647 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
56159e0d 648 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
9f6eb1cd 649 bool properties, new_line = false;
56159e0d
LP
650 sd_bus *bus = userdata;
651 int r = 0, i;
1ee306e1
LP
652
653 assert(bus);
1ee306e1 654
56159e0d 655 properties = !strstr(argv[0], "status");
1ee306e1
LP
656
657 pager_open_if_enabled();
658
56159e0d 659 if (properties && argc <= 1) {
a1da8583 660
9f6eb1cd 661 /* If no argument is specified, inspect the manager
1ee306e1 662 * itself */
fefdc04b 663 r = show_machine_properties(bus, "/org/freedesktop/machine1", &new_line);
8c841f21 664 if (r < 0)
9f6eb1cd 665 return r;
1ee306e1
LP
666 }
667
56159e0d 668 for (i = 1; i < argc; i++) {
1ee306e1
LP
669 const char *path = NULL;
670
a1da8583
TG
671 r = sd_bus_call_method(
672 bus,
673 "org.freedesktop.machine1",
674 "/org/freedesktop/machine1",
675 "org.freedesktop.machine1.Manager",
676 "GetMachine",
677 &error,
678 &reply,
56159e0d 679 "s", argv[i]);
a1da8583
TG
680 if (r < 0) {
681 log_error("Could not get path to machine: %s", bus_error_message(&error, -r));
682 return r;
683 }
684
685 r = sd_bus_message_read(reply, "o", &path);
5b30bef8
LP
686 if (r < 0)
687 return bus_log_parse_error(r);
1ee306e1 688
9f6eb1cd 689 if (properties)
fefdc04b 690 r = show_machine_properties(bus, path, &new_line);
9f6eb1cd 691 else
fefdc04b
LP
692 r = show_machine_info(argv[0], bus, path, &new_line);
693 }
694
695 return r;
696}
697
698typedef struct ImageStatusInfo {
699 char *name;
700 char *path;
701 char *type;
702 int read_only;
703 usec_t crtime;
704 usec_t mtime;
b6b18498
LP
705 uint64_t size;
706 uint64_t limit;
707 uint64_t size_exclusive;
708 uint64_t limit_exclusive;
fefdc04b
LP
709} ImageStatusInfo;
710
711static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) {
712 char ts_relative[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
713 char ts_absolute[FORMAT_TIMESTAMP_MAX], *s2;
b6b18498
LP
714 char bs[FORMAT_BYTES_MAX], *s3;
715 char bs_exclusive[FORMAT_BYTES_MAX], *s4;
fefdc04b
LP
716
717 assert(bus);
718 assert(i);
719
720 if (i->name) {
721 fputs(i->name, stdout);
722 putchar('\n');
723 }
724
9a14fb62 725 if (i->type)
fefdc04b
LP
726 printf("\t Type: %s\n", i->type);
727
728 if (i->path)
729 printf("\t Path: %s\n", i->path);
730
731 printf("\t RO: %s%s%s\n",
732 i->read_only ? ansi_highlight_red() : "",
733 i->read_only ? "read-only" : "writable",
734 i->read_only ? ansi_highlight_off() : "");
735
736 s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->crtime);
737 s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->crtime);
b6b18498 738 if (s1 && s2)
fefdc04b
LP
739 printf("\t Created: %s; %s\n", s2, s1);
740 else if (s2)
741 printf("\t Created: %s\n", s2);
742
743 s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->mtime);
744 s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->mtime);
b6b18498 745 if (s1 && s2)
fefdc04b
LP
746 printf("\tModified: %s; %s\n", s2, s1);
747 else if (s2)
748 printf("\tModified: %s\n", s2);
b6b18498
LP
749
750 s3 = format_bytes(bs, sizeof(bs), i->size);
751 s4 = i->size_exclusive != i->size ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->size_exclusive) : NULL;
752 if (s3 && s4)
753 printf("\t Size: %s (exclusive: %s)\n", s3, s4);
754 else if (s3)
755 printf("\t Size: %s\n", s3);
756
757 s3 = format_bytes(bs, sizeof(bs), i->limit);
758 s4 = i->limit_exclusive != i->limit ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->limit_exclusive) : NULL;
759 if (s3 && s4)
760 printf("\t Limit: %s (exclusive: %s)\n", s3, s4);
761 else if (s3)
762 printf("\t Limit: %s\n", s3);
fefdc04b
LP
763}
764
765static int show_image_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
766
767 static const struct bus_properties_map map[] = {
b6b18498
LP
768 { "Name", "s", NULL, offsetof(ImageStatusInfo, name) },
769 { "Path", "s", NULL, offsetof(ImageStatusInfo, path) },
770 { "Type", "s", NULL, offsetof(ImageStatusInfo, type) },
771 { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) },
772 { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) },
773 { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) },
774 { "Size", "t", NULL, offsetof(ImageStatusInfo, size) },
775 { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) },
776 { "SizeExclusive", "t", NULL, offsetof(ImageStatusInfo, size_exclusive) },
777 { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) },
fefdc04b
LP
778 {}
779 };
780
781 ImageStatusInfo info = {};
782 int r;
783
784 assert(verb);
785 assert(bus);
786 assert(path);
787 assert(new_line);
788
789 r = bus_map_all_properties(bus,
790 "org.freedesktop.machine1",
791 path,
792 map,
793 &info);
794 if (r < 0)
795 return log_error_errno(r, "Could not get properties: %m");
796
797 if (*new_line)
798 printf("\n");
799 *new_line = true;
800
801 print_image_status_info(bus, &info);
802
803 free(info.name);
804 free(info.path);
805 free(info.type);
806
807 return r;
808}
809
810static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) {
811 int r;
812
813 assert(bus);
814 assert(path);
815 assert(new_line);
816
817 if (*new_line)
818 printf("\n");
819
820 *new_line = true;
821
822 r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all);
823 if (r < 0)
824 log_error_errno(r, "Could not get properties: %m");
825
826 return r;
827}
828
829static int show_image(int argc, char *argv[], void *userdata) {
830
831 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
832 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
833 bool properties, new_line = false;
834 sd_bus *bus = userdata;
835 int r = 0, i;
836
837 assert(bus);
838
839 properties = !strstr(argv[0], "status");
840
841 pager_open_if_enabled();
842
843 if (properties && argc <= 1) {
844
845 /* If no argument is specified, inspect the manager
846 * itself */
847 r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line);
848 if (r < 0)
849 return r;
850 }
851
852 for (i = 1; i < argc; i++) {
853 const char *path = NULL;
854
855 r = sd_bus_call_method(
856 bus,
857 "org.freedesktop.machine1",
858 "/org/freedesktop/machine1",
859 "org.freedesktop.machine1.Manager",
860 "GetImage",
861 &error,
862 &reply,
863 "s", argv[i]);
864 if (r < 0) {
865 log_error("Could not get path to image: %s", bus_error_message(&error, -r));
866 return r;
867 }
868
869 r = sd_bus_message_read(reply, "o", &path);
870 if (r < 0)
871 return bus_log_parse_error(r);
872
873 if (properties)
874 r = show_image_properties(bus, path, &new_line);
875 else
876 r = show_image_info(argv[0], bus, path, &new_line);
1ee306e1
LP
877 }
878
9f6eb1cd 879 return r;
1ee306e1
LP
880}
881
56159e0d 882static int kill_machine(int argc, char *argv[], void *userdata) {
a1da8583 883 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
56159e0d
LP
884 sd_bus *bus = userdata;
885 int i;
1ee306e1 886
56159e0d 887 assert(bus);
1ee306e1
LP
888
889 if (!arg_kill_who)
890 arg_kill_who = "all";
891
56159e0d 892 for (i = 1; i < argc; i++) {
1ee306e1
LP
893 int r;
894
a1da8583 895 r = sd_bus_call_method(
56159e0d
LP
896 bus,
897 "org.freedesktop.machine1",
898 "/org/freedesktop/machine1",
899 "org.freedesktop.machine1.Manager",
900 "KillMachine",
901 &error,
902 NULL,
903 "ssi", argv[i], arg_kill_who, arg_signal);
a1da8583
TG
904 if (r < 0) {
905 log_error("Could not kill machine: %s", bus_error_message(&error, -r));
1ee306e1 906 return r;
a1da8583 907 }
1ee306e1
LP
908 }
909
910 return 0;
911}
912
56159e0d 913static int reboot_machine(int argc, char *argv[], void *userdata) {
1dba654b
LP
914 arg_kill_who = "leader";
915 arg_signal = SIGINT; /* sysvinit + systemd */
1ee306e1 916
56159e0d 917 return kill_machine(argc, argv, userdata);
1dba654b 918}
1ee306e1 919
56159e0d 920static int poweroff_machine(int argc, char *argv[], void *userdata) {
1dba654b
LP
921 arg_kill_who = "leader";
922 arg_signal = SIGRTMIN+4; /* only systemd */
1ee306e1 923
56159e0d 924 return kill_machine(argc, argv, userdata);
1ee306e1
LP
925}
926
56159e0d 927static int terminate_machine(int argc, char *argv[], void *userdata) {
923d8fd3 928 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
56159e0d
LP
929 sd_bus *bus = userdata;
930 int i;
923d8fd3 931
56159e0d 932 assert(bus);
923d8fd3 933
56159e0d 934 for (i = 1; i < argc; i++) {
1dba654b 935 int r;
923d8fd3
LP
936
937 r = sd_bus_call_method(
938 bus,
939 "org.freedesktop.machine1",
940 "/org/freedesktop/machine1",
941 "org.freedesktop.machine1.Manager",
1dba654b 942 "TerminateMachine",
923d8fd3 943 &error,
1dba654b 944 NULL,
56159e0d 945 "s", argv[i]);
923d8fd3 946 if (r < 0) {
1dba654b 947 log_error("Could not terminate machine: %s", bus_error_message(&error, -r));
923d8fd3
LP
948 return r;
949 }
923d8fd3
LP
950 }
951
952 return 0;
953}
954
785890ac
LP
955static int machine_get_leader(sd_bus *bus, const char *name, pid_t *ret) {
956 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
957 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL, *reply2 = NULL;
958 const char *object;
959 uint32_t leader;
960 int r;
961
962 assert(bus);
963 assert(name);
964 assert(ret);
965
966 r = sd_bus_call_method(
967 bus,
968 "org.freedesktop.machine1",
969 "/org/freedesktop/machine1",
970 "org.freedesktop.machine1.Manager",
971 "GetMachine",
972 &error,
973 &reply,
974 "s", name);
975 if (r < 0) {
976 log_error("Could not get path to machine: %s", bus_error_message(&error, -r));
977 return r;
978 }
979
980 r = sd_bus_message_read(reply, "o", &object);
981 if (r < 0)
982 return bus_log_parse_error(r);
983
984 r = sd_bus_get_property(
985 bus,
986 "org.freedesktop.machine1",
987 object,
988 "org.freedesktop.machine1.Machine",
989 "Leader",
990 &error,
991 &reply2,
992 "u");
993 if (r < 0)
994 return log_error_errno(r, "Failed to retrieve PID of leader: %m");
995
996 r = sd_bus_message_read(reply2, "u", &leader);
997 if (r < 0)
998 return bus_log_parse_error(r);
999
1000 *ret = leader;
1001 return 0;
1002}
1003
56159e0d 1004static int copy_files(int argc, char *argv[], void *userdata) {
f2cbe59e
LP
1005 char *dest, *host_path, *container_path, *host_dirname, *host_basename, *container_dirname, *container_basename, *t;
1006 _cleanup_close_ int hostfd = -1;
56159e0d 1007 sd_bus *bus = userdata;
f2cbe59e
LP
1008 pid_t child, leader;
1009 bool copy_from;
1010 siginfo_t si;
1011 int r;
1012
56159e0d 1013 assert(bus);
f2cbe59e 1014
56159e0d
LP
1015 copy_from = streq(argv[0], "copy-from");
1016 dest = argv[3] ?: argv[2];
1017 host_path = strdupa(copy_from ? dest : argv[2]);
1018 container_path = strdupa(copy_from ? argv[2] : dest);
f2cbe59e
LP
1019
1020 if (!path_is_absolute(container_path)) {
1021 log_error("Container path not absolute.");
1022 return -EINVAL;
1023 }
1024
1025 t = strdup(host_path);
1026 host_basename = basename(t);
1027 host_dirname = dirname(host_path);
1028
1029 t = strdup(container_path);
1030 container_basename = basename(t);
1031 container_dirname = dirname(container_path);
1032
56159e0d 1033 r = machine_get_leader(bus, argv[1], &leader);
f2cbe59e
LP
1034 if (r < 0)
1035 return r;
1036
1037 hostfd = open(host_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY);
1038 if (r < 0)
1039 return log_error_errno(errno, "Failed to open source directory: %m");
1040
1041 child = fork();
1042 if (child < 0)
1043 return log_error_errno(errno, "Failed to fork(): %m");
1044
1045 if (child == 0) {
1046 int containerfd;
1047 const char *q;
1048 int mntfd;
1049
1050 q = procfs_file_alloca(leader, "ns/mnt");
1051 mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC);
1052 if (mntfd < 0) {
1053 log_error_errno(errno, "Failed to open mount namespace of leader: %m");
1054 _exit(EXIT_FAILURE);
1055 }
1056
1057 if (setns(mntfd, CLONE_NEWNS) < 0) {
1058 log_error_errno(errno, "Failed to join namespace of leader: %m");
1059 _exit(EXIT_FAILURE);
1060 }
1061
1062 containerfd = open(container_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY);
1063 if (containerfd < 0) {
1064 log_error_errno(errno, "Failed top open destination directory: %m");
1065 _exit(EXIT_FAILURE);
1066 }
1067
1068 if (copy_from)
1069 r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, true);
1070 else
1071 r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, true);
1072 if (r < 0) {
1073 log_error_errno(errno, "Failed to copy tree: %m");
1074 _exit(EXIT_FAILURE);
1075 }
1076
1077 _exit(EXIT_SUCCESS);
1078 }
1079
1080 r = wait_for_terminate(child, &si);
1081 if (r < 0)
1082 return log_error_errno(r, "Failed to wait for client: %m");
1083 if (si.si_code != CLD_EXITED) {
1084 log_error("Client died abnormally.");
1085 return -EIO;
1086 }
1087 if (si.si_status != EXIT_SUCCESS)
1088 return -EIO;
1089
1090 return 0;
1091}
1092
56159e0d 1093static int bind_mount(int argc, char *argv[], void *userdata) {
785890ac 1094 char mount_slave[] = "/tmp/propagate.XXXXXX", *mount_tmp, *mount_outside, *p;
56159e0d 1095 sd_bus *bus = userdata;
785890ac
LP
1096 pid_t child, leader;
1097 const char *dest;
1098 siginfo_t si;
1099 bool mount_slave_created = false, mount_slave_mounted = false,
1100 mount_tmp_created = false, mount_tmp_mounted = false,
1101 mount_outside_created = false, mount_outside_mounted = false;
1102 int r;
1103
56159e0d
LP
1104 assert(bus);
1105
785890ac
LP
1106 /* One day, when bind mounting /proc/self/fd/n works across
1107 * namespace boundaries we should rework this logic to make
1108 * use of it... */
1109
56159e0d 1110 dest = argv[3] ?: argv[2];
785890ac
LP
1111 if (!path_is_absolute(dest)) {
1112 log_error("Destination path not absolute.");
1113 return -EINVAL;
1114 }
1115
56159e0d 1116 p = strappenda("/run/systemd/nspawn/propagate/", argv[1], "/");
785890ac
LP
1117 if (access(p, F_OK) < 0) {
1118 log_error("Container does not allow propagation of mount points.");
1119 return -ENOTSUP;
1120 }
1121
56159e0d 1122 r = machine_get_leader(bus, argv[1], &leader);
785890ac
LP
1123 if (r < 0)
1124 return r;
1125
1126 /* Our goal is to install a new bind mount into the container,
1127 possibly read-only. This is irritatingly complex
1128 unfortunately, currently.
1129
1130 First, we start by creating a private playground in /tmp,
1131 that we can mount MS_SLAVE. (Which is necessary, since
1132 MS_MOUNT cannot be applied to mounts with MS_SHARED parent
1133 mounts.) */
1134
1135 if (!mkdtemp(mount_slave))
1136 return log_error_errno(errno, "Failed to create playground: %m");
1137
1138 mount_slave_created = true;
1139
1140 if (mount(mount_slave, mount_slave, NULL, MS_BIND, NULL) < 0) {
1141 r = log_error_errno(errno, "Failed to make bind mount: %m");
1142 goto finish;
1143 }
1144
1145 mount_slave_mounted = true;
1146
1147 if (mount(NULL, mount_slave, NULL, MS_SLAVE, NULL) < 0) {
1148 r = log_error_errno(errno, "Failed to remount slave: %m");
1149 goto finish;
1150 }
1151
1152 /* Second, we mount the source directory to a directory inside
1153 of our MS_SLAVE playground. */
1154 mount_tmp = strappenda(mount_slave, "/mount");
1155 if (mkdir(mount_tmp, 0700) < 0) {
1156 r = log_error_errno(errno, "Failed to create temporary mount: %m");
1157 goto finish;
1158 }
1159
1160 mount_tmp_created = true;
1161
56159e0d 1162 if (mount(argv[2], mount_tmp, NULL, MS_BIND, NULL) < 0) {
785890ac
LP
1163 r = log_error_errno(errno, "Failed to overmount: %m");
1164 goto finish;
1165 }
1166
1167 mount_tmp_mounted = true;
1168
1169 /* Third, we remount the new bind mount read-only if requested. */
1170 if (arg_read_only)
1171 if (mount(NULL, mount_tmp, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) {
1172 r = log_error_errno(errno, "Failed to mark read-only: %m");
1173 goto finish;
1174 }
1175
1176 /* Fourth, we move the new bind mount into the propagation
1177 * directory. This way it will appear there read-only
1178 * right-away. */
1179
56159e0d 1180 mount_outside = strappenda("/run/systemd/nspawn/propagate/", argv[1], "/XXXXXX");
785890ac
LP
1181 if (!mkdtemp(mount_outside)) {
1182 r = log_error_errno(errno, "Cannot create propagation directory: %m");
1183 goto finish;
1184 }
1185
1186 mount_outside_created = true;
1187
1188 if (mount(mount_tmp, mount_outside, NULL, MS_MOVE, NULL) < 0) {
1189 r = log_error_errno(errno, "Failed to move: %m");
1190 goto finish;
1191 }
1192
1193 mount_outside_mounted = true;
1194 mount_tmp_mounted = false;
1195
1196 (void) rmdir(mount_tmp);
1197 mount_tmp_created = false;
1198
1199 (void) umount(mount_slave);
1200 mount_slave_mounted = false;
1201
1202 (void) rmdir(mount_slave);
1203 mount_slave_created = false;
1204
1205 child = fork();
1206 if (child < 0) {
1207 r = log_error_errno(errno, "Failed to fork(): %m");
1208 goto finish;
1209 }
1210
1211 if (child == 0) {
1212 const char *mount_inside;
1213 int mntfd;
1214 const char *q;
1215
1216 q = procfs_file_alloca(leader, "ns/mnt");
1217 mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC);
1218 if (mntfd < 0) {
1219 log_error_errno(errno, "Failed to open mount namespace of leader: %m");
1220 _exit(EXIT_FAILURE);
1221 }
1222
1223 if (setns(mntfd, CLONE_NEWNS) < 0) {
1224 log_error_errno(errno, "Failed to join namespace of leader: %m");
1225 _exit(EXIT_FAILURE);
1226 }
1227
1228 if (arg_mkdir)
1229 mkdir_p(dest, 0755);
1230
1231 /* Fifth, move the mount to the right place inside */
1232 mount_inside = strappenda("/run/systemd/nspawn/incoming/", basename(mount_outside));
1233 if (mount(mount_inside, dest, NULL, MS_MOVE, NULL) < 0) {
1234 log_error_errno(errno, "Failed to mount: %m");
1235 _exit(EXIT_FAILURE);
1236 }
1237
1238 _exit(EXIT_SUCCESS);
1239 }
1240
1241 r = wait_for_terminate(child, &si);
1242 if (r < 0) {
1243 log_error_errno(r, "Failed to wait for client: %m");
1244 goto finish;
1245 }
1246 if (si.si_code != CLD_EXITED) {
1247 log_error("Client died abnormally.");
1248 r = -EIO;
1249 goto finish;
1250 }
1251 if (si.si_status != EXIT_SUCCESS) {
1252 r = -EIO;
1253 goto finish;
1254 }
1255
1256 r = 0;
1257
1258finish:
1259 if (mount_outside_mounted)
1260 umount(mount_outside);
1261 if (mount_outside_created)
1262 rmdir(mount_outside);
1263
1264 if (mount_tmp_mounted)
1265 umount(mount_tmp);
1266 if (mount_tmp_created)
1267 umount(mount_tmp);
1268
1269 if (mount_slave_mounted)
1270 umount(mount_slave);
1271 if (mount_slave_created)
1272 umount(mount_slave);
1273
1274 return r;
1275}
1276
0ec5543c
LP
1277static int on_machine_removed(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
1278 PTYForward ** forward = (PTYForward**) userdata;
1279 int r;
1280
1281 assert(bus);
1282 assert(m);
1283 assert(forward);
1284
1285 if (*forward) {
1286 /* If the forwarder is already initialized, tell it to
da054c37
LP
1287 * exit on the next vhangup(), so that we still flush
1288 * out what might be queued and exit then. */
0ec5543c 1289
da054c37 1290 r = pty_forward_set_ignore_vhangup(*forward, false);
0ec5543c
LP
1291 if (r >= 0)
1292 return 0;
1293
da054c37 1294 log_error_errno(r, "Failed to set ignore_vhangup flag: %m");
0ec5543c
LP
1295 }
1296
1297 /* On error, or when the forwarder is not initialized yet, quit immediately */
1298 sd_event_exit(sd_bus_get_event(bus), EXIT_FAILURE);
1299 return 0;
1300}
1301
56159e0d 1302static int login_machine(int argc, char *argv[], void *userdata) {
04d39279 1303 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
d04c1fb8 1304 _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
0ec5543c 1305 _cleanup_bus_slot_unref_ sd_bus_slot *slot = NULL;
023fb90b
LP
1306 _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
1307 _cleanup_event_unref_ sd_event *event = NULL;
40205d70 1308 int master = -1, r, ret = 0;
56159e0d 1309 sd_bus *bus = userdata;
0ec5543c 1310 const char *pty, *match;
04d39279 1311 sigset_t mask;
c7b7d449 1312 char last_char = 0;
0ec5543c 1313 bool machine_died;
04d39279
LP
1314
1315 assert(bus);
04d39279 1316
bf441e3d 1317 if (arg_transport != BUS_TRANSPORT_LOCAL &&
de33fc62 1318 arg_transport != BUS_TRANSPORT_MACHINE) {
923d8fd3 1319 log_error("Login only supported on local machines.");
04d39279
LP
1320 return -ENOTSUP;
1321 }
1322
023fb90b 1323 r = sd_event_default(&event);
f647962d
MS
1324 if (r < 0)
1325 return log_error_errno(r, "Failed to get event loop: %m");
023fb90b
LP
1326
1327 r = sd_bus_attach_event(bus, event, 0);
f647962d
MS
1328 if (r < 0)
1329 return log_error_errno(r, "Failed to attach bus to event loop: %m");
023fb90b 1330
0ec5543c
LP
1331 match = strappenda("type='signal',"
1332 "sender='org.freedesktop.machine1',"
1333 "path='/org/freedesktop/machine1',",
1334 "interface='org.freedesktop.machine1.Manager',"
1335 "member='MachineRemoved',"
1336 "arg0='",
1337 argv[1],
1338 "'");
1339
1340 r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward);
1341 if (r < 0)
1342 return log_error_errno(r, "Failed to add machine removal match: %m");
1343
d04c1fb8
LP
1344 r = sd_bus_message_new_method_call(bus,
1345 &m,
1346 "org.freedesktop.machine1",
1347 "/org/freedesktop/machine1",
1348 "org.freedesktop.machine1.Manager",
1349 "OpenMachineLogin");
1350 if (r < 0)
1351 return bus_log_create_error(r);
1352
1353 r = sd_bus_message_set_allow_interactive_authorization(m, true);
1354 if (r < 0)
1355 return bus_log_create_error(r);
1356
1357 r = sd_bus_message_append(m, "s", argv[1]);
1358 if (r < 0)
1359 return bus_log_create_error(r);
1360
1361 r = sd_bus_call(bus, m, 0, &error, &reply);
40205d70
LP
1362 if (r < 0) {
1363 log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r));
785890ac 1364 return r;
40205d70 1365 }
04d39279 1366
40205d70
LP
1367 r = sd_bus_message_read(reply, "hs", &master, &pty);
1368 if (r < 0)
ee451d76 1369 return bus_log_parse_error(r);
04d39279 1370
04d39279
LP
1371 assert_se(sigemptyset(&mask) == 0);
1372 sigset_add_many(&mask, SIGWINCH, SIGTERM, SIGINT, -1);
1373 assert_se(sigprocmask(SIG_BLOCK, &mask, NULL) == 0);
1374
0ec5543c 1375 log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", argv[1]);
04d39279 1376
023fb90b
LP
1377 sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
1378 sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
1379
9b15b784 1380 r = pty_forward_new(event, master, true, &forward);
f647962d
MS
1381 if (r < 0)
1382 return log_error_errno(r, "Failed to create PTY forwarder: %m");
023fb90b
LP
1383
1384 r = sd_event_loop(event);
f647962d
MS
1385 if (r < 0)
1386 return log_error_errno(r, "Failed to run event loop: %m");
04d39279 1387
0ec5543c 1388 pty_forward_get_last_char(forward, &last_char);
da054c37 1389 machine_died = pty_forward_get_ignore_vhangup(forward) == 0;
c7b7d449 1390
023fb90b
LP
1391 forward = pty_forward_free(forward);
1392
c7b7d449
LP
1393 if (last_char != '\n')
1394 fputc('\n', stdout);
04d39279 1395
0ec5543c
LP
1396 if (machine_died)
1397 log_info("Machine %s terminated.", argv[1]);
1398 else
1399 log_info("Connection to machine %s terminated.", argv[1]);
04d39279 1400
023fb90b
LP
1401 sd_event_get_exit_code(event, &ret);
1402 return ret;
04d39279
LP
1403}
1404
08682124
LP
1405static int remove_image(int argc, char *argv[], void *userdata) {
1406 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1407 sd_bus *bus = userdata;
ebd93cb6 1408 int r, i;
08682124
LP
1409
1410 assert(bus);
1411
1412 for (i = 1; i < argc; i++) {
08682124
LP
1413 r = sd_bus_call_method(
1414 bus,
1415 "org.freedesktop.machine1",
1416 "/org/freedesktop/machine1",
1417 "org.freedesktop.machine1.Manager",
1418 "RemoveImage",
1419 &error,
1420 NULL,
1421 "s", argv[i]);
1422 if (r < 0) {
1423 log_error("Could not remove image: %s", bus_error_message(&error, -r));
1424 return r;
1425 }
1426 }
1427
1428 return 0;
1429}
1430
ebd93cb6
LP
1431static int rename_image(int argc, char *argv[], void *userdata) {
1432 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1433 sd_bus *bus = userdata;
1434 int r;
1435
1436 r = sd_bus_call_method(
1437 bus,
1438 "org.freedesktop.machine1",
1439 "/org/freedesktop/machine1",
1440 "org.freedesktop.machine1.Manager",
1441 "RenameImage",
1442 &error,
1443 NULL,
1444 "ss", argv[1], argv[2]);
1445 if (r < 0) {
1446 log_error("Could not rename image: %s", bus_error_message(&error, -r));
1447 return r;
1448 }
1449
1450 return 0;
1451}
1452
1453static int clone_image(int argc, char *argv[], void *userdata) {
1454 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1455 sd_bus *bus = userdata;
1456 int r;
1457
1458 r = sd_bus_call_method(
1459 bus,
1460 "org.freedesktop.machine1",
1461 "/org/freedesktop/machine1",
1462 "org.freedesktop.machine1.Manager",
1463 "CloneImage",
1464 &error,
1465 NULL,
1466 "ssb", argv[1], argv[2], arg_read_only);
1467 if (r < 0) {
1468 log_error("Could not clone image: %s", bus_error_message(&error, -r));
1469 return r;
1470 }
1471
1472 return 0;
1473}
1474
1475static int read_only_image(int argc, char *argv[], void *userdata) {
1476 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1477 sd_bus *bus = userdata;
1478 int b = true, r;
1479
1480 if (argc > 2) {
1481 b = parse_boolean(argv[2]);
1482 if (b < 0) {
1483 log_error("Failed to parse boolean argument: %s", argv[2]);
1484 return -EINVAL;
1485 }
1486 }
1487
1488 r = sd_bus_call_method(
1489 bus,
1490 "org.freedesktop.machine1",
1491 "/org/freedesktop/machine1",
1492 "org.freedesktop.machine1.Manager",
1493 "MarkImageReadOnly",
1494 &error,
1495 NULL,
1496 "sb", argv[1], b);
1497 if (r < 0) {
1498 log_error("Could not mark image read-only: %s", bus_error_message(&error, -r));
1499 return r;
1500 }
1501
1502 return 0;
1503}
1504
ebd011d9
LP
1505static int start_machine(int argc, char *argv[], void *userdata) {
1506 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1507 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
1508 sd_bus *bus = userdata;
1509 int r, i;
1510
1511 assert(bus);
1512
1513 r = bus_wait_for_jobs_new(bus, &w);
1514 if (r < 0)
1515 return log_oom();
1516
1517 for (i = 1; i < argc; i++) {
1518 _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
1519 _cleanup_free_ char *e = NULL, *unit = NULL;
1520 const char *object;
1521
1522 if (!machine_name_is_valid(argv[i])) {
1523 log_error("Invalid machine name %s.", argv[i]);
1524 return -EINVAL;
1525 }
1526
1527 e = unit_name_escape(argv[i]);
1528 if (!e)
1529 return log_oom();
1530
1531 unit = unit_name_build("systemd-nspawn", e, ".service");
1532 if (!unit)
1533 return log_oom();
1534
1535 r = sd_bus_message_new_method_call(
1536 bus,
1537 &m,
1538 "org.freedesktop.systemd1",
1539 "/org/freedesktop/systemd1",
1540 "org.freedesktop.systemd1.Manager",
1541 "StartUnit");
1542 if (r < 0)
1543 return bus_log_create_error(r);
1544
1545 r = sd_bus_message_set_allow_interactive_authorization(m, true);
1546 if (r < 0)
1547 return bus_log_create_error(r);
1548
1549 r = sd_bus_message_append(m, "ss", unit, "fail");
1550 if (r < 0)
1551 return bus_log_create_error(r);
1552
1553 r = sd_bus_call(bus, m, 0, &error, &reply);
1554 if (r < 0) {
1555 log_error("Failed to start unit: %s", bus_error_message(&error, -r));
1556 return r;
1557 }
1558
1559 r = sd_bus_message_read(reply, "o", &object);
1560 if (r < 0)
1561 return bus_log_parse_error(r);
1562
1563 r = bus_wait_for_jobs_add(w, object);
1564 if (r < 0)
1565 return log_oom();
1566 }
1567
d8f52ed2 1568 r = bus_wait_for_jobs(w, arg_quiet);
ebd011d9
LP
1569 if (r < 0)
1570 return r;
1571
1572 return 0;
1573}
1574
d8f52ed2
LP
1575static int enable_machine(int argc, char *argv[], void *userdata) {
1576 _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
1577 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1578 int carries_install_info = 0;
1579 const char *method = NULL;
1580 sd_bus *bus = userdata;
1581 int r, i;
1582
1583 assert(bus);
1584
1585 method = streq(argv[0], "enable") ? "EnableUnitFiles" : "DisableUnitFiles";
1586
1587 r = sd_bus_message_new_method_call(
1588 bus,
1589 &m,
1590 "org.freedesktop.systemd1",
1591 "/org/freedesktop/systemd1",
1592 "org.freedesktop.systemd1.Manager",
1593 method);
1594 if (r < 0)
1595 return bus_log_create_error(r);
1596
1597 r = sd_bus_message_set_allow_interactive_authorization(m, true);
1598 if (r < 0)
1599 return bus_log_create_error(r);
1600
1601 r = sd_bus_message_open_container(m, 'a', "s");
1602 if (r < 0)
1603 return bus_log_create_error(r);
1604
1605 for (i = 1; i < argc; i++) {
1606 _cleanup_free_ char *e = NULL, *unit = NULL;
1607
1608 if (!machine_name_is_valid(argv[i])) {
1609 log_error("Invalid machine name %s.", argv[i]);
1610 return -EINVAL;
1611 }
1612
1613 e = unit_name_escape(argv[i]);
1614 if (!e)
1615 return log_oom();
1616
1617 unit = unit_name_build("systemd-nspawn", e, ".service");
1618 if (!unit)
1619 return log_oom();
1620
1621 r = sd_bus_message_append(m, "s", unit);
1622 if (r < 0)
1623 return bus_log_create_error(r);
1624 }
1625
1626 r = sd_bus_message_close_container(m);
1627 if (r < 0)
1628 return bus_log_create_error(r);
1629
1630 if (streq(argv[0], "enable"))
1631 r = sd_bus_message_append(m, "bb", false, false);
1632 else
1633 r = sd_bus_message_append(m, "b", false);
1634 if (r < 0)
1635 return bus_log_create_error(r);
1636
1637 r = sd_bus_call(bus, m, 0, &error, &reply);
1638 if (r < 0) {
1639 log_error("Failed to enable or disable unit: %s", bus_error_message(&error, -r));
1640 return r;
1641 }
1642
1643 if (streq(argv[0], "enable")) {
1644 r = sd_bus_message_read(reply, "b", carries_install_info);
1645 if (r < 0)
1646 return bus_log_parse_error(r);
1647 }
1648
1649 r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
1650 if (r < 0)
1651 return r;
1652
1653 m = sd_bus_message_unref(m);
1654
1655 r = sd_bus_message_new_method_call(
1656 bus,
1657 &m,
1658 "org.freedesktop.systemd1",
1659 "/org/freedesktop/systemd1",
1660 "org.freedesktop.systemd1.Manager",
1661 "Reload");
1662 if (r < 0)
1663 return bus_log_create_error(r);
1664
1665 r = sd_bus_message_set_allow_interactive_authorization(m, true);
1666 if (r < 0)
1667 return bus_log_create_error(r);
1668
1669 r = sd_bus_call(bus, m, 0, &error, NULL);
1670 if (r < 0) {
1671 log_error("Failed to reload daemon: %s", bus_error_message(&error, -r));
1672 return r;
1673 }
1674
1675 return 0;
1676}
1677
56159e0d
LP
1678static int help(int argc, char *argv[], void *userdata) {
1679
1ee306e1 1680 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
f2cbe59e
LP
1681 "Send control commands to or query the virtual machine and container\n"
1682 "registration manager.\n\n"
1683 " -h --help Show this help\n"
1684 " --version Show package version\n"
1685 " --no-pager Do not pipe output into a pager\n"
1686 " --no-legend Do not show the headers and footers\n"
1687 " -H --host=[USER@]HOST Operate on remote host\n"
1688 " -M --machine=CONTAINER Operate on local container\n"
1689 " -p --property=NAME Show only properties by this name\n"
d8f52ed2 1690 " -q --quiet Suppress output\n"
f2cbe59e
LP
1691 " -a --all Show all properties, including empty ones\n"
1692 " -l --full Do not ellipsize output\n"
1693 " --kill-who=WHO Who to send signal to\n"
1694 " -s --signal=SIGNAL Which signal to send\n"
1695 " --read-only Create read-only bind mount\n"
1696 " --mkdir Create directory before bind mounting, if missing\n\n"
cd61c3bf 1697 "Machine Commands:\n"
f2cbe59e 1698 " list List running VMs and containers\n"
fefdc04b 1699 " status NAME... Show VM/container details\n"
f2cbe59e
LP
1700 " show NAME... Show properties of one or more VMs/containers\n"
1701 " login NAME Get a login prompt on a container\n"
ebd011d9 1702 " start NAME... Start container as a service\n"
d8f52ed2
LP
1703 " enable NAME... Enable automatic container start at boot\n"
1704 " disable NAME... Disable automatic container start at boot\n"
f2cbe59e
LP
1705 " poweroff NAME... Power off one or more containers\n"
1706 " reboot NAME... Reboot one or more containers\n"
f2cbe59e 1707 " terminate NAME... Terminate one or more VMs/containers\n"
ebd93cb6 1708 " kill NAME... Send signal to processes of a VM/container\n"
f2cbe59e 1709 " copy-to NAME PATH [PATH] Copy files from the host to a container\n"
ebd93cb6
LP
1710 " copy-from NAME PATH [PATH] Copy files from a container to the host\n"
1711 " bind NAME PATH [PATH] Bind mount a path from the host into a container\n\n"
fefdc04b
LP
1712 "Image Commands:\n"
1713 " list-images Show available images\n"
1714 " image-status NAME... Show image details\n"
08682124 1715 " show-image NAME... Show properties of image\n"
ebd93cb6
LP
1716 " clone NAME NAME Clone an image\n"
1717 " rename NAME NAME Rename an image\n"
1718 " read-only NAME [BOOL] Mark or unmark image read-only\n"
08682124 1719 " remove NAME... Remove an image\n",
1ee306e1 1720 program_invocation_short_name);
56159e0d
LP
1721
1722 return 0;
1ee306e1
LP
1723}
1724
1725static int parse_argv(int argc, char *argv[]) {
1726
1727 enum {
1728 ARG_VERSION = 0x100,
1729 ARG_NO_PAGER,
e56056e9 1730 ARG_NO_LEGEND,
1ee306e1 1731 ARG_KILL_WHO,
785890ac
LP
1732 ARG_READ_ONLY,
1733 ARG_MKDIR,
1ee306e1
LP
1734 };
1735
1736 static const struct option options[] = {
1737 { "help", no_argument, NULL, 'h' },
1738 { "version", no_argument, NULL, ARG_VERSION },
1739 { "property", required_argument, NULL, 'p' },
1740 { "all", no_argument, NULL, 'a' },
1741 { "full", no_argument, NULL, 'l' },
1742 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
e56056e9 1743 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
1ee306e1
LP
1744 { "kill-who", required_argument, NULL, ARG_KILL_WHO },
1745 { "signal", required_argument, NULL, 's' },
1746 { "host", required_argument, NULL, 'H' },
a7893c6b 1747 { "machine", required_argument, NULL, 'M' },
785890ac
LP
1748 { "read-only", no_argument, NULL, ARG_READ_ONLY },
1749 { "mkdir", no_argument, NULL, ARG_MKDIR },
d8f52ed2 1750 { "quiet", no_argument, NULL, 'q' },
eb9da376 1751 {}
1ee306e1
LP
1752 };
1753
a7893c6b 1754 int c, r;
1ee306e1
LP
1755
1756 assert(argc >= 0);
1757 assert(argv);
1758
d8f52ed2 1759 while ((c = getopt_long(argc, argv, "hp:als:H:M:q", options, NULL)) >= 0)
1ee306e1
LP
1760
1761 switch (c) {
1762
1763 case 'h':
56159e0d 1764 return help(0, NULL, NULL);
1ee306e1
LP
1765
1766 case ARG_VERSION:
1767 puts(PACKAGE_STRING);
1768 puts(SYSTEMD_FEATURES);
1769 return 0;
1770
a7893c6b
LP
1771 case 'p':
1772 r = strv_extend(&arg_property, optarg);
1773 if (r < 0)
1774 return log_oom();
1ee306e1
LP
1775
1776 /* If the user asked for a particular
1777 * property, show it to him, even if it is
1778 * empty. */
1779 arg_all = true;
1780 break;
1ee306e1
LP
1781
1782 case 'a':
1783 arg_all = true;
1784 break;
1785
1786 case 'l':
1787 arg_full = true;
1788 break;
1789
1790 case ARG_NO_PAGER:
1791 arg_no_pager = true;
1792 break;
1793
e56056e9
TA
1794 case ARG_NO_LEGEND:
1795 arg_legend = false;
1796 break;
1797
1ee306e1
LP
1798 case ARG_KILL_WHO:
1799 arg_kill_who = optarg;
1800 break;
1801
1802 case 's':
1803 arg_signal = signal_from_string_try_harder(optarg);
1804 if (arg_signal < 0) {
1805 log_error("Failed to parse signal string %s.", optarg);
1806 return -EINVAL;
1807 }
1808 break;
1809
1ee306e1 1810 case 'H':
d21ed1ea 1811 arg_transport = BUS_TRANSPORT_REMOTE;
a7893c6b
LP
1812 arg_host = optarg;
1813 break;
1814
1815 case 'M':
de33fc62 1816 arg_transport = BUS_TRANSPORT_MACHINE;
a7893c6b 1817 arg_host = optarg;
1ee306e1
LP
1818 break;
1819
785890ac
LP
1820 case ARG_READ_ONLY:
1821 arg_read_only = true;
1822 break;
1823
1824 case ARG_MKDIR:
1825 arg_mkdir = true;
1826 break;
1827
d8f52ed2
LP
1828 case 'q':
1829 arg_quiet = true;
1830 break;
1831
1ee306e1
LP
1832 case '?':
1833 return -EINVAL;
1834
1835 default:
eb9da376 1836 assert_not_reached("Unhandled option");
1ee306e1 1837 }
1ee306e1
LP
1838
1839 return 1;
1840}
1841
56159e0d
LP
1842static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
1843
1844 static const Verb verbs[] = {
1845 { "help", VERB_ANY, VERB_ANY, 0, help },
1846 { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines },
1847 { "list-images", VERB_ANY, 1, 0, list_images },
fefdc04b
LP
1848 { "status", 2, VERB_ANY, 0, show_machine },
1849 { "image-status",2, VERB_ANY, 0, show_image },
1850 { "show", VERB_ANY, VERB_ANY, 0, show_machine },
1851 { "show-image", VERB_ANY, VERB_ANY, 0, show_image },
56159e0d
LP
1852 { "terminate", 2, VERB_ANY, 0, terminate_machine },
1853 { "reboot", 2, VERB_ANY, 0, reboot_machine },
1854 { "poweroff", 2, VERB_ANY, 0, poweroff_machine },
1855 { "kill", 2, VERB_ANY, 0, kill_machine },
1856 { "login", 2, 2, 0, login_machine },
1857 { "bind", 3, 4, 0, bind_mount },
1858 { "copy-to", 3, 4, 0, copy_files },
1859 { "copy-from", 3, 4, 0, copy_files },
08682124 1860 { "remove", 2, VERB_ANY, 0, remove_image },
ebd93cb6
LP
1861 { "rename", 3, 3, 0, rename_image },
1862 { "clone", 3, 3, 0, clone_image },
1863 { "read-only", 2, 3, 0, read_only_image },
ebd011d9 1864 { "start", 2, VERB_ANY, 0, start_machine },
d8f52ed2
LP
1865 { "enable", 2, VERB_ANY, 0, enable_machine },
1866 { "disable", 2, VERB_ANY, 0, enable_machine },
56159e0d 1867 {}
1ee306e1
LP
1868 };
1869
56159e0d 1870 return dispatch_verb(argc, argv, verbs, bus);
1ee306e1
LP
1871}
1872
1873int main(int argc, char*argv[]) {
24996861 1874 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
84f6181c 1875 int r;
1ee306e1
LP
1876
1877 setlocale(LC_ALL, "");
1878 log_parse_environment();
1879 log_open();
1880
1881 r = parse_argv(argc, argv);
84f6181c 1882 if (r <= 0)
1ee306e1 1883 goto finish;
1ee306e1 1884
d21ed1ea 1885 r = bus_open_transport(arg_transport, arg_host, false, &bus);
a1da8583 1886 if (r < 0) {
da927ba9 1887 log_error_errno(r, "Failed to create bus connection: %m");
a1da8583
TG
1888 goto finish;
1889 }
1ee306e1 1890
56159e0d 1891 r = machinectl_main(argc, argv, bus);
1ee306e1
LP
1892
1893finish:
1ee306e1
LP
1894 pager_close();
1895
84f6181c
LP
1896 strv_free(arg_property);
1897
1898 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1ee306e1 1899}