]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/machine/machinectl.c
machined: make "machinectl copy-to" and "machinectl copy-from" server side operations
[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>
eef46c37 34
a1da8583 35#include "sd-bus.h"
1ee306e1
LP
36#include "log.h"
37#include "util.h"
38#include "macro.h"
39#include "pager.h"
acf97e21 40#include "spawn-polkit-agent.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"
8b0cc9a3 47#include "logs-show.h"
9d127096 48#include "cgroup-util.h"
04d39279 49#include "ptyfwd.h"
023fb90b 50#include "event-util.h"
785890ac
LP
51#include "path-util.h"
52#include "mkdir.h"
f2cbe59e 53#include "copy.h"
56159e0d 54#include "verbs.h"
3d7415f4 55#include "import-util.h"
1ee306e1
LP
56
57static char **arg_property = NULL;
58static bool arg_all = false;
59static bool arg_full = false;
60static bool arg_no_pager = false;
e56056e9 61static bool arg_legend = true;
1ee306e1
LP
62static const char *arg_kill_who = NULL;
63static int arg_signal = SIGTERM;
d21ed1ea 64static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
1ee306e1 65static char *arg_host = NULL;
785890ac
LP
66static bool arg_read_only = false;
67static bool arg_mkdir = false;
d8f52ed2 68static bool arg_quiet = false;
acf97e21 69static bool arg_ask_password = true;
8b0cc9a3
LP
70static unsigned arg_lines = 10;
71static OutputMode arg_output = OUTPUT_SHORT;
3d7415f4 72static bool arg_force = false;
6e18cc9f 73static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
3d7415f4 74static const char* arg_dkr_index_url = NULL;
1ee306e1
LP
75
76static void pager_open_if_enabled(void) {
77
1ee306e1
LP
78 if (arg_no_pager)
79 return;
80
81 pager_open(false);
82}
83
acf97e21
LP
84static void polkit_agent_open_if_enabled(void) {
85
86 /* Open the polkit agent as a child process if necessary */
87
88 if (!arg_ask_password)
89 return;
90
91 if (arg_transport != BUS_TRANSPORT_LOCAL)
92 return;
93
94 polkit_agent_open();
95}
96
8b0cc9a3
LP
97static OutputFlags get_output_flags(void) {
98 return
99 arg_all * OUTPUT_SHOW_ALL |
100 arg_full * OUTPUT_FULL_WIDTH |
101 (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH |
102 on_tty() * OUTPUT_COLOR |
103 !arg_quiet * OUTPUT_WARN_CUTOFF;
104}
105
0b63e278
LP
106typedef struct MachineInfo {
107 const char *name;
108 const char *class;
109 const char *service;
110} MachineInfo;
111
112static int compare_machine_info(const void *a, const void *b) {
113 const MachineInfo *x = a, *y = b;
114
115 return strcmp(x->name, y->name);
116}
117
56159e0d
LP
118static int list_machines(int argc, char *argv[], void *userdata) {
119
0b63e278 120 size_t max_name = strlen("MACHINE"), max_class = strlen("CLASS"), max_service = strlen("SERVICE");
a1da8583 121 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
0b63e278
LP
122 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
123 _cleanup_free_ MachineInfo *machines = NULL;
a1da8583 124 const char *name, *class, *service, *object;
0b63e278 125 size_t n_machines = 0, n_allocated = 0, j;
56159e0d 126 sd_bus *bus = userdata;
1ee306e1
LP
127 int r;
128
56159e0d
LP
129 assert(bus);
130
1ee306e1
LP
131 pager_open_if_enabled();
132
a1da8583
TG
133 r = sd_bus_call_method(
134 bus,
135 "org.freedesktop.machine1",
136 "/org/freedesktop/machine1",
137 "org.freedesktop.machine1.Manager",
138 "ListMachines",
139 &error,
140 &reply,
3d7415f4 141 NULL);
a1da8583
TG
142 if (r < 0) {
143 log_error("Could not get machines: %s", bus_error_message(&error, -r));
1ee306e1 144 return r;
1ee306e1
LP
145 }
146
3d7415f4 147 r = sd_bus_message_enter_container(reply, 'a', "(ssso)");
a1da8583 148 if (r < 0)
5b30bef8 149 return bus_log_parse_error(r);
1ee306e1 150
a1da8583 151 while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) {
0b63e278
LP
152 size_t l;
153
154 if (!GREEDY_REALLOC(machines, n_allocated, n_machines + 1))
155 return log_oom();
1ee306e1 156
0b63e278
LP
157 machines[n_machines].name = name;
158 machines[n_machines].class = class;
159 machines[n_machines].service = service;
160
161 l = strlen(name);
162 if (l > max_name)
163 max_name = l;
164
165 l = strlen(class);
166 if (l > max_class)
167 max_class = l;
168
169 l = strlen(service);
170 if (l > max_service)
171 max_service = l;
172
173 n_machines ++;
1ee306e1 174 }
a1da8583 175 if (r < 0)
5b30bef8 176 return bus_log_parse_error(r);
a1da8583
TG
177
178 r = sd_bus_message_exit_container(reply);
179 if (r < 0)
5b30bef8 180 return bus_log_parse_error(r);
1ee306e1 181
0b63e278
LP
182 qsort_safe(machines, n_machines, sizeof(MachineInfo), compare_machine_info);
183
184 if (arg_legend)
185 printf("%-*s %-*s %-*s\n",
186 (int) max_name, "MACHINE",
187 (int) max_class, "CLASS",
188 (int) max_service, "SERVICE");
189
190 for (j = 0; j < n_machines; j++)
191 printf("%-*s %-*s %-*s\n",
192 (int) max_name, machines[j].name,
193 (int) max_class, machines[j].class,
194 (int) max_service, machines[j].service);
195
e56056e9 196 if (arg_legend)
0b63e278 197 printf("\n%zu machines listed.\n", n_machines);
1ee306e1
LP
198
199 return 0;
200}
201
cd61c3bf
LP
202typedef struct ImageInfo {
203 const char *name;
204 const char *type;
205 bool read_only;
10f9c755
LP
206 usec_t crtime;
207 usec_t mtime;
b6b18498 208 uint64_t size;
cd61c3bf
LP
209} ImageInfo;
210
211static int compare_image_info(const void *a, const void *b) {
212 const ImageInfo *x = a, *y = b;
213
214 return strcmp(x->name, y->name);
215}
216
56159e0d 217static int list_images(int argc, char *argv[], void *userdata) {
cd61c3bf
LP
218
219 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
c19de711 220 size_t max_name = strlen("NAME"), max_type = strlen("TYPE"), max_size = strlen("USAGE"), max_crtime = strlen("CREATED"), max_mtime = strlen("MODIFIED");
cd61c3bf
LP
221 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
222 _cleanup_free_ ImageInfo *images = NULL;
223 size_t n_images = 0, n_allocated = 0, j;
224 const char *name, *type, *object;
56159e0d 225 sd_bus *bus = userdata;
b6b18498 226 uint64_t crtime, mtime, size;
10f9c755 227 int read_only, r;
cd61c3bf 228
56159e0d
LP
229 assert(bus);
230
cd61c3bf
LP
231 pager_open_if_enabled();
232
233 r = sd_bus_call_method(
234 bus,
235 "org.freedesktop.machine1",
236 "/org/freedesktop/machine1",
237 "org.freedesktop.machine1.Manager",
238 "ListImages",
239 &error,
240 &reply,
241 "");
242 if (r < 0) {
243 log_error("Could not get images: %s", bus_error_message(&error, -r));
244 return r;
245 }
246
b6b18498 247 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)");
cd61c3bf
LP
248 if (r < 0)
249 return bus_log_parse_error(r);
250
b6b18498
LP
251 while ((r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &read_only, &crtime, &mtime, &size, &object)) > 0) {
252 char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_BYTES_MAX)];
10f9c755 253 size_t l;
cd61c3bf
LP
254
255 if (name[0] == '.' && !arg_all)
256 continue;
257
258 if (!GREEDY_REALLOC(images, n_allocated, n_images + 1))
259 return log_oom();
260
261 images[n_images].name = name;
262 images[n_images].type = type;
263 images[n_images].read_only = read_only;
10f9c755
LP
264 images[n_images].crtime = crtime;
265 images[n_images].mtime = mtime;
b6b18498 266 images[n_images].size = size;
10f9c755
LP
267
268 l = strlen(name);
269 if (l > max_name)
270 max_name = l;
cd61c3bf 271
10f9c755
LP
272 l = strlen(type);
273 if (l > max_type)
274 max_type = l;
cd61c3bf 275
10f9c755 276 if (crtime != 0) {
b6b18498 277 l = strlen(strna(format_timestamp(buf, sizeof(buf), crtime)));
10f9c755
LP
278 if (l > max_crtime)
279 max_crtime = l;
280 }
281
282 if (mtime != 0) {
b6b18498 283 l = strlen(strna(format_timestamp(buf, sizeof(buf), mtime)));
10f9c755
LP
284 if (l > max_mtime)
285 max_mtime = l;
286 }
cd61c3bf 287
b6b18498
LP
288 if (size != (uint64_t) -1) {
289 l = strlen(strna(format_bytes(buf, sizeof(buf), size)));
290 if (l > max_size)
291 max_size = l;
292 }
293
cd61c3bf
LP
294 n_images++;
295 }
296 if (r < 0)
297 return bus_log_parse_error(r);
298
299 r = sd_bus_message_exit_container(reply);
300 if (r < 0)
301 return bus_log_parse_error(r);
302
303 qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info);
304
305 if (arg_legend)
b6b18498 306 printf("%-*s %-*s %-3s %-*s %-*s %-*s\n",
10f9c755
LP
307 (int) max_name, "NAME",
308 (int) max_type, "TYPE",
309 "RO",
c19de711 310 (int) max_size, "USAGE",
10f9c755
LP
311 (int) max_crtime, "CREATED",
312 (int) max_mtime, "MODIFIED");
cd61c3bf
LP
313
314 for (j = 0; j < n_images; j++) {
b6b18498 315 char crtime_buf[FORMAT_TIMESTAMP_MAX], mtime_buf[FORMAT_TIMESTAMP_MAX], size_buf[FORMAT_BYTES_MAX];
10f9c755 316
b6b18498 317 printf("%-*s %-*s %s%-3s%s %-*s %-*s %-*s\n",
cd61c3bf
LP
318 (int) max_name, images[j].name,
319 (int) max_type, images[j].type,
8937e7b6 320 images[j].read_only ? ansi_highlight_red() : "", yes_no(images[j].read_only), images[j].read_only ? ansi_highlight_off() : "",
b6b18498
LP
321 (int) max_size, strna(format_bytes(size_buf, sizeof(size_buf), images[j].size)),
322 (int) max_crtime, strna(format_timestamp(crtime_buf, sizeof(crtime_buf), images[j].crtime)),
323 (int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime)));
cd61c3bf
LP
324 }
325
cd61c3bf
LP
326 if (arg_legend)
327 printf("\n%zu images listed.\n", n_images);
328
329 return 0;
330}
331
89f7c846 332static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) {
a1da8583
TG
333 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
334 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
aa1936ea 335 _cleanup_free_ char *path = NULL;
aa1936ea 336 const char *cgroup;
8b0cc9a3 337 int r;
aa1936ea
LP
338 unsigned c;
339
340 assert(bus);
341 assert(unit);
342
d21ed1ea 343 if (arg_transport == BUS_TRANSPORT_REMOTE)
aa1936ea
LP
344 return 0;
345
346 path = unit_dbus_path_from_name(unit);
347 if (!path)
348 return log_oom();
349
a7893c6b 350 r = sd_bus_get_property(
aa1936ea
LP
351 bus,
352 "org.freedesktop.systemd1",
353 path,
89f7c846 354 endswith(unit, ".scope") ? "org.freedesktop.systemd1.Scope" : "org.freedesktop.systemd1.Service",
a7893c6b 355 "ControlGroup",
aa1936ea 356 &error,
a1da8583 357 &reply,
a7893c6b 358 "s");
aa1936ea 359 if (r < 0) {
a1da8583 360 log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r));
aa1936ea
LP
361 return r;
362 }
363
a7893c6b 364 r = sd_bus_message_read(reply, "s", &cgroup);
5b30bef8
LP
365 if (r < 0)
366 return bus_log_parse_error(r);
aa1936ea 367
9d127096
LP
368 if (isempty(cgroup))
369 return 0;
370
371 if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup, false) != 0 && leader <= 0)
372 return 0;
373
aa1936ea
LP
374 c = columns();
375 if (c > 18)
376 c -= 18;
377 else
378 c = 0;
379
8b0cc9a3 380 show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, get_output_flags());
aa1936ea
LP
381 return 0;
382}
383
f48e75cb 384static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) {
878cd7e9
LP
385 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
386 int r;
387
388 assert(bus);
389 assert(name);
390 assert(prefix);
391 assert(prefix2);
392
393 r = sd_bus_call_method(bus,
394 "org.freedesktop.machine1",
395 "/org/freedesktop/machine1",
396 "org.freedesktop.machine1.Manager",
397 "GetMachineAddresses",
398 NULL,
399 &reply,
400 "s", name);
401 if (r < 0)
402 return r;
403
0dd25fb9 404 r = sd_bus_message_enter_container(reply, 'a', "(iay)");
878cd7e9
LP
405 if (r < 0)
406 return bus_log_parse_error(r);
407
0dd25fb9
LP
408 while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) {
409 int family;
878cd7e9
LP
410 const void *a;
411 size_t sz;
412 char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)];
413
0dd25fb9 414 r = sd_bus_message_read(reply, "i", &family);
878cd7e9
LP
415 if (r < 0)
416 return bus_log_parse_error(r);
417
418 r = sd_bus_message_read_array(reply, 'y', &a, &sz);
419 if (r < 0)
420 return bus_log_parse_error(r);
421
f48e75cb
LP
422 fputs(prefix, stdout);
423 fputs(inet_ntop(family, a, buffer, sizeof(buffer)), stdout);
424 if (family == AF_INET6 && ifi > 0)
425 printf("%%%i", ifi);
426 fputc('\n', stdout);
878cd7e9
LP
427
428 r = sd_bus_message_exit_container(reply);
429 if (r < 0)
430 return bus_log_parse_error(r);
431
432 if (prefix != prefix2)
433 prefix = prefix2;
434 }
435 if (r < 0)
436 return bus_log_parse_error(r);
437
438 r = sd_bus_message_exit_container(reply);
439 if (r < 0)
440 return bus_log_parse_error(r);
441
442 return 0;
443}
444
717603e3
LP
445static int print_os_release(sd_bus *bus, const char *name, const char *prefix) {
446 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
447 const char *k, *v, *pretty = NULL;
448 int r;
449
450 assert(bus);
451 assert(name);
452 assert(prefix);
453
454 r = sd_bus_call_method(bus,
455 "org.freedesktop.machine1",
456 "/org/freedesktop/machine1",
457 "org.freedesktop.machine1.Manager",
458 "GetMachineOSRelease",
459 NULL,
460 &reply,
461 "s", name);
462 if (r < 0)
463 return r;
464
465 r = sd_bus_message_enter_container(reply, 'a', "{ss}");
466 if (r < 0)
467 return bus_log_parse_error(r);
468
469 while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) {
470 if (streq(k, "PRETTY_NAME"))
471 pretty = v;
472
473 }
474 if (r < 0)
475 return bus_log_parse_error(r);
476
477 r = sd_bus_message_exit_container(reply);
478 if (r < 0)
479 return bus_log_parse_error(r);
480
481 if (pretty)
482 printf("%s%s\n", prefix, pretty);
483
484 return 0;
485}
486
1ee306e1 487typedef struct MachineStatusInfo {
9f6eb1cd 488 char *name;
1ee306e1 489 sd_id128_t id;
9f6eb1cd
KS
490 char *class;
491 char *service;
89f7c846 492 char *unit;
9f6eb1cd 493 char *root_directory;
1ee306e1 494 pid_t leader;
8b0cc9a3 495 struct dual_timestamp timestamp;
f48e75cb
LP
496 int *netif;
497 unsigned n_netif;
1ee306e1
LP
498} MachineStatusInfo;
499
a1da8583 500static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
1ee306e1
LP
501 char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
502 char since2[FORMAT_TIMESTAMP_MAX], *s2;
f48e75cb
LP
503 int ifi = -1;
504
56159e0d 505 assert(bus);
1ee306e1
LP
506 assert(i);
507
508 fputs(strna(i->name), stdout);
509
510 if (!sd_id128_equal(i->id, SD_ID128_NULL))
511 printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id));
512 else
513 putchar('\n');
514
8b0cc9a3
LP
515 s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp.realtime);
516 s2 = format_timestamp(since2, sizeof(since2), i->timestamp.realtime);
1ee306e1
LP
517
518 if (s1)
519 printf("\t Since: %s; %s\n", s2, s1);
520 else if (s2)
521 printf("\t Since: %s\n", s2);
522
523 if (i->leader > 0) {
524 _cleanup_free_ char *t = NULL;
525
526 printf("\t Leader: %u", (unsigned) i->leader);
527
528 get_process_comm(i->leader, &t);
529 if (t)
530 printf(" (%s)", t);
531
532 putchar('\n');
533 }
534
535 if (i->service) {
536 printf("\t Service: %s", i->service);
537
538 if (i->class)
539 printf("; class %s", i->class);
540
541 putchar('\n');
542 } else if (i->class)
543 printf("\t Class: %s\n", i->class);
544
1ee306e1
LP
545 if (i->root_directory)
546 printf("\t Root: %s\n", i->root_directory);
547
f48e75cb
LP
548 if (i->n_netif > 0) {
549 unsigned c;
550
551 fputs("\t Iface:", stdout);
552
553 for (c = 0; c < i->n_netif; c++) {
554 char name[IF_NAMESIZE+1] = "";
555
556 if (if_indextoname(i->netif[c], name)) {
557 fputc(' ', stdout);
558 fputs(name, stdout);
559
560 if (ifi < 0)
561 ifi = i->netif[c];
562 else
563 ifi = 0;
564 } else
565 printf(" %i", i->netif[c]);
566 }
567
568 fputc('\n', stdout);
569 }
570
571 print_addresses(bus, i->name, ifi,
878cd7e9
LP
572 "\t Address: ",
573 "\t ");
574
717603e3
LP
575 print_os_release(bus, i->name, "\t OS: ");
576
89f7c846
LP
577 if (i->unit) {
578 printf("\t Unit: %s\n", i->unit);
579 show_unit_cgroup(bus, i->unit, i->leader);
8b0cc9a3
LP
580
581 if (arg_transport == BUS_TRANSPORT_LOCAL) {
582
583 show_journal_by_unit(
584 stdout,
585 i->unit,
586 arg_output,
587 0,
588 i->timestamp.monotonic,
589 arg_lines,
590 0,
591 get_output_flags() | OUTPUT_BEGIN_NEWLINE,
592 SD_JOURNAL_LOCAL_ONLY,
593 true,
594 NULL);
595 }
1ee306e1
LP
596 }
597}
598
f48e75cb
LP
599static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) {
600 MachineStatusInfo *i = userdata;
601 size_t l;
602 const void *v;
603 int r;
604
605 assert_cc(sizeof(int32_t) == sizeof(int));
606 r = sd_bus_message_read_array(m, SD_BUS_TYPE_INT32, &v, &l);
607 if (r < 0)
608 return r;
e7e9b6bb
ZJS
609 if (r == 0)
610 return -EBADMSG;
f48e75cb
LP
611
612 i->n_netif = l / sizeof(int32_t);
613 i->netif = memdup(v, l);
614 if (!i->netif)
615 return -ENOMEM;
616
617 return 0;
618}
619
fefdc04b 620static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
a6c61602 621
9f6eb1cd 622 static const struct bus_properties_map map[] = {
8b0cc9a3
LP
623 { "Name", "s", NULL, offsetof(MachineStatusInfo, name) },
624 { "Class", "s", NULL, offsetof(MachineStatusInfo, class) },
625 { "Service", "s", NULL, offsetof(MachineStatusInfo, service) },
626 { "Unit", "s", NULL, offsetof(MachineStatusInfo, unit) },
627 { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) },
628 { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) },
629 { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp.realtime) },
630 { "TimestampMonotonic", "t", NULL, offsetof(MachineStatusInfo, timestamp.monotonic) },
631 { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) },
632 { "NetworkInterfaces", "ai", map_netif, 0 },
9f6eb1cd
KS
633 {}
634 };
a6c61602
LP
635
636 MachineStatusInfo info = {};
a1da8583
TG
637 int r;
638
56159e0d
LP
639 assert(verb);
640 assert(bus);
1ee306e1
LP
641 assert(path);
642 assert(new_line);
643
9f6eb1cd
KS
644 r = bus_map_all_properties(bus,
645 "org.freedesktop.machine1",
646 path,
647 map,
648 &info);
f647962d
MS
649 if (r < 0)
650 return log_error_errno(r, "Could not get properties: %m");
1ee306e1 651
1ee306e1
LP
652 if (*new_line)
653 printf("\n");
1ee306e1
LP
654 *new_line = true;
655
9f6eb1cd 656 print_machine_status_info(bus, &info);
1ee306e1 657
9f6eb1cd
KS
658 free(info.name);
659 free(info.class);
660 free(info.service);
89f7c846 661 free(info.unit);
9f6eb1cd 662 free(info.root_directory);
f48e75cb 663 free(info.netif);
1ee306e1 664
9f6eb1cd
KS
665 return r;
666}
1ee306e1 667
fefdc04b 668static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line) {
9f6eb1cd 669 int r;
1ee306e1 670
56159e0d
LP
671 assert(bus);
672 assert(path);
673 assert(new_line);
674
9f6eb1cd
KS
675 if (*new_line)
676 printf("\n");
1ee306e1 677
9f6eb1cd 678 *new_line = true;
a1da8583 679
27e72d6b 680 r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all);
a1da8583 681 if (r < 0)
da927ba9 682 log_error_errno(r, "Could not get properties: %m");
1ee306e1 683
a7893c6b 684 return r;
1ee306e1
LP
685}
686
fefdc04b 687static int show_machine(int argc, char *argv[], void *userdata) {
56159e0d 688
a1da8583 689 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
56159e0d 690 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
9f6eb1cd 691 bool properties, new_line = false;
56159e0d
LP
692 sd_bus *bus = userdata;
693 int r = 0, i;
1ee306e1
LP
694
695 assert(bus);
1ee306e1 696
56159e0d 697 properties = !strstr(argv[0], "status");
1ee306e1
LP
698
699 pager_open_if_enabled();
700
56159e0d 701 if (properties && argc <= 1) {
a1da8583 702
9f6eb1cd 703 /* If no argument is specified, inspect the manager
1ee306e1 704 * itself */
fefdc04b 705 r = show_machine_properties(bus, "/org/freedesktop/machine1", &new_line);
8c841f21 706 if (r < 0)
9f6eb1cd 707 return r;
1ee306e1
LP
708 }
709
56159e0d 710 for (i = 1; i < argc; i++) {
1ee306e1
LP
711 const char *path = NULL;
712
a1da8583
TG
713 r = sd_bus_call_method(
714 bus,
715 "org.freedesktop.machine1",
716 "/org/freedesktop/machine1",
717 "org.freedesktop.machine1.Manager",
718 "GetMachine",
719 &error,
720 &reply,
56159e0d 721 "s", argv[i]);
a1da8583
TG
722 if (r < 0) {
723 log_error("Could not get path to machine: %s", bus_error_message(&error, -r));
724 return r;
725 }
726
727 r = sd_bus_message_read(reply, "o", &path);
5b30bef8
LP
728 if (r < 0)
729 return bus_log_parse_error(r);
1ee306e1 730
9f6eb1cd 731 if (properties)
fefdc04b 732 r = show_machine_properties(bus, path, &new_line);
9f6eb1cd 733 else
fefdc04b
LP
734 r = show_machine_info(argv[0], bus, path, &new_line);
735 }
736
737 return r;
738}
739
740typedef struct ImageStatusInfo {
741 char *name;
742 char *path;
743 char *type;
744 int read_only;
745 usec_t crtime;
746 usec_t mtime;
c19de711 747 uint64_t usage;
b6b18498 748 uint64_t limit;
c19de711 749 uint64_t usage_exclusive;
b6b18498 750 uint64_t limit_exclusive;
fefdc04b
LP
751} ImageStatusInfo;
752
753static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) {
754 char ts_relative[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
755 char ts_absolute[FORMAT_TIMESTAMP_MAX], *s2;
b6b18498
LP
756 char bs[FORMAT_BYTES_MAX], *s3;
757 char bs_exclusive[FORMAT_BYTES_MAX], *s4;
fefdc04b
LP
758
759 assert(bus);
760 assert(i);
761
762 if (i->name) {
763 fputs(i->name, stdout);
764 putchar('\n');
765 }
766
9a14fb62 767 if (i->type)
fefdc04b
LP
768 printf("\t Type: %s\n", i->type);
769
770 if (i->path)
771 printf("\t Path: %s\n", i->path);
772
773 printf("\t RO: %s%s%s\n",
774 i->read_only ? ansi_highlight_red() : "",
775 i->read_only ? "read-only" : "writable",
776 i->read_only ? ansi_highlight_off() : "");
777
778 s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->crtime);
779 s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->crtime);
b6b18498 780 if (s1 && s2)
fefdc04b
LP
781 printf("\t Created: %s; %s\n", s2, s1);
782 else if (s2)
783 printf("\t Created: %s\n", s2);
784
785 s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->mtime);
786 s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->mtime);
b6b18498 787 if (s1 && s2)
fefdc04b
LP
788 printf("\tModified: %s; %s\n", s2, s1);
789 else if (s2)
790 printf("\tModified: %s\n", s2);
b6b18498 791
c19de711
LP
792 s3 = format_bytes(bs, sizeof(bs), i->usage);
793 s4 = i->usage_exclusive != i->usage ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->usage_exclusive) : NULL;
b6b18498 794 if (s3 && s4)
c19de711 795 printf("\t Usage: %s (exclusive: %s)\n", s3, s4);
b6b18498 796 else if (s3)
c19de711 797 printf("\t Usage: %s\n", s3);
b6b18498
LP
798
799 s3 = format_bytes(bs, sizeof(bs), i->limit);
800 s4 = i->limit_exclusive != i->limit ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->limit_exclusive) : NULL;
801 if (s3 && s4)
802 printf("\t Limit: %s (exclusive: %s)\n", s3, s4);
803 else if (s3)
804 printf("\t Limit: %s\n", s3);
fefdc04b
LP
805}
806
807static int show_image_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
808
809 static const struct bus_properties_map map[] = {
b6b18498
LP
810 { "Name", "s", NULL, offsetof(ImageStatusInfo, name) },
811 { "Path", "s", NULL, offsetof(ImageStatusInfo, path) },
812 { "Type", "s", NULL, offsetof(ImageStatusInfo, type) },
813 { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) },
814 { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) },
815 { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) },
c19de711 816 { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) },
b6b18498 817 { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) },
c19de711 818 { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) },
b6b18498 819 { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) },
fefdc04b
LP
820 {}
821 };
822
823 ImageStatusInfo info = {};
824 int r;
825
826 assert(verb);
827 assert(bus);
828 assert(path);
829 assert(new_line);
830
831 r = bus_map_all_properties(bus,
832 "org.freedesktop.machine1",
833 path,
834 map,
835 &info);
836 if (r < 0)
837 return log_error_errno(r, "Could not get properties: %m");
838
839 if (*new_line)
840 printf("\n");
841 *new_line = true;
842
843 print_image_status_info(bus, &info);
844
845 free(info.name);
846 free(info.path);
847 free(info.type);
848
849 return r;
850}
851
852static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) {
853 int r;
854
855 assert(bus);
856 assert(path);
857 assert(new_line);
858
859 if (*new_line)
860 printf("\n");
861
862 *new_line = true;
863
864 r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all);
865 if (r < 0)
866 log_error_errno(r, "Could not get properties: %m");
867
868 return r;
869}
870
871static int show_image(int argc, char *argv[], void *userdata) {
872
873 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
874 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
875 bool properties, new_line = false;
876 sd_bus *bus = userdata;
877 int r = 0, i;
878
879 assert(bus);
880
881 properties = !strstr(argv[0], "status");
882
883 pager_open_if_enabled();
884
885 if (properties && argc <= 1) {
886
887 /* If no argument is specified, inspect the manager
888 * itself */
889 r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line);
890 if (r < 0)
891 return r;
892 }
893
894 for (i = 1; i < argc; i++) {
895 const char *path = NULL;
896
897 r = sd_bus_call_method(
90adaa25
LP
898 bus,
899 "org.freedesktop.machine1",
900 "/org/freedesktop/machine1",
901 "org.freedesktop.machine1.Manager",
902 "GetImage",
903 &error,
904 &reply,
905 "s", argv[i]);
fefdc04b
LP
906 if (r < 0) {
907 log_error("Could not get path to image: %s", bus_error_message(&error, -r));
908 return r;
909 }
910
911 r = sd_bus_message_read(reply, "o", &path);
912 if (r < 0)
913 return bus_log_parse_error(r);
914
915 if (properties)
916 r = show_image_properties(bus, path, &new_line);
917 else
918 r = show_image_info(argv[0], bus, path, &new_line);
1ee306e1
LP
919 }
920
9f6eb1cd 921 return r;
1ee306e1
LP
922}
923
56159e0d 924static int kill_machine(int argc, char *argv[], void *userdata) {
a1da8583 925 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
56159e0d 926 sd_bus *bus = userdata;
90adaa25 927 int r, i;
1ee306e1 928
56159e0d 929 assert(bus);
1ee306e1 930
acf97e21
LP
931 polkit_agent_open_if_enabled();
932
1ee306e1
LP
933 if (!arg_kill_who)
934 arg_kill_who = "all";
935
56159e0d 936 for (i = 1; i < argc; i++) {
a1da8583 937 r = sd_bus_call_method(
56159e0d
LP
938 bus,
939 "org.freedesktop.machine1",
940 "/org/freedesktop/machine1",
941 "org.freedesktop.machine1.Manager",
942 "KillMachine",
943 &error,
944 NULL,
945 "ssi", argv[i], arg_kill_who, arg_signal);
a1da8583
TG
946 if (r < 0) {
947 log_error("Could not kill machine: %s", bus_error_message(&error, -r));
1ee306e1 948 return r;
a1da8583 949 }
1ee306e1
LP
950 }
951
952 return 0;
953}
954
56159e0d 955static int reboot_machine(int argc, char *argv[], void *userdata) {
1dba654b
LP
956 arg_kill_who = "leader";
957 arg_signal = SIGINT; /* sysvinit + systemd */
1ee306e1 958
56159e0d 959 return kill_machine(argc, argv, userdata);
1dba654b 960}
1ee306e1 961
56159e0d 962static int poweroff_machine(int argc, char *argv[], void *userdata) {
1dba654b
LP
963 arg_kill_who = "leader";
964 arg_signal = SIGRTMIN+4; /* only systemd */
1ee306e1 965
56159e0d 966 return kill_machine(argc, argv, userdata);
1ee306e1
LP
967}
968
56159e0d 969static int terminate_machine(int argc, char *argv[], void *userdata) {
923d8fd3 970 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
56159e0d
LP
971 sd_bus *bus = userdata;
972 int i;
923d8fd3 973
56159e0d 974 assert(bus);
923d8fd3 975
acf97e21
LP
976 polkit_agent_open_if_enabled();
977
56159e0d 978 for (i = 1; i < argc; i++) {
1dba654b 979 int r;
923d8fd3
LP
980
981 r = sd_bus_call_method(
982 bus,
983 "org.freedesktop.machine1",
984 "/org/freedesktop/machine1",
985 "org.freedesktop.machine1.Manager",
1dba654b 986 "TerminateMachine",
923d8fd3 987 &error,
1dba654b 988 NULL,
56159e0d 989 "s", argv[i]);
923d8fd3 990 if (r < 0) {
1dba654b 991 log_error("Could not terminate machine: %s", bus_error_message(&error, -r));
923d8fd3
LP
992 return r;
993 }
923d8fd3
LP
994 }
995
996 return 0;
997}
998
0370612e 999static int copy_files(int argc, char *argv[], void *userdata) {
785890ac 1000 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
0370612e
LP
1001 sd_bus *bus = userdata;
1002 bool copy_from;
785890ac
LP
1003 int r;
1004
1005 assert(bus);
0370612e
LP
1006
1007 copy_from = streq(argv[0], "copy-from");
785890ac
LP
1008
1009 r = sd_bus_call_method(
1010 bus,
1011 "org.freedesktop.machine1",
1012 "/org/freedesktop/machine1",
1013 "org.freedesktop.machine1.Manager",
0370612e 1014 copy_from ? "CopyFromMachine" : "CopyToMachine",
785890ac 1015 &error,
0370612e
LP
1016 NULL,
1017 "sss",
1018 argv[1],
1019 argv[2],
1020 argv[3]);
785890ac 1021 if (r < 0) {
0370612e 1022 log_error("Failed to copy: %s", bus_error_message(&error, -r));
f2cbe59e 1023 return r;
f2cbe59e 1024 }
f2cbe59e
LP
1025
1026 return 0;
1027}
1028
56159e0d 1029static int bind_mount(int argc, char *argv[], void *userdata) {
90adaa25 1030 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
56159e0d 1031 sd_bus *bus = userdata;
785890ac
LP
1032 int r;
1033
56159e0d
LP
1034 assert(bus);
1035
90adaa25
LP
1036 r = sd_bus_call_method(
1037 bus,
1038 "org.freedesktop.machine1",
1039 "/org/freedesktop/machine1",
1040 "org.freedesktop.machine1.Manager",
1041 "BindMountMachine",
1042 &error,
1043 NULL,
1044 "sssbb",
1045 argv[1],
1046 argv[2],
1047 argv[3],
1048 arg_read_only,
1049 arg_mkdir);
785890ac 1050 if (r < 0) {
90adaa25
LP
1051 log_error("Failed to bind mount: %s", bus_error_message(&error, -r));
1052 return r;
785890ac
LP
1053 }
1054
90adaa25 1055 return 0;
785890ac
LP
1056}
1057
0ec5543c
LP
1058static int on_machine_removed(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
1059 PTYForward ** forward = (PTYForward**) userdata;
1060 int r;
1061
1062 assert(bus);
1063 assert(m);
1064 assert(forward);
1065
1066 if (*forward) {
1067 /* If the forwarder is already initialized, tell it to
da054c37
LP
1068 * exit on the next vhangup(), so that we still flush
1069 * out what might be queued and exit then. */
0ec5543c 1070
da054c37 1071 r = pty_forward_set_ignore_vhangup(*forward, false);
0ec5543c
LP
1072 if (r >= 0)
1073 return 0;
1074
da054c37 1075 log_error_errno(r, "Failed to set ignore_vhangup flag: %m");
0ec5543c
LP
1076 }
1077
1078 /* On error, or when the forwarder is not initialized yet, quit immediately */
1079 sd_event_exit(sd_bus_get_event(bus), EXIT_FAILURE);
1080 return 0;
1081}
1082
56159e0d 1083static int login_machine(int argc, char *argv[], void *userdata) {
04d39279 1084 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
d04c1fb8 1085 _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
0ec5543c 1086 _cleanup_bus_slot_unref_ sd_bus_slot *slot = NULL;
023fb90b
LP
1087 _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
1088 _cleanup_event_unref_ sd_event *event = NULL;
40205d70 1089 int master = -1, r, ret = 0;
56159e0d 1090 sd_bus *bus = userdata;
0ec5543c 1091 const char *pty, *match;
c7b7d449 1092 char last_char = 0;
0ec5543c 1093 bool machine_died;
04d39279
LP
1094
1095 assert(bus);
04d39279 1096
bf441e3d 1097 if (arg_transport != BUS_TRANSPORT_LOCAL &&
de33fc62 1098 arg_transport != BUS_TRANSPORT_MACHINE) {
923d8fd3 1099 log_error("Login only supported on local machines.");
04d39279
LP
1100 return -ENOTSUP;
1101 }
1102
acf97e21
LP
1103 polkit_agent_open_if_enabled();
1104
023fb90b 1105 r = sd_event_default(&event);
f647962d
MS
1106 if (r < 0)
1107 return log_error_errno(r, "Failed to get event loop: %m");
023fb90b
LP
1108
1109 r = sd_bus_attach_event(bus, event, 0);
f647962d
MS
1110 if (r < 0)
1111 return log_error_errno(r, "Failed to attach bus to event loop: %m");
023fb90b 1112
63c372cb 1113 match = strjoina("type='signal',"
0ec5543c
LP
1114 "sender='org.freedesktop.machine1',"
1115 "path='/org/freedesktop/machine1',",
1116 "interface='org.freedesktop.machine1.Manager',"
1117 "member='MachineRemoved',"
1118 "arg0='",
1119 argv[1],
1120 "'");
1121
1122 r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward);
1123 if (r < 0)
1124 return log_error_errno(r, "Failed to add machine removal match: %m");
1125
d04c1fb8
LP
1126 r = sd_bus_message_new_method_call(bus,
1127 &m,
1128 "org.freedesktop.machine1",
1129 "/org/freedesktop/machine1",
1130 "org.freedesktop.machine1.Manager",
1131 "OpenMachineLogin");
1132 if (r < 0)
1133 return bus_log_create_error(r);
1134
acf97e21 1135 r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
d04c1fb8
LP
1136 if (r < 0)
1137 return bus_log_create_error(r);
1138
1139 r = sd_bus_message_append(m, "s", argv[1]);
1140 if (r < 0)
1141 return bus_log_create_error(r);
1142
1143 r = sd_bus_call(bus, m, 0, &error, &reply);
40205d70
LP
1144 if (r < 0) {
1145 log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r));
785890ac 1146 return r;
40205d70 1147 }
04d39279 1148
40205d70
LP
1149 r = sd_bus_message_read(reply, "hs", &master, &pty);
1150 if (r < 0)
ee451d76 1151 return bus_log_parse_error(r);
04d39279 1152
813c65c3 1153 sigprocmask_many(SIG_BLOCK, SIGWINCH, SIGTERM, SIGINT, -1);
04d39279 1154
0ec5543c 1155 log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", argv[1]);
04d39279 1156
023fb90b
LP
1157 sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
1158 sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
1159
9b15b784 1160 r = pty_forward_new(event, master, true, &forward);
f647962d
MS
1161 if (r < 0)
1162 return log_error_errno(r, "Failed to create PTY forwarder: %m");
023fb90b
LP
1163
1164 r = sd_event_loop(event);
f647962d
MS
1165 if (r < 0)
1166 return log_error_errno(r, "Failed to run event loop: %m");
04d39279 1167
0ec5543c 1168 pty_forward_get_last_char(forward, &last_char);
da054c37 1169 machine_died = pty_forward_get_ignore_vhangup(forward) == 0;
c7b7d449 1170
023fb90b
LP
1171 forward = pty_forward_free(forward);
1172
c7b7d449
LP
1173 if (last_char != '\n')
1174 fputc('\n', stdout);
04d39279 1175
0ec5543c
LP
1176 if (machine_died)
1177 log_info("Machine %s terminated.", argv[1]);
1178 else
1179 log_info("Connection to machine %s terminated.", argv[1]);
04d39279 1180
023fb90b
LP
1181 sd_event_get_exit_code(event, &ret);
1182 return ret;
04d39279
LP
1183}
1184
08682124
LP
1185static int remove_image(int argc, char *argv[], void *userdata) {
1186 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1187 sd_bus *bus = userdata;
ebd93cb6 1188 int r, i;
08682124
LP
1189
1190 assert(bus);
1191
acf97e21
LP
1192 polkit_agent_open_if_enabled();
1193
08682124 1194 for (i = 1; i < argc; i++) {
08682124
LP
1195 r = sd_bus_call_method(
1196 bus,
1197 "org.freedesktop.machine1",
1198 "/org/freedesktop/machine1",
1199 "org.freedesktop.machine1.Manager",
1200 "RemoveImage",
1201 &error,
1202 NULL,
1203 "s", argv[i]);
1204 if (r < 0) {
1205 log_error("Could not remove image: %s", bus_error_message(&error, -r));
1206 return r;
1207 }
1208 }
1209
1210 return 0;
1211}
1212
ebd93cb6
LP
1213static int rename_image(int argc, char *argv[], void *userdata) {
1214 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1215 sd_bus *bus = userdata;
1216 int r;
1217
acf97e21
LP
1218 polkit_agent_open_if_enabled();
1219
ebd93cb6
LP
1220 r = sd_bus_call_method(
1221 bus,
1222 "org.freedesktop.machine1",
1223 "/org/freedesktop/machine1",
1224 "org.freedesktop.machine1.Manager",
1225 "RenameImage",
1226 &error,
1227 NULL,
1228 "ss", argv[1], argv[2]);
1229 if (r < 0) {
1230 log_error("Could not rename image: %s", bus_error_message(&error, -r));
1231 return r;
1232 }
1233
1234 return 0;
1235}
1236
1237static int clone_image(int argc, char *argv[], void *userdata) {
1238 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1239 sd_bus *bus = userdata;
1240 int r;
1241
acf97e21
LP
1242 polkit_agent_open_if_enabled();
1243
ebd93cb6
LP
1244 r = sd_bus_call_method(
1245 bus,
1246 "org.freedesktop.machine1",
1247 "/org/freedesktop/machine1",
1248 "org.freedesktop.machine1.Manager",
1249 "CloneImage",
1250 &error,
1251 NULL,
1252 "ssb", argv[1], argv[2], arg_read_only);
1253 if (r < 0) {
1254 log_error("Could not clone image: %s", bus_error_message(&error, -r));
1255 return r;
1256 }
1257
1258 return 0;
1259}
1260
1261static int read_only_image(int argc, char *argv[], void *userdata) {
1262 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1263 sd_bus *bus = userdata;
1264 int b = true, r;
1265
1266 if (argc > 2) {
1267 b = parse_boolean(argv[2]);
1268 if (b < 0) {
1269 log_error("Failed to parse boolean argument: %s", argv[2]);
1270 return -EINVAL;
1271 }
1272 }
1273
acf97e21
LP
1274 polkit_agent_open_if_enabled();
1275
ebd93cb6
LP
1276 r = sd_bus_call_method(
1277 bus,
1278 "org.freedesktop.machine1",
1279 "/org/freedesktop/machine1",
1280 "org.freedesktop.machine1.Manager",
1281 "MarkImageReadOnly",
1282 &error,
1283 NULL,
1284 "sb", argv[1], b);
1285 if (r < 0) {
1286 log_error("Could not mark image read-only: %s", bus_error_message(&error, -r));
1287 return r;
1288 }
1289
1290 return 0;
1291}
1292
ebd011d9
LP
1293static int start_machine(int argc, char *argv[], void *userdata) {
1294 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1295 _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
1296 sd_bus *bus = userdata;
1297 int r, i;
1298
1299 assert(bus);
1300
acf97e21
LP
1301 polkit_agent_open_if_enabled();
1302
ebd011d9
LP
1303 r = bus_wait_for_jobs_new(bus, &w);
1304 if (r < 0)
1305 return log_oom();
1306
1307 for (i = 1; i < argc; i++) {
1308 _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
1309 _cleanup_free_ char *e = NULL, *unit = NULL;
1310 const char *object;
1311
1312 if (!machine_name_is_valid(argv[i])) {
1313 log_error("Invalid machine name %s.", argv[i]);
1314 return -EINVAL;
1315 }
1316
1317 e = unit_name_escape(argv[i]);
1318 if (!e)
1319 return log_oom();
1320
1321 unit = unit_name_build("systemd-nspawn", e, ".service");
1322 if (!unit)
1323 return log_oom();
1324
1325 r = sd_bus_message_new_method_call(
1326 bus,
1327 &m,
1328 "org.freedesktop.systemd1",
1329 "/org/freedesktop/systemd1",
1330 "org.freedesktop.systemd1.Manager",
1331 "StartUnit");
1332 if (r < 0)
1333 return bus_log_create_error(r);
1334
acf97e21 1335 r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
ebd011d9
LP
1336 if (r < 0)
1337 return bus_log_create_error(r);
1338
1339 r = sd_bus_message_append(m, "ss", unit, "fail");
1340 if (r < 0)
1341 return bus_log_create_error(r);
1342
1343 r = sd_bus_call(bus, m, 0, &error, &reply);
1344 if (r < 0) {
1345 log_error("Failed to start unit: %s", bus_error_message(&error, -r));
1346 return r;
1347 }
1348
1349 r = sd_bus_message_read(reply, "o", &object);
1350 if (r < 0)
1351 return bus_log_parse_error(r);
1352
1353 r = bus_wait_for_jobs_add(w, object);
1354 if (r < 0)
1355 return log_oom();
1356 }
1357
d8f52ed2 1358 r = bus_wait_for_jobs(w, arg_quiet);
ebd011d9
LP
1359 if (r < 0)
1360 return r;
1361
1362 return 0;
1363}
1364
d8f52ed2
LP
1365static int enable_machine(int argc, char *argv[], void *userdata) {
1366 _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
1367 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1368 int carries_install_info = 0;
1369 const char *method = NULL;
1370 sd_bus *bus = userdata;
1371 int r, i;
1372
1373 assert(bus);
1374
acf97e21
LP
1375 polkit_agent_open_if_enabled();
1376
d8f52ed2
LP
1377 method = streq(argv[0], "enable") ? "EnableUnitFiles" : "DisableUnitFiles";
1378
1379 r = sd_bus_message_new_method_call(
1380 bus,
1381 &m,
1382 "org.freedesktop.systemd1",
1383 "/org/freedesktop/systemd1",
1384 "org.freedesktop.systemd1.Manager",
1385 method);
1386 if (r < 0)
1387 return bus_log_create_error(r);
1388
acf97e21 1389 r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
d8f52ed2
LP
1390 if (r < 0)
1391 return bus_log_create_error(r);
1392
1393 r = sd_bus_message_open_container(m, 'a', "s");
1394 if (r < 0)
1395 return bus_log_create_error(r);
1396
1397 for (i = 1; i < argc; i++) {
1398 _cleanup_free_ char *e = NULL, *unit = NULL;
1399
1400 if (!machine_name_is_valid(argv[i])) {
1401 log_error("Invalid machine name %s.", argv[i]);
1402 return -EINVAL;
1403 }
1404
1405 e = unit_name_escape(argv[i]);
1406 if (!e)
1407 return log_oom();
1408
1409 unit = unit_name_build("systemd-nspawn", e, ".service");
1410 if (!unit)
1411 return log_oom();
1412
1413 r = sd_bus_message_append(m, "s", unit);
1414 if (r < 0)
1415 return bus_log_create_error(r);
1416 }
1417
1418 r = sd_bus_message_close_container(m);
1419 if (r < 0)
1420 return bus_log_create_error(r);
1421
1422 if (streq(argv[0], "enable"))
1423 r = sd_bus_message_append(m, "bb", false, false);
1424 else
1425 r = sd_bus_message_append(m, "b", false);
1426 if (r < 0)
1427 return bus_log_create_error(r);
1428
1429 r = sd_bus_call(bus, m, 0, &error, &reply);
1430 if (r < 0) {
1431 log_error("Failed to enable or disable unit: %s", bus_error_message(&error, -r));
1432 return r;
1433 }
1434
1435 if (streq(argv[0], "enable")) {
1436 r = sd_bus_message_read(reply, "b", carries_install_info);
1437 if (r < 0)
1438 return bus_log_parse_error(r);
1439 }
1440
1441 r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet);
1442 if (r < 0)
1443 return r;
1444
1445 m = sd_bus_message_unref(m);
1446
1447 r = sd_bus_message_new_method_call(
1448 bus,
1449 &m,
1450 "org.freedesktop.systemd1",
1451 "/org/freedesktop/systemd1",
1452 "org.freedesktop.systemd1.Manager",
1453 "Reload");
1454 if (r < 0)
1455 return bus_log_create_error(r);
1456
acf97e21 1457 r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
d8f52ed2
LP
1458 if (r < 0)
1459 return bus_log_create_error(r);
1460
1461 r = sd_bus_call(bus, m, 0, &error, NULL);
1462 if (r < 0) {
1463 log_error("Failed to reload daemon: %s", bus_error_message(&error, -r));
1464 return r;
1465 }
1466
1467 return 0;
1468}
1469
3d7415f4 1470static int match_log_message(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
6adf7b5e 1471 const char **our_path = userdata, *line;
3d7415f4
LP
1472 unsigned priority;
1473 int r;
1474
1475 assert(bus);
1476 assert(m);
6adf7b5e 1477 assert(our_path);
3d7415f4
LP
1478
1479 r = sd_bus_message_read(m, "us", &priority, &line);
1480 if (r < 0) {
1481 bus_log_parse_error(r);
1482 return 0;
1483 }
1484
6adf7b5e 1485 if (!streq_ptr(*our_path, sd_bus_message_get_path(m)))
3d7415f4
LP
1486 return 0;
1487
1488 if (arg_quiet && LOG_PRI(priority) >= LOG_INFO)
1489 return 0;
1490
1491 log_full(priority, "%s", line);
1492 return 0;
1493}
1494
1495static int match_transfer_removed(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
6adf7b5e 1496 const char **our_path = userdata, *path, *result;
3d7415f4
LP
1497 uint32_t id;
1498 int r;
1499
1500 assert(bus);
1501 assert(m);
6adf7b5e 1502 assert(our_path);
3d7415f4
LP
1503
1504 r = sd_bus_message_read(m, "uos", &id, &path, &result);
1505 if (r < 0) {
1506 bus_log_parse_error(r);
1507 return 0;
1508 }
1509
6adf7b5e 1510 if (!streq_ptr(*our_path, path))
3d7415f4
LP
1511 return 0;
1512
6adf7b5e
LP
1513 sd_event_exit(sd_bus_get_event(bus), !streq_ptr(result, "done"));
1514 return 0;
1515}
1516
1517static int transfer_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
1518 assert(s);
1519 assert(si);
1520
1521 if (!arg_quiet)
cc98b302 1522 log_info("Continuing download in the background. Use \"machinectl cancel-transfer %" PRIu32 "\" to abort transfer.", PTR_TO_UINT32(userdata));
6adf7b5e
LP
1523
1524 sd_event_exit(sd_event_source_get_event(s), EINTR);
3d7415f4
LP
1525 return 0;
1526}
1527
1528static int pull_image_common(sd_bus *bus, sd_bus_message *m) {
1529 _cleanup_bus_slot_unref_ sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL;
1530 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1531 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
6adf7b5e
LP
1532 _cleanup_event_unref_ sd_event* event = NULL;
1533 const char *path = NULL;
3d7415f4
LP
1534 uint32_t id;
1535 int r;
1536
1537 assert(bus);
1538 assert(m);
1539
1540 polkit_agent_open_if_enabled();
1541
6adf7b5e
LP
1542 r = sd_event_default(&event);
1543 if (r < 0)
1544 return log_error_errno(r, "Failed to get event loop: %m");
1545
1546 r = sd_bus_attach_event(bus, event, 0);
1547 if (r < 0)
1548 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1549
3d7415f4
LP
1550 r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
1551 if (r < 0)
1552 return bus_log_create_error(r);
1553
1554 r = sd_bus_add_match(
1555 bus,
1556 &slot_job_removed,
1557 "type='signal',"
1558 "sender='org.freedesktop.import1',"
1559 "interface='org.freedesktop.import1.Manager',"
1560 "member='TransferRemoved',"
1561 "path='/org/freedesktop/import1'",
6adf7b5e 1562 match_transfer_removed, &path);
3d7415f4
LP
1563 if (r < 0)
1564 return log_error_errno(r, "Failed to install match: %m");
1565
1566 r = sd_bus_add_match(
1567 bus,
1568 &slot_log_message,
1569 "type='signal',"
1570 "sender='org.freedesktop.import1',"
1571 "interface='org.freedesktop.import1.Transfer',"
1572 "member='LogMessage'",
6adf7b5e 1573 match_log_message, &path);
3d7415f4
LP
1574 if (r < 0)
1575 return log_error_errno(r, "Failed to install match: %m");
1576
1577 r = sd_bus_call(bus, m, 0, &error, &reply);
1578 if (r < 0) {
1579 log_error("Failed pull image: %s", bus_error_message(&error, -r));
1580 return r;
1581 }
1582
6adf7b5e 1583 r = sd_bus_message_read(reply, "uo", &id, &path);
3d7415f4
LP
1584 if (r < 0)
1585 return bus_log_parse_error(r);
1586
6adf7b5e 1587 sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
3d7415f4 1588
6adf7b5e
LP
1589 if (!arg_quiet)
1590 log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id);
3d7415f4 1591
6adf7b5e
LP
1592 sd_event_add_signal(event, NULL, SIGINT, transfer_signal_handler, UINT32_TO_PTR(id));
1593 sd_event_add_signal(event, NULL, SIGTERM, transfer_signal_handler, UINT32_TO_PTR(id));
1594
1595 r = sd_event_loop(event);
1596 if (r < 0)
1597 return log_error_errno(r, "Failed to run event loop: %m");
3d7415f4 1598
6adf7b5e 1599 return -r;
3d7415f4
LP
1600}
1601
1602static int pull_tar(int argc, char *argv[], void *userdata) {
1603 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
1604 _cleanup_free_ char *l = NULL, *ll = NULL;
1605 const char *local, *remote;
1606 sd_bus *bus = userdata;
1607 int r;
1608
1609 assert(bus);
1610
1611 remote = argv[1];
1612 if (!http_url_is_valid(remote)) {
1613 log_error("URL '%s' is not valid.", remote);
1614 return -EINVAL;
1615 }
1616
1617 if (argc >= 3)
1618 local = argv[2];
1619 else {
1620 r = import_url_last_component(remote, &l);
1621 if (r < 0)
1622 return log_error_errno(r, "Failed to get final component of URL: %m");
1623
1624 local = l;
1625 }
1626
1627 if (isempty(local) || streq(local, "-"))
1628 local = NULL;
1629
1630 if (local) {
1631 r = tar_strip_suffixes(local, &ll);
1632 if (r < 0)
1633 return log_error_errno(r, "Failed to strip tar suffixes: %m");
1634
1635 local = ll;
1636
1637 if (!machine_name_is_valid(local)) {
1638 log_error("Local name %s is not a suitable machine name.", local);
1639 return -EINVAL;
1640 }
1641 }
1642
1643 r = sd_bus_message_new_method_call(
1644 bus,
1645 &m,
1646 "org.freedesktop.import1",
1647 "/org/freedesktop/import1",
1648 "org.freedesktop.import1.Manager",
1649 "PullTar");
1650 if (r < 0)
1651 return bus_log_create_error(r);
1652
1653 r = sd_bus_message_append(
1654 m,
1655 "sssb",
1656 remote,
1657 local,
6e18cc9f 1658 import_verify_to_string(arg_verify),
3d7415f4
LP
1659 arg_force);
1660 if (r < 0)
1661 return bus_log_create_error(r);
1662
1663 return pull_image_common(bus, m);
1664}
1665
1666static int pull_raw(int argc, char *argv[], void *userdata) {
1667 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
1668 _cleanup_free_ char *l = NULL, *ll = NULL;
1669 const char *local, *remote;
1670 sd_bus *bus = userdata;
1671 int r;
1672
1673 assert(bus);
1674
1675 remote = argv[1];
1676 if (!http_url_is_valid(remote)) {
1677 log_error("URL '%s' is not valid.", remote);
1678 return -EINVAL;
1679 }
1680
1681 if (argc >= 3)
1682 local = argv[2];
1683 else {
1684 r = import_url_last_component(remote, &l);
1685 if (r < 0)
1686 return log_error_errno(r, "Failed to get final component of URL: %m");
1687
1688 local = l;
1689 }
1690
1691 if (isempty(local) || streq(local, "-"))
1692 local = NULL;
1693
1694 if (local) {
1695 r = raw_strip_suffixes(local, &ll);
1696 if (r < 0)
1697 return log_error_errno(r, "Failed to strip tar suffixes: %m");
1698
1699 local = ll;
1700
1701 if (!machine_name_is_valid(local)) {
1702 log_error("Local name %s is not a suitable machine name.", local);
1703 return -EINVAL;
1704 }
1705 }
1706
1707 r = sd_bus_message_new_method_call(
1708 bus,
1709 &m,
1710 "org.freedesktop.import1",
1711 "/org/freedesktop/import1",
1712 "org.freedesktop.import1.Manager",
1713 "PullRaw");
1714 if (r < 0)
1715 return bus_log_create_error(r);
1716
1717 r = sd_bus_message_append(
1718 m,
1719 "sssb",
1720 remote,
1721 local,
6e18cc9f 1722 import_verify_to_string(arg_verify),
3d7415f4
LP
1723 arg_force);
1724 if (r < 0)
1725 return bus_log_create_error(r);
1726
1727 return pull_image_common(bus, m);
1728}
1729
1730static int pull_dkr(int argc, char *argv[], void *userdata) {
1731 _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
3d7415f4
LP
1732 const char *local, *remote, *tag;
1733 sd_bus *bus = userdata;
1734 int r;
1735
56e6c2ab 1736 if (arg_verify != IMPORT_VERIFY_NO) {
3d7415f4
LP
1737 log_error("Imports from DKR do not support image verification, please pass --verify=no.");
1738 return -EINVAL;
1739 }
1740
1741 remote = argv[1];
1742 tag = strchr(remote, ':');
1743 if (tag) {
1744 remote = strndupa(remote, tag - remote);
1745 tag++;
1746 }
1747
1748 if (!dkr_name_is_valid(remote)) {
1749 log_error("DKR name '%s' is invalid.", remote);
1750 return -EINVAL;
1751 }
1752 if (tag && !dkr_tag_is_valid(tag)) {
1753 log_error("DKR tag '%s' is invalid.", remote);
1754 return -EINVAL;
1755 }
1756
1757 if (argc >= 3)
1758 local = argv[2];
1759 else {
1760 local = strchr(remote, '/');
1761 if (local)
1762 local++;
1763 else
1764 local = remote;
1765 }
1766
1767 if (isempty(local) || streq(local, "-"))
1768 local = NULL;
1769
1770 if (local) {
1771 if (!machine_name_is_valid(local)) {
1772 log_error("Local name %s is not a suitable machine name.", local);
1773 return -EINVAL;
1774 }
1775 }
1776
1777 r = sd_bus_message_new_method_call(
1778 bus,
1779 &m,
1780 "org.freedesktop.import1",
1781 "/org/freedesktop/import1",
1782 "org.freedesktop.import1.Manager",
1783 "PullDkr");
1784 if (r < 0)
1785 return bus_log_create_error(r);
1786
1787 r = sd_bus_message_append(
1788 m,
1789 "sssssb",
1790 arg_dkr_index_url,
1791 remote,
1792 tag,
1793 local,
6e18cc9f 1794 import_verify_to_string(arg_verify),
3d7415f4
LP
1795 arg_force);
1796 if (r < 0)
1797 return bus_log_create_error(r);
1798
1799 return pull_image_common(bus, m);
1800}
1801
1802typedef struct TransferInfo {
1803 uint32_t id;
1804 const char *type;
1805 const char *remote;
1806 const char *local;
7079cfef 1807 double progress;
3d7415f4
LP
1808} TransferInfo;
1809
1810static int compare_transfer_info(const void *a, const void *b) {
1811 const TransferInfo *x = a, *y = b;
1812
1813 return strcmp(x->local, y->local);
1814}
1815
1816static int list_transfers(int argc, char *argv[], void *userdata) {
1817 size_t max_type = strlen("TYPE"), max_local = strlen("LOCAL"), max_remote = strlen("REMOTE");
1818 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1819 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1820 _cleanup_free_ TransferInfo *transfers = NULL;
1821 size_t n_transfers = 0, n_allocated = 0, j;
1822 const char *type, *remote, *local, *object;
1823 sd_bus *bus = userdata;
1824 uint32_t id, max_id = 0;
7079cfef 1825 double progress;
3d7415f4
LP
1826 int r;
1827
1828 pager_open_if_enabled();
1829
1830 r = sd_bus_call_method(
1831 bus,
1832 "org.freedesktop.import1",
1833 "/org/freedesktop/import1",
1834 "org.freedesktop.import1.Manager",
1835 "ListTransfers",
1836 &error,
1837 &reply,
1838 NULL);
1839 if (r < 0) {
1840 log_error("Could not get transfers: %s", bus_error_message(&error, -r));
1841 return r;
1842 }
1843
7079cfef 1844 r = sd_bus_message_enter_container(reply, 'a', "(usssdo)");
3d7415f4
LP
1845 if (r < 0)
1846 return bus_log_parse_error(r);
1847
7079cfef 1848 while ((r = sd_bus_message_read(reply, "(usssdo)", &id, &type, &remote, &local, &progress, &object)) > 0) {
3d7415f4
LP
1849 size_t l;
1850
1851 if (!GREEDY_REALLOC(transfers, n_allocated, n_transfers + 1))
1852 return log_oom();
1853
1854 transfers[n_transfers].id = id;
1855 transfers[n_transfers].type = type;
1856 transfers[n_transfers].remote = remote;
1857 transfers[n_transfers].local = local;
7079cfef 1858 transfers[n_transfers].progress = progress;
3d7415f4
LP
1859
1860 l = strlen(type);
1861 if (l > max_type)
1862 max_type = l;
1863
1864 l = strlen(remote);
1865 if (l > max_remote)
1866 max_remote = l;
1867
1868 l = strlen(local);
1869 if (l > max_local)
1870 max_local = l;
1871
1872 if (id > max_id)
1873 max_id = id;
1874
1875 n_transfers ++;
1876 }
1877 if (r < 0)
1878 return bus_log_parse_error(r);
1879
1880 r = sd_bus_message_exit_container(reply);
1881 if (r < 0)
1882 return bus_log_parse_error(r);
1883
1884 qsort_safe(transfers, n_transfers, sizeof(TransferInfo), compare_transfer_info);
1885
1886 if (arg_legend)
7079cfef 1887 printf("%-*s %-*s %-*s %-*s %-*s\n",
3d7415f4 1888 (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID",
7079cfef 1889 (int) 7, "PERCENT",
3d7415f4
LP
1890 (int) max_type, "TYPE",
1891 (int) max_local, "LOCAL",
1892 (int) max_remote, "REMOTE");
1893
1894 for (j = 0; j < n_transfers; j++)
7079cfef 1895 printf("%*" PRIu32 " %*u%% %-*s %-*s %-*s\n",
3d7415f4 1896 (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), transfers[j].id,
7079cfef 1897 (int) 6, (unsigned) (transfers[j].progress * 100),
3d7415f4
LP
1898 (int) max_type, transfers[j].type,
1899 (int) max_local, transfers[j].local,
1900 (int) max_remote, transfers[j].remote);
1901
1902 if (arg_legend)
1903 printf("\n%zu transfers listed.\n", n_transfers);
1904
1905 return 0;
1906}
1907
1908static int cancel_transfer(int argc, char *argv[], void *userdata) {
1909 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1910 sd_bus *bus = userdata;
1911 int r, i;
1912
1913 assert(bus);
1914
1915 polkit_agent_open_if_enabled();
1916
1917 for (i = 1; i < argc; i++) {
1918 uint32_t id;
1919
1920 r = safe_atou32(argv[i], &id);
1921 if (r < 0)
1922 return log_error_errno(r, "Failed to parse transfer id: %s", argv[i]);
1923
1924 r = sd_bus_call_method(
1925 bus,
1926 "org.freedesktop.import1",
1927 "/org/freedesktop/import1",
1928 "org.freedesktop.import1.Manager",
1929 "CancelTransfer",
1930 &error,
1931 NULL,
1932 "u", id);
1933 if (r < 0) {
1934 log_error("Could not cancel transfer: %s", bus_error_message(&error, -r));
1935 return r;
1936 }
1937 }
1938
1939 return 0;
1940}
1941
56159e0d
LP
1942static int help(int argc, char *argv[], void *userdata) {
1943
1ee306e1 1944 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
f2cbe59e
LP
1945 "Send control commands to or query the virtual machine and container\n"
1946 "registration manager.\n\n"
1947 " -h --help Show this help\n"
1948 " --version Show package version\n"
1949 " --no-pager Do not pipe output into a pager\n"
1950 " --no-legend Do not show the headers and footers\n"
acf97e21 1951 " --no-ask-password Do not ask for system passwords\n"
f2cbe59e
LP
1952 " -H --host=[USER@]HOST Operate on remote host\n"
1953 " -M --machine=CONTAINER Operate on local container\n"
1954 " -p --property=NAME Show only properties by this name\n"
d8f52ed2 1955 " -q --quiet Suppress output\n"
f2cbe59e
LP
1956 " -a --all Show all properties, including empty ones\n"
1957 " -l --full Do not ellipsize output\n"
1958 " --kill-who=WHO Who to send signal to\n"
1959 " -s --signal=SIGNAL Which signal to send\n"
1960 " --read-only Create read-only bind mount\n"
8b0cc9a3
LP
1961 " --mkdir Create directory before bind mounting, if missing\n"
1962 " -n --lines=INTEGER Number of journal entries to show\n"
1963 " -o --output=STRING Change journal output mode (short,\n"
1964 " short-monotonic, verbose, export, json,\n"
3d7415f4 1965 " json-pretty, json-sse, cat)\n"
7f444afa
LP
1966 " --verify=MODE Verification mode for downloaded images (no,\n"
1967 " checksum, signature)\n"
3d7415f4
LP
1968 " --force Download image even if already exists\n"
1969 " --dkr-index-url=URL Specify the index URL to use for DKR image\n"
1970 " downloads\n\n"
cd61c3bf 1971 "Machine Commands:\n"
f2cbe59e 1972 " list List running VMs and containers\n"
fefdc04b 1973 " status NAME... Show VM/container details\n"
f2cbe59e 1974 " show NAME... Show properties of one or more VMs/containers\n"
ebd011d9 1975 " start NAME... Start container as a service\n"
e45fc5e7 1976 " login NAME Get a login prompt on a container\n"
d8f52ed2
LP
1977 " enable NAME... Enable automatic container start at boot\n"
1978 " disable NAME... Disable automatic container start at boot\n"
f2cbe59e
LP
1979 " poweroff NAME... Power off one or more containers\n"
1980 " reboot NAME... Reboot one or more containers\n"
f2cbe59e 1981 " terminate NAME... Terminate one or more VMs/containers\n"
ebd93cb6 1982 " kill NAME... Send signal to processes of a VM/container\n"
f2cbe59e 1983 " copy-to NAME PATH [PATH] Copy files from the host to a container\n"
ebd93cb6
LP
1984 " copy-from NAME PATH [PATH] Copy files from a container to the host\n"
1985 " bind NAME PATH [PATH] Bind mount a path from the host into a container\n\n"
fefdc04b 1986 "Image Commands:\n"
56b921c3 1987 " list-images Show available container and VM images\n"
fefdc04b 1988 " image-status NAME... Show image details\n"
08682124 1989 " show-image NAME... Show properties of image\n"
ebd93cb6
LP
1990 " clone NAME NAME Clone an image\n"
1991 " rename NAME NAME Rename an image\n"
1992 " read-only NAME [BOOL] Mark or unmark image read-only\n"
3d7415f4 1993 " remove NAME... Remove an image\n\n"
b5b38b41
LP
1994 "Image Transfer Commands:\n"
1995 " pull-tar URL [NAME] Download a TAR container image\n"
1996 " pull-raw URL [NAME] Download a RAW container or VM image\n"
1997 " pull-dkr REMOTE [NAME] Download a DKR container image\n"
1998 " list-transfers Show list of downloads in progress\n"
3d7415f4 1999 " cancel-transfer Cancel a download\n"
f7621db0 2000 , program_invocation_short_name);
56159e0d
LP
2001
2002 return 0;
1ee306e1
LP
2003}
2004
2005static int parse_argv(int argc, char *argv[]) {
2006
2007 enum {
2008 ARG_VERSION = 0x100,
2009 ARG_NO_PAGER,
e56056e9 2010 ARG_NO_LEGEND,
1ee306e1 2011 ARG_KILL_WHO,
785890ac
LP
2012 ARG_READ_ONLY,
2013 ARG_MKDIR,
acf97e21 2014 ARG_NO_ASK_PASSWORD,
3d7415f4
LP
2015 ARG_VERIFY,
2016 ARG_FORCE,
2017 ARG_DKR_INDEX_URL,
1ee306e1
LP
2018 };
2019
2020 static const struct option options[] = {
2021 { "help", no_argument, NULL, 'h' },
2022 { "version", no_argument, NULL, ARG_VERSION },
2023 { "property", required_argument, NULL, 'p' },
2024 { "all", no_argument, NULL, 'a' },
2025 { "full", no_argument, NULL, 'l' },
2026 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
e56056e9 2027 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
1ee306e1
LP
2028 { "kill-who", required_argument, NULL, ARG_KILL_WHO },
2029 { "signal", required_argument, NULL, 's' },
2030 { "host", required_argument, NULL, 'H' },
a7893c6b 2031 { "machine", required_argument, NULL, 'M' },
785890ac
LP
2032 { "read-only", no_argument, NULL, ARG_READ_ONLY },
2033 { "mkdir", no_argument, NULL, ARG_MKDIR },
d8f52ed2 2034 { "quiet", no_argument, NULL, 'q' },
8b0cc9a3
LP
2035 { "lines", required_argument, NULL, 'n' },
2036 { "output", required_argument, NULL, 'o' },
acf97e21 2037 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
3d7415f4
LP
2038 { "verify", required_argument, NULL, ARG_VERIFY },
2039 { "force", no_argument, NULL, ARG_FORCE },
2040 { "dkr-index-url", required_argument, NULL, ARG_DKR_INDEX_URL },
eb9da376 2041 {}
1ee306e1
LP
2042 };
2043
a7893c6b 2044 int c, r;
1ee306e1
LP
2045
2046 assert(argc >= 0);
2047 assert(argv);
2048
8b0cc9a3 2049 while ((c = getopt_long(argc, argv, "hp:als:H:M:qn:o:", options, NULL)) >= 0)
1ee306e1
LP
2050
2051 switch (c) {
2052
2053 case 'h':
56159e0d 2054 return help(0, NULL, NULL);
1ee306e1
LP
2055
2056 case ARG_VERSION:
2057 puts(PACKAGE_STRING);
2058 puts(SYSTEMD_FEATURES);
2059 return 0;
2060
a7893c6b
LP
2061 case 'p':
2062 r = strv_extend(&arg_property, optarg);
2063 if (r < 0)
2064 return log_oom();
1ee306e1
LP
2065
2066 /* If the user asked for a particular
2067 * property, show it to him, even if it is
2068 * empty. */
2069 arg_all = true;
2070 break;
1ee306e1
LP
2071
2072 case 'a':
2073 arg_all = true;
2074 break;
2075
2076 case 'l':
2077 arg_full = true;
2078 break;
2079
8b0cc9a3
LP
2080 case 'n':
2081 if (safe_atou(optarg, &arg_lines) < 0) {
2082 log_error("Failed to parse lines '%s'", optarg);
2083 return -EINVAL;
2084 }
2085 break;
2086
2087 case 'o':
2088 arg_output = output_mode_from_string(optarg);
2089 if (arg_output < 0) {
2090 log_error("Unknown output '%s'.", optarg);
2091 return -EINVAL;
2092 }
2093 break;
2094
1ee306e1
LP
2095 case ARG_NO_PAGER:
2096 arg_no_pager = true;
2097 break;
2098
e56056e9
TA
2099 case ARG_NO_LEGEND:
2100 arg_legend = false;
2101 break;
2102
1ee306e1
LP
2103 case ARG_KILL_WHO:
2104 arg_kill_who = optarg;
2105 break;
2106
2107 case 's':
2108 arg_signal = signal_from_string_try_harder(optarg);
2109 if (arg_signal < 0) {
2110 log_error("Failed to parse signal string %s.", optarg);
2111 return -EINVAL;
2112 }
2113 break;
2114
acf97e21
LP
2115 case ARG_NO_ASK_PASSWORD:
2116 arg_ask_password = false;
2117 break;
2118
1ee306e1 2119 case 'H':
d21ed1ea 2120 arg_transport = BUS_TRANSPORT_REMOTE;
a7893c6b
LP
2121 arg_host = optarg;
2122 break;
2123
2124 case 'M':
de33fc62 2125 arg_transport = BUS_TRANSPORT_MACHINE;
a7893c6b 2126 arg_host = optarg;
1ee306e1
LP
2127 break;
2128
785890ac
LP
2129 case ARG_READ_ONLY:
2130 arg_read_only = true;
2131 break;
2132
2133 case ARG_MKDIR:
2134 arg_mkdir = true;
2135 break;
2136
d8f52ed2
LP
2137 case 'q':
2138 arg_quiet = true;
2139 break;
2140
3d7415f4 2141 case ARG_VERIFY:
6e18cc9f
LP
2142 arg_verify = import_verify_from_string(optarg);
2143 if (arg_verify < 0) {
2144 log_error("Failed to parse --verify= setting: %s", optarg);
2145 return -EINVAL;
2146 }
3d7415f4
LP
2147 break;
2148
2149 case ARG_FORCE:
2150 arg_force = true;
2151 break;
2152
2153 case ARG_DKR_INDEX_URL:
2154 if (!http_url_is_valid(optarg)) {
2155 log_error("Index URL is invalid: %s", optarg);
2156 return -EINVAL;
2157 }
2158
2159 arg_dkr_index_url = optarg;
2160 break;
2161
1ee306e1
LP
2162 case '?':
2163 return -EINVAL;
2164
2165 default:
eb9da376 2166 assert_not_reached("Unhandled option");
1ee306e1 2167 }
1ee306e1
LP
2168
2169 return 1;
2170}
2171
56159e0d
LP
2172static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
2173
2174 static const Verb verbs[] = {
3d7415f4
LP
2175 { "help", VERB_ANY, VERB_ANY, 0, help },
2176 { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines },
2177 { "list-images", VERB_ANY, 1, 0, list_images },
2178 { "status", 2, VERB_ANY, 0, show_machine },
2179 { "image-status", 2, VERB_ANY, 0, show_image },
2180 { "show", VERB_ANY, VERB_ANY, 0, show_machine },
2181 { "show-image", VERB_ANY, VERB_ANY, 0, show_image },
2182 { "terminate", 2, VERB_ANY, 0, terminate_machine },
2183 { "reboot", 2, VERB_ANY, 0, reboot_machine },
2184 { "poweroff", 2, VERB_ANY, 0, poweroff_machine },
2185 { "kill", 2, VERB_ANY, 0, kill_machine },
2186 { "login", 2, 2, 0, login_machine },
2187 { "bind", 3, 4, 0, bind_mount },
2188 { "copy-to", 3, 4, 0, copy_files },
2189 { "copy-from", 3, 4, 0, copy_files },
2190 { "remove", 2, VERB_ANY, 0, remove_image },
2191 { "rename", 3, 3, 0, rename_image },
2192 { "clone", 3, 3, 0, clone_image },
2193 { "read-only", 2, 3, 0, read_only_image },
2194 { "start", 2, VERB_ANY, 0, start_machine },
2195 { "enable", 2, VERB_ANY, 0, enable_machine },
2196 { "disable", 2, VERB_ANY, 0, enable_machine },
2197 { "pull-tar", 2, 3, 0, pull_tar },
2198 { "pull-raw", 2, 3, 0, pull_raw },
2199 { "pull-dkr", 2, 3, 0, pull_dkr },
2200 { "list-transfers", VERB_ANY, 1, 0, list_transfers },
2201 { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer },
56159e0d 2202 {}
1ee306e1
LP
2203 };
2204
56159e0d 2205 return dispatch_verb(argc, argv, verbs, bus);
1ee306e1
LP
2206}
2207
2208int main(int argc, char*argv[]) {
24996861 2209 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
84f6181c 2210 int r;
1ee306e1
LP
2211
2212 setlocale(LC_ALL, "");
2213 log_parse_environment();
2214 log_open();
2215
2216 r = parse_argv(argc, argv);
84f6181c 2217 if (r <= 0)
1ee306e1 2218 goto finish;
1ee306e1 2219
d21ed1ea 2220 r = bus_open_transport(arg_transport, arg_host, false, &bus);
a1da8583 2221 if (r < 0) {
da927ba9 2222 log_error_errno(r, "Failed to create bus connection: %m");
a1da8583
TG
2223 goto finish;
2224 }
1ee306e1 2225
56159e0d 2226 r = machinectl_main(argc, argv, bus);
1ee306e1
LP
2227
2228finish:
1ee306e1 2229 pager_close();
acf97e21 2230 polkit_agent_close();
1ee306e1 2231
84f6181c
LP
2232 strv_free(arg_property);
2233
2234 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1ee306e1 2235}