]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/machine/machinectl.c
machined: make "machinectl copy-to" and "machinectl copy-from" server side operations
[thirdparty/systemd.git] / src / machine / machinectl.c
... / ...
CommitLineData
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2013 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <sys/socket.h>
23#include <unistd.h>
24#include <errno.h>
25#include <string.h>
26#include <getopt.h>
27#include <pwd.h>
28#include <locale.h>
29#include <fcntl.h>
30#include <netinet/in.h>
31#include <arpa/inet.h>
32#include <net/if.h>
33#include <sys/mount.h>
34
35#include "sd-bus.h"
36#include "log.h"
37#include "util.h"
38#include "macro.h"
39#include "pager.h"
40#include "spawn-polkit-agent.h"
41#include "bus-util.h"
42#include "bus-error.h"
43#include "build.h"
44#include "strv.h"
45#include "unit-name.h"
46#include "cgroup-show.h"
47#include "logs-show.h"
48#include "cgroup-util.h"
49#include "ptyfwd.h"
50#include "event-util.h"
51#include "path-util.h"
52#include "mkdir.h"
53#include "copy.h"
54#include "verbs.h"
55#include "import-util.h"
56
57static char **arg_property = NULL;
58static bool arg_all = false;
59static bool arg_full = false;
60static bool arg_no_pager = false;
61static bool arg_legend = true;
62static const char *arg_kill_who = NULL;
63static int arg_signal = SIGTERM;
64static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
65static char *arg_host = NULL;
66static bool arg_read_only = false;
67static bool arg_mkdir = false;
68static bool arg_quiet = false;
69static bool arg_ask_password = true;
70static unsigned arg_lines = 10;
71static OutputMode arg_output = OUTPUT_SHORT;
72static bool arg_force = false;
73static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
74static const char* arg_dkr_index_url = NULL;
75
76static void pager_open_if_enabled(void) {
77
78 if (arg_no_pager)
79 return;
80
81 pager_open(false);
82}
83
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
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
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
118static int list_machines(int argc, char *argv[], void *userdata) {
119
120 size_t max_name = strlen("MACHINE"), max_class = strlen("CLASS"), max_service = strlen("SERVICE");
121 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
122 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
123 _cleanup_free_ MachineInfo *machines = NULL;
124 const char *name, *class, *service, *object;
125 size_t n_machines = 0, n_allocated = 0, j;
126 sd_bus *bus = userdata;
127 int r;
128
129 assert(bus);
130
131 pager_open_if_enabled();
132
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,
141 NULL);
142 if (r < 0) {
143 log_error("Could not get machines: %s", bus_error_message(&error, -r));
144 return r;
145 }
146
147 r = sd_bus_message_enter_container(reply, 'a', "(ssso)");
148 if (r < 0)
149 return bus_log_parse_error(r);
150
151 while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) {
152 size_t l;
153
154 if (!GREEDY_REALLOC(machines, n_allocated, n_machines + 1))
155 return log_oom();
156
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 ++;
174 }
175 if (r < 0)
176 return bus_log_parse_error(r);
177
178 r = sd_bus_message_exit_container(reply);
179 if (r < 0)
180 return bus_log_parse_error(r);
181
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
196 if (arg_legend)
197 printf("\n%zu machines listed.\n", n_machines);
198
199 return 0;
200}
201
202typedef struct ImageInfo {
203 const char *name;
204 const char *type;
205 bool read_only;
206 usec_t crtime;
207 usec_t mtime;
208 uint64_t size;
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
217static int list_images(int argc, char *argv[], void *userdata) {
218
219 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
220 size_t max_name = strlen("NAME"), max_type = strlen("TYPE"), max_size = strlen("USAGE"), max_crtime = strlen("CREATED"), max_mtime = strlen("MODIFIED");
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;
225 sd_bus *bus = userdata;
226 uint64_t crtime, mtime, size;
227 int read_only, r;
228
229 assert(bus);
230
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
247 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)");
248 if (r < 0)
249 return bus_log_parse_error(r);
250
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)];
253 size_t l;
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;
264 images[n_images].crtime = crtime;
265 images[n_images].mtime = mtime;
266 images[n_images].size = size;
267
268 l = strlen(name);
269 if (l > max_name)
270 max_name = l;
271
272 l = strlen(type);
273 if (l > max_type)
274 max_type = l;
275
276 if (crtime != 0) {
277 l = strlen(strna(format_timestamp(buf, sizeof(buf), crtime)));
278 if (l > max_crtime)
279 max_crtime = l;
280 }
281
282 if (mtime != 0) {
283 l = strlen(strna(format_timestamp(buf, sizeof(buf), mtime)));
284 if (l > max_mtime)
285 max_mtime = l;
286 }
287
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
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)
306 printf("%-*s %-*s %-3s %-*s %-*s %-*s\n",
307 (int) max_name, "NAME",
308 (int) max_type, "TYPE",
309 "RO",
310 (int) max_size, "USAGE",
311 (int) max_crtime, "CREATED",
312 (int) max_mtime, "MODIFIED");
313
314 for (j = 0; j < n_images; j++) {
315 char crtime_buf[FORMAT_TIMESTAMP_MAX], mtime_buf[FORMAT_TIMESTAMP_MAX], size_buf[FORMAT_BYTES_MAX];
316
317 printf("%-*s %-*s %s%-3s%s %-*s %-*s %-*s\n",
318 (int) max_name, images[j].name,
319 (int) max_type, images[j].type,
320 images[j].read_only ? ansi_highlight_red() : "", yes_no(images[j].read_only), images[j].read_only ? ansi_highlight_off() : "",
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)));
324 }
325
326 if (arg_legend)
327 printf("\n%zu images listed.\n", n_images);
328
329 return 0;
330}
331
332static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) {
333 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
334 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
335 _cleanup_free_ char *path = NULL;
336 const char *cgroup;
337 int r;
338 unsigned c;
339
340 assert(bus);
341 assert(unit);
342
343 if (arg_transport == BUS_TRANSPORT_REMOTE)
344 return 0;
345
346 path = unit_dbus_path_from_name(unit);
347 if (!path)
348 return log_oom();
349
350 r = sd_bus_get_property(
351 bus,
352 "org.freedesktop.systemd1",
353 path,
354 endswith(unit, ".scope") ? "org.freedesktop.systemd1.Scope" : "org.freedesktop.systemd1.Service",
355 "ControlGroup",
356 &error,
357 &reply,
358 "s");
359 if (r < 0) {
360 log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r));
361 return r;
362 }
363
364 r = sd_bus_message_read(reply, "s", &cgroup);
365 if (r < 0)
366 return bus_log_parse_error(r);
367
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
374 c = columns();
375 if (c > 18)
376 c -= 18;
377 else
378 c = 0;
379
380 show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, get_output_flags());
381 return 0;
382}
383
384static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) {
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
404 r = sd_bus_message_enter_container(reply, 'a', "(iay)");
405 if (r < 0)
406 return bus_log_parse_error(r);
407
408 while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) {
409 int family;
410 const void *a;
411 size_t sz;
412 char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)];
413
414 r = sd_bus_message_read(reply, "i", &family);
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
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);
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
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
487typedef struct MachineStatusInfo {
488 char *name;
489 sd_id128_t id;
490 char *class;
491 char *service;
492 char *unit;
493 char *root_directory;
494 pid_t leader;
495 struct dual_timestamp timestamp;
496 int *netif;
497 unsigned n_netif;
498} MachineStatusInfo;
499
500static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
501 char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
502 char since2[FORMAT_TIMESTAMP_MAX], *s2;
503 int ifi = -1;
504
505 assert(bus);
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
515 s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp.realtime);
516 s2 = format_timestamp(since2, sizeof(since2), i->timestamp.realtime);
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
545 if (i->root_directory)
546 printf("\t Root: %s\n", i->root_directory);
547
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,
572 "\t Address: ",
573 "\t ");
574
575 print_os_release(bus, i->name, "\t OS: ");
576
577 if (i->unit) {
578 printf("\t Unit: %s\n", i->unit);
579 show_unit_cgroup(bus, i->unit, i->leader);
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 }
596 }
597}
598
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;
609 if (r == 0)
610 return -EBADMSG;
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
620static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
621
622 static const struct bus_properties_map map[] = {
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 },
633 {}
634 };
635
636 MachineStatusInfo info = {};
637 int r;
638
639 assert(verb);
640 assert(bus);
641 assert(path);
642 assert(new_line);
643
644 r = bus_map_all_properties(bus,
645 "org.freedesktop.machine1",
646 path,
647 map,
648 &info);
649 if (r < 0)
650 return log_error_errno(r, "Could not get properties: %m");
651
652 if (*new_line)
653 printf("\n");
654 *new_line = true;
655
656 print_machine_status_info(bus, &info);
657
658 free(info.name);
659 free(info.class);
660 free(info.service);
661 free(info.unit);
662 free(info.root_directory);
663 free(info.netif);
664
665 return r;
666}
667
668static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line) {
669 int r;
670
671 assert(bus);
672 assert(path);
673 assert(new_line);
674
675 if (*new_line)
676 printf("\n");
677
678 *new_line = true;
679
680 r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all);
681 if (r < 0)
682 log_error_errno(r, "Could not get properties: %m");
683
684 return r;
685}
686
687static int show_machine(int argc, char *argv[], void *userdata) {
688
689 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
690 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
691 bool properties, new_line = false;
692 sd_bus *bus = userdata;
693 int r = 0, i;
694
695 assert(bus);
696
697 properties = !strstr(argv[0], "status");
698
699 pager_open_if_enabled();
700
701 if (properties && argc <= 1) {
702
703 /* If no argument is specified, inspect the manager
704 * itself */
705 r = show_machine_properties(bus, "/org/freedesktop/machine1", &new_line);
706 if (r < 0)
707 return r;
708 }
709
710 for (i = 1; i < argc; i++) {
711 const char *path = NULL;
712
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,
721 "s", argv[i]);
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);
728 if (r < 0)
729 return bus_log_parse_error(r);
730
731 if (properties)
732 r = show_machine_properties(bus, path, &new_line);
733 else
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;
747 uint64_t usage;
748 uint64_t limit;
749 uint64_t usage_exclusive;
750 uint64_t limit_exclusive;
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;
756 char bs[FORMAT_BYTES_MAX], *s3;
757 char bs_exclusive[FORMAT_BYTES_MAX], *s4;
758
759 assert(bus);
760 assert(i);
761
762 if (i->name) {
763 fputs(i->name, stdout);
764 putchar('\n');
765 }
766
767 if (i->type)
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);
780 if (s1 && s2)
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);
787 if (s1 && s2)
788 printf("\tModified: %s; %s\n", s2, s1);
789 else if (s2)
790 printf("\tModified: %s\n", s2);
791
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;
794 if (s3 && s4)
795 printf("\t Usage: %s (exclusive: %s)\n", s3, s4);
796 else if (s3)
797 printf("\t Usage: %s\n", s3);
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);
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[] = {
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) },
816 { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) },
817 { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) },
818 { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) },
819 { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) },
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(
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]);
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);
919 }
920
921 return r;
922}
923
924static int kill_machine(int argc, char *argv[], void *userdata) {
925 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
926 sd_bus *bus = userdata;
927 int r, i;
928
929 assert(bus);
930
931 polkit_agent_open_if_enabled();
932
933 if (!arg_kill_who)
934 arg_kill_who = "all";
935
936 for (i = 1; i < argc; i++) {
937 r = sd_bus_call_method(
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);
946 if (r < 0) {
947 log_error("Could not kill machine: %s", bus_error_message(&error, -r));
948 return r;
949 }
950 }
951
952 return 0;
953}
954
955static int reboot_machine(int argc, char *argv[], void *userdata) {
956 arg_kill_who = "leader";
957 arg_signal = SIGINT; /* sysvinit + systemd */
958
959 return kill_machine(argc, argv, userdata);
960}
961
962static int poweroff_machine(int argc, char *argv[], void *userdata) {
963 arg_kill_who = "leader";
964 arg_signal = SIGRTMIN+4; /* only systemd */
965
966 return kill_machine(argc, argv, userdata);
967}
968
969static int terminate_machine(int argc, char *argv[], void *userdata) {
970 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
971 sd_bus *bus = userdata;
972 int i;
973
974 assert(bus);
975
976 polkit_agent_open_if_enabled();
977
978 for (i = 1; i < argc; i++) {
979 int r;
980
981 r = sd_bus_call_method(
982 bus,
983 "org.freedesktop.machine1",
984 "/org/freedesktop/machine1",
985 "org.freedesktop.machine1.Manager",
986 "TerminateMachine",
987 &error,
988 NULL,
989 "s", argv[i]);
990 if (r < 0) {
991 log_error("Could not terminate machine: %s", bus_error_message(&error, -r));
992 return r;
993 }
994 }
995
996 return 0;
997}
998
999static int copy_files(int argc, char *argv[], void *userdata) {
1000 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1001 sd_bus *bus = userdata;
1002 bool copy_from;
1003 int r;
1004
1005 assert(bus);
1006
1007 copy_from = streq(argv[0], "copy-from");
1008
1009 r = sd_bus_call_method(
1010 bus,
1011 "org.freedesktop.machine1",
1012 "/org/freedesktop/machine1",
1013 "org.freedesktop.machine1.Manager",
1014 copy_from ? "CopyFromMachine" : "CopyToMachine",
1015 &error,
1016 NULL,
1017 "sss",
1018 argv[1],
1019 argv[2],
1020 argv[3]);
1021 if (r < 0) {
1022 log_error("Failed to copy: %s", bus_error_message(&error, -r));
1023 return r;
1024 }
1025
1026 return 0;
1027}
1028
1029static int bind_mount(int argc, char *argv[], void *userdata) {
1030 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1031 sd_bus *bus = userdata;
1032 int r;
1033
1034 assert(bus);
1035
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);
1050 if (r < 0) {
1051 log_error("Failed to bind mount: %s", bus_error_message(&error, -r));
1052 return r;
1053 }
1054
1055 return 0;
1056}
1057
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
1068 * exit on the next vhangup(), so that we still flush
1069 * out what might be queued and exit then. */
1070
1071 r = pty_forward_set_ignore_vhangup(*forward, false);
1072 if (r >= 0)
1073 return 0;
1074
1075 log_error_errno(r, "Failed to set ignore_vhangup flag: %m");
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
1083static int login_machine(int argc, char *argv[], void *userdata) {
1084 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1085 _cleanup_bus_message_unref_ sd_bus_message *m = NULL, *reply = NULL;
1086 _cleanup_bus_slot_unref_ sd_bus_slot *slot = NULL;
1087 _cleanup_(pty_forward_freep) PTYForward *forward = NULL;
1088 _cleanup_event_unref_ sd_event *event = NULL;
1089 int master = -1, r, ret = 0;
1090 sd_bus *bus = userdata;
1091 const char *pty, *match;
1092 char last_char = 0;
1093 bool machine_died;
1094
1095 assert(bus);
1096
1097 if (arg_transport != BUS_TRANSPORT_LOCAL &&
1098 arg_transport != BUS_TRANSPORT_MACHINE) {
1099 log_error("Login only supported on local machines.");
1100 return -ENOTSUP;
1101 }
1102
1103 polkit_agent_open_if_enabled();
1104
1105 r = sd_event_default(&event);
1106 if (r < 0)
1107 return log_error_errno(r, "Failed to get event loop: %m");
1108
1109 r = sd_bus_attach_event(bus, event, 0);
1110 if (r < 0)
1111 return log_error_errno(r, "Failed to attach bus to event loop: %m");
1112
1113 match = strjoina("type='signal',"
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
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
1135 r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
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);
1144 if (r < 0) {
1145 log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r));
1146 return r;
1147 }
1148
1149 r = sd_bus_message_read(reply, "hs", &master, &pty);
1150 if (r < 0)
1151 return bus_log_parse_error(r);
1152
1153 sigprocmask_many(SIG_BLOCK, SIGWINCH, SIGTERM, SIGINT, -1);
1154
1155 log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", argv[1]);
1156
1157 sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
1158 sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
1159
1160 r = pty_forward_new(event, master, true, &forward);
1161 if (r < 0)
1162 return log_error_errno(r, "Failed to create PTY forwarder: %m");
1163
1164 r = sd_event_loop(event);
1165 if (r < 0)
1166 return log_error_errno(r, "Failed to run event loop: %m");
1167
1168 pty_forward_get_last_char(forward, &last_char);
1169 machine_died = pty_forward_get_ignore_vhangup(forward) == 0;
1170
1171 forward = pty_forward_free(forward);
1172
1173 if (last_char != '\n')
1174 fputc('\n', stdout);
1175
1176 if (machine_died)
1177 log_info("Machine %s terminated.", argv[1]);
1178 else
1179 log_info("Connection to machine %s terminated.", argv[1]);
1180
1181 sd_event_get_exit_code(event, &ret);
1182 return ret;
1183}
1184
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;
1188 int r, i;
1189
1190 assert(bus);
1191
1192 polkit_agent_open_if_enabled();
1193
1194 for (i = 1; i < argc; i++) {
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
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
1218 polkit_agent_open_if_enabled();
1219
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
1242 polkit_agent_open_if_enabled();
1243
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
1274 polkit_agent_open_if_enabled();
1275
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
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
1301 polkit_agent_open_if_enabled();
1302
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
1335 r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
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
1358 r = bus_wait_for_jobs(w, arg_quiet);
1359 if (r < 0)
1360 return r;
1361
1362 return 0;
1363}
1364
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
1375 polkit_agent_open_if_enabled();
1376
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
1389 r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
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
1457 r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password);
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
1470static int match_log_message(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) {
1471 const char **our_path = userdata, *line;
1472 unsigned priority;
1473 int r;
1474
1475 assert(bus);
1476 assert(m);
1477 assert(our_path);
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
1485 if (!streq_ptr(*our_path, sd_bus_message_get_path(m)))
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) {
1496 const char **our_path = userdata, *path, *result;
1497 uint32_t id;
1498 int r;
1499
1500 assert(bus);
1501 assert(m);
1502 assert(our_path);
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
1510 if (!streq_ptr(*our_path, path))
1511 return 0;
1512
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)
1522 log_info("Continuing download in the background. Use \"machinectl cancel-transfer %" PRIu32 "\" to abort transfer.", PTR_TO_UINT32(userdata));
1523
1524 sd_event_exit(sd_event_source_get_event(s), EINTR);
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;
1532 _cleanup_event_unref_ sd_event* event = NULL;
1533 const char *path = NULL;
1534 uint32_t id;
1535 int r;
1536
1537 assert(bus);
1538 assert(m);
1539
1540 polkit_agent_open_if_enabled();
1541
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
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'",
1562 match_transfer_removed, &path);
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'",
1573 match_log_message, &path);
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
1583 r = sd_bus_message_read(reply, "uo", &id, &path);
1584 if (r < 0)
1585 return bus_log_parse_error(r);
1586
1587 sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1);
1588
1589 if (!arg_quiet)
1590 log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id);
1591
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");
1598
1599 return -r;
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,
1658 import_verify_to_string(arg_verify),
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,
1722 import_verify_to_string(arg_verify),
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;
1732 const char *local, *remote, *tag;
1733 sd_bus *bus = userdata;
1734 int r;
1735
1736 if (arg_verify != IMPORT_VERIFY_NO) {
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,
1794 import_verify_to_string(arg_verify),
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;
1807 double progress;
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;
1825 double progress;
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
1844 r = sd_bus_message_enter_container(reply, 'a', "(usssdo)");
1845 if (r < 0)
1846 return bus_log_parse_error(r);
1847
1848 while ((r = sd_bus_message_read(reply, "(usssdo)", &id, &type, &remote, &local, &progress, &object)) > 0) {
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;
1858 transfers[n_transfers].progress = progress;
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)
1887 printf("%-*s %-*s %-*s %-*s %-*s\n",
1888 (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID",
1889 (int) 7, "PERCENT",
1890 (int) max_type, "TYPE",
1891 (int) max_local, "LOCAL",
1892 (int) max_remote, "REMOTE");
1893
1894 for (j = 0; j < n_transfers; j++)
1895 printf("%*" PRIu32 " %*u%% %-*s %-*s %-*s\n",
1896 (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), transfers[j].id,
1897 (int) 6, (unsigned) (transfers[j].progress * 100),
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
1942static int help(int argc, char *argv[], void *userdata) {
1943
1944 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
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"
1951 " --no-ask-password Do not ask for system passwords\n"
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"
1955 " -q --quiet Suppress output\n"
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"
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"
1965 " json-pretty, json-sse, cat)\n"
1966 " --verify=MODE Verification mode for downloaded images (no,\n"
1967 " checksum, signature)\n"
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"
1971 "Machine Commands:\n"
1972 " list List running VMs and containers\n"
1973 " status NAME... Show VM/container details\n"
1974 " show NAME... Show properties of one or more VMs/containers\n"
1975 " start NAME... Start container as a service\n"
1976 " login NAME Get a login prompt on a container\n"
1977 " enable NAME... Enable automatic container start at boot\n"
1978 " disable NAME... Disable automatic container start at boot\n"
1979 " poweroff NAME... Power off one or more containers\n"
1980 " reboot NAME... Reboot one or more containers\n"
1981 " terminate NAME... Terminate one or more VMs/containers\n"
1982 " kill NAME... Send signal to processes of a VM/container\n"
1983 " copy-to NAME PATH [PATH] Copy files from the host to a container\n"
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"
1986 "Image Commands:\n"
1987 " list-images Show available container and VM images\n"
1988 " image-status NAME... Show image details\n"
1989 " show-image NAME... Show properties of image\n"
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"
1993 " remove NAME... Remove an image\n\n"
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"
1999 " cancel-transfer Cancel a download\n"
2000 , program_invocation_short_name);
2001
2002 return 0;
2003}
2004
2005static int parse_argv(int argc, char *argv[]) {
2006
2007 enum {
2008 ARG_VERSION = 0x100,
2009 ARG_NO_PAGER,
2010 ARG_NO_LEGEND,
2011 ARG_KILL_WHO,
2012 ARG_READ_ONLY,
2013 ARG_MKDIR,
2014 ARG_NO_ASK_PASSWORD,
2015 ARG_VERIFY,
2016 ARG_FORCE,
2017 ARG_DKR_INDEX_URL,
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 },
2027 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
2028 { "kill-who", required_argument, NULL, ARG_KILL_WHO },
2029 { "signal", required_argument, NULL, 's' },
2030 { "host", required_argument, NULL, 'H' },
2031 { "machine", required_argument, NULL, 'M' },
2032 { "read-only", no_argument, NULL, ARG_READ_ONLY },
2033 { "mkdir", no_argument, NULL, ARG_MKDIR },
2034 { "quiet", no_argument, NULL, 'q' },
2035 { "lines", required_argument, NULL, 'n' },
2036 { "output", required_argument, NULL, 'o' },
2037 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
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 },
2041 {}
2042 };
2043
2044 int c, r;
2045
2046 assert(argc >= 0);
2047 assert(argv);
2048
2049 while ((c = getopt_long(argc, argv, "hp:als:H:M:qn:o:", options, NULL)) >= 0)
2050
2051 switch (c) {
2052
2053 case 'h':
2054 return help(0, NULL, NULL);
2055
2056 case ARG_VERSION:
2057 puts(PACKAGE_STRING);
2058 puts(SYSTEMD_FEATURES);
2059 return 0;
2060
2061 case 'p':
2062 r = strv_extend(&arg_property, optarg);
2063 if (r < 0)
2064 return log_oom();
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;
2071
2072 case 'a':
2073 arg_all = true;
2074 break;
2075
2076 case 'l':
2077 arg_full = true;
2078 break;
2079
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
2095 case ARG_NO_PAGER:
2096 arg_no_pager = true;
2097 break;
2098
2099 case ARG_NO_LEGEND:
2100 arg_legend = false;
2101 break;
2102
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
2115 case ARG_NO_ASK_PASSWORD:
2116 arg_ask_password = false;
2117 break;
2118
2119 case 'H':
2120 arg_transport = BUS_TRANSPORT_REMOTE;
2121 arg_host = optarg;
2122 break;
2123
2124 case 'M':
2125 arg_transport = BUS_TRANSPORT_MACHINE;
2126 arg_host = optarg;
2127 break;
2128
2129 case ARG_READ_ONLY:
2130 arg_read_only = true;
2131 break;
2132
2133 case ARG_MKDIR:
2134 arg_mkdir = true;
2135 break;
2136
2137 case 'q':
2138 arg_quiet = true;
2139 break;
2140
2141 case ARG_VERIFY:
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 }
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
2162 case '?':
2163 return -EINVAL;
2164
2165 default:
2166 assert_not_reached("Unhandled option");
2167 }
2168
2169 return 1;
2170}
2171
2172static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
2173
2174 static const Verb verbs[] = {
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 },
2202 {}
2203 };
2204
2205 return dispatch_verb(argc, argv, verbs, bus);
2206}
2207
2208int main(int argc, char*argv[]) {
2209 _cleanup_bus_close_unref_ sd_bus *bus = NULL;
2210 int r;
2211
2212 setlocale(LC_ALL, "");
2213 log_parse_environment();
2214 log_open();
2215
2216 r = parse_argv(argc, argv);
2217 if (r <= 0)
2218 goto finish;
2219
2220 r = bus_open_transport(arg_transport, arg_host, false, &bus);
2221 if (r < 0) {
2222 log_error_errno(r, "Failed to create bus connection: %m");
2223 goto finish;
2224 }
2225
2226 r = machinectl_main(argc, argv, bus);
2227
2228finish:
2229 pager_close();
2230 polkit_agent_close();
2231
2232 strv_free(arg_property);
2233
2234 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
2235}