]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/import/importctl.c
importd: make keeping pristine copy of downloaded images optional
[thirdparty/systemd.git] / src / import / importctl.c
CommitLineData
1db33ce5
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <getopt.h>
4
5#include "sd-bus.h"
6
7#include "alloc-util.h"
8#include "build.h"
9#include "bus-error.h"
10#include "bus-locator.h"
11#include "bus-util.h"
67c7ee11 12#include "discover-image.h"
1db33ce5
LP
13#include "fd-util.h"
14#include "format-table.h"
15#include "hostname-util.h"
67c7ee11 16#include "import-common.h"
1db33ce5
LP
17#include "import-util.h"
18#include "locale-util.h"
19#include "log.h"
20#include "macro.h"
21#include "main-func.h"
67c7ee11 22#include "os-util.h"
1db33ce5 23#include "pager.h"
8229e226 24#include "parse-argument.h"
1db33ce5
LP
25#include "parse-util.h"
26#include "path-util.h"
27#include "pretty-print.h"
28#include "signal-util.h"
29#include "sort-util.h"
30#include "spawn-polkit-agent.h"
31#include "string-table.h"
32#include "verbs.h"
33#include "web-util.h"
34
35static PagerFlags arg_pager_flags = 0;
36static bool arg_legend = true;
37static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
38static const char *arg_host = NULL;
67c7ee11 39static ImportFlags arg_import_flags = 0;
b146afc4 40static ImportFlags arg_import_flags_mask = 0; /* Indicates which flags have been explicitly set to on or to off */
1db33ce5
LP
41static bool arg_quiet = false;
42static bool arg_ask_password = true;
1db33ce5
LP
43static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
44static const char* arg_format = NULL;
8229e226 45static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
67c7ee11
LP
46static ImageClass arg_image_class = _IMAGE_CLASS_INVALID;
47
48static int settle_image_class(void) {
49
50 if (arg_image_class < 0) {
51 _cleanup_free_ char *j = NULL;
52
53 for (ImageClass class = 0; class < _IMAGE_CLASS_MAX; class++)
54 if (strextendf_with_separator(&j, ", ", "%s (downloads to %s/)",
55 image_class_to_string(class),
56 image_root_to_string(class)) < 0)
57 return log_oom();
58
59 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
60 "No image class specified, retry with --class= set to one of: %s.", j);
61 }
62
b146afc4
LP
63 /* Keep the original pristine downloaded file as a copy only when dealing with machine images,
64 * because unlike sysext/confext/portable they are typically modified during runtime. */
65 if (!FLAGS_SET(arg_import_flags_mask, IMPORT_PULL_KEEP_DOWNLOAD))
66 SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, arg_image_class == IMAGE_MACHINE);
67
67c7ee11
LP
68 return 0;
69}
1db33ce5
LP
70
71static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) {
72 const char **our_path = userdata, *line;
73 unsigned priority;
74 int r;
75
76 assert(m);
77 assert(our_path);
78
79 r = sd_bus_message_read(m, "us", &priority, &line);
80 if (r < 0) {
81 bus_log_parse_error(r);
82 return 0;
83 }
84
85 if (!streq_ptr(*our_path, sd_bus_message_get_path(m)))
86 return 0;
87
88 if (arg_quiet && LOG_PRI(priority) >= LOG_INFO)
89 return 0;
90
91 log_full(priority, "%s", line);
92 return 0;
93}
94
95static int match_transfer_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) {
96 const char **our_path = userdata, *path, *result;
97 uint32_t id;
98 int r;
99
100 assert(m);
101 assert(our_path);
102
103 r = sd_bus_message_read(m, "uos", &id, &path, &result);
104 if (r < 0) {
105 bus_log_parse_error(r);
106 return 0;
107 }
108
109 if (!streq_ptr(*our_path, path))
110 return 0;
111
112 sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), !streq_ptr(result, "done"));
113 return 0;
114}
115
116static int transfer_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
117 assert(s);
118 assert(si);
119
120 if (!arg_quiet)
121 log_info("Continuing download in the background. Use \"%s cancel-transfer %" PRIu32 "\" to abort transfer.",
122 program_invocation_short_name,
123 PTR_TO_UINT32(userdata));
124
125 sd_event_exit(sd_event_source_get_event(s), EINTR);
126 return 0;
127}
128
129static int transfer_image_common(sd_bus *bus, sd_bus_message *m) {
130 _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL;
131 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
132 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
133 _cleanup_(sd_event_unrefp) sd_event* event = NULL;
134 const char *path = NULL;
135 uint32_t id;
136 int r;
137
138 assert(bus);
139 assert(m);
140
141 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
142
143 r = sd_event_default(&event);
144 if (r < 0)
145 return log_error_errno(r, "Failed to get event loop: %m");
146
147 r = sd_bus_attach_event(bus, event, 0);
148 if (r < 0)
149 return log_error_errno(r, "Failed to attach bus to event loop: %m");
150
151 r = bus_match_signal_async(
152 bus,
153 &slot_job_removed,
154 bus_import_mgr,
155 "TransferRemoved",
156 match_transfer_removed, NULL, &path);
157 if (r < 0)
158 return log_error_errno(r, "Failed to request match: %m");
159
160 r = sd_bus_match_signal_async(
161 bus,
162 &slot_log_message,
163 "org.freedesktop.import1",
164 NULL,
165 "org.freedesktop.import1.Transfer",
166 "LogMessage",
167 match_log_message, NULL, &path);
168 if (r < 0)
169 return log_error_errno(r, "Failed to request match: %m");
170
171 r = sd_bus_call(bus, m, 0, &error, &reply);
172 if (r < 0)
173 return log_error_errno(r, "Failed to transfer image: %s", bus_error_message(&error, r));
174
175 r = sd_bus_message_read(reply, "uo", &id, &path);
176 if (r < 0)
177 return bus_log_parse_error(r);
178
1db33ce5
LP
179 if (!arg_quiet)
180 log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id);
181
663a15e7
LP
182 (void) sd_event_add_signal(event, NULL, SIGINT|SD_EVENT_SIGNAL_PROCMASK, transfer_signal_handler, UINT32_TO_PTR(id));
183 (void) sd_event_add_signal(event, NULL, SIGTERM|SD_EVENT_SIGNAL_PROCMASK, transfer_signal_handler, UINT32_TO_PTR(id));
1db33ce5
LP
184
185 r = sd_event_loop(event);
186 if (r < 0)
187 return log_error_errno(r, "Failed to run event loop: %m");
188
189 return -r;
190}
191
192static int import_tar(int argc, char *argv[], void *userdata) {
193 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
194 _cleanup_free_ char *ll = NULL, *fn = NULL;
195 const char *local = NULL, *path = NULL;
196 _cleanup_close_ int fd = -EBADF;
197 sd_bus *bus = ASSERT_PTR(userdata);
198 int r;
199
67c7ee11
LP
200 r = settle_image_class();
201 if (r < 0)
202 return r;
203
1db33ce5
LP
204 if (argc >= 2)
205 path = empty_or_dash_to_null(argv[1]);
206
207 if (argc >= 3)
208 local = empty_or_dash_to_null(argv[2]);
209 else if (path) {
210 r = path_extract_filename(path, &fn);
211 if (r < 0)
212 return log_error_errno(r, "Cannot extract container name from filename: %m");
213 if (r == O_DIRECTORY)
214 return log_error_errno(SYNTHETIC_ERRNO(EISDIR),
215 "Path '%s' refers to directory, but we need a regular file: %m", path);
216
217 local = fn;
218 }
219 if (!local)
220 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
221 "Need either path or local name.");
222
223 r = tar_strip_suffixes(local, &ll);
224 if (r < 0)
225 return log_oom();
226
227 local = ll;
228
8f20b498 229 if (!image_name_is_valid(local))
1db33ce5 230 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
8f20b498 231 "Local name %s is not a suitable image name.",
1db33ce5
LP
232 local);
233
234 if (path) {
235 fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
236 if (fd < 0)
237 return log_error_errno(errno, "Failed to open %s: %m", path);
238 }
239
67c7ee11
LP
240 if (arg_image_class == IMAGE_MACHINE && (arg_image_class & ~(IMPORT_FORCE|IMPORT_READ_ONLY)) == 0) {
241 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportTar");
242 if (r < 0)
243 return bus_log_create_error(r);
244
245 r = sd_bus_message_append(
246 m,
247 "hsbb",
248 fd >= 0 ? fd : STDIN_FILENO,
249 local,
250 FLAGS_SET(arg_import_flags, IMPORT_FORCE),
251 FLAGS_SET(arg_import_flags, IMPORT_READ_ONLY));
252 } else {
253 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportTarEx");
254 if (r < 0)
255 return bus_log_create_error(r);
256
257 r = sd_bus_message_append(
258 m,
259 "hsst",
260 fd >= 0 ? fd : STDIN_FILENO,
261 local,
262 image_class_to_string(arg_image_class),
263 (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY));
264 }
1db33ce5
LP
265 if (r < 0)
266 return bus_log_create_error(r);
267
268 return transfer_image_common(bus, m);
269}
270
271static int import_raw(int argc, char *argv[], void *userdata) {
272 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
273 _cleanup_free_ char *ll = NULL, *fn = NULL;
274 const char *local = NULL, *path = NULL;
275 _cleanup_close_ int fd = -EBADF;
276 sd_bus *bus = ASSERT_PTR(userdata);
277 int r;
278
67c7ee11
LP
279 r = settle_image_class();
280 if (r < 0)
281 return r;
282
1db33ce5
LP
283 if (argc >= 2)
284 path = empty_or_dash_to_null(argv[1]);
285
286 if (argc >= 3)
287 local = empty_or_dash_to_null(argv[2]);
288 else if (path) {
289 r = path_extract_filename(path, &fn);
290 if (r < 0)
291 return log_error_errno(r, "Cannot extract container name from filename: %m");
292 if (r == O_DIRECTORY)
293 return log_error_errno(SYNTHETIC_ERRNO(EISDIR),
294 "Path '%s' refers to directory, but we need a regular file: %m", path);
295
296 local = fn;
297 }
298 if (!local)
299 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
300 "Need either path or local name.");
301
302 r = raw_strip_suffixes(local, &ll);
303 if (r < 0)
304 return log_oom();
305
306 local = ll;
307
8f20b498 308 if (!image_name_is_valid(local))
1db33ce5 309 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
8f20b498 310 "Local name %s is not a suitable image name.",
1db33ce5
LP
311 local);
312
313 if (path) {
314 fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
315 if (fd < 0)
316 return log_error_errno(errno, "Failed to open %s: %m", path);
317 }
318
67c7ee11
LP
319 if (arg_image_class == IMAGE_MACHINE && (arg_image_class & ~(IMPORT_FORCE|IMPORT_READ_ONLY)) == 0) {
320 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportRaw");
321 if (r < 0)
322 return bus_log_create_error(r);
323
324 r = sd_bus_message_append(
325 m,
326 "hsbb",
327 fd >= 0 ? fd : STDIN_FILENO,
328 local,
329 FLAGS_SET(arg_import_flags, IMPORT_FORCE),
330 FLAGS_SET(arg_import_flags, IMPORT_READ_ONLY));
331 } else {
332 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportRawEx");
333 if (r < 0)
334 return bus_log_create_error(r);
335
336 r = sd_bus_message_append(
337 m,
338 "hsst",
339 fd >= 0 ? fd : STDIN_FILENO,
340 local,
341 image_class_to_string(arg_image_class),
342 (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY));
343 }
1db33ce5
LP
344 if (r < 0)
345 return bus_log_create_error(r);
346
347 return transfer_image_common(bus, m);
348}
349
350static int import_fs(int argc, char *argv[], void *userdata) {
351 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
352 const char *local = NULL, *path = NULL;
353 _cleanup_free_ char *fn = NULL;
354 _cleanup_close_ int fd = -EBADF;
355 sd_bus *bus = ASSERT_PTR(userdata);
356 int r;
357
67c7ee11
LP
358 r = settle_image_class();
359 if (r < 0)
360 return r;
361
1db33ce5
LP
362 if (argc >= 2)
363 path = empty_or_dash_to_null(argv[1]);
364
365 if (argc >= 3)
366 local = empty_or_dash_to_null(argv[2]);
367 else if (path) {
368 r = path_extract_filename(path, &fn);
369 if (r < 0)
370 return log_error_errno(r, "Cannot extract container name from filename: %m");
371
372 local = fn;
373 }
374 if (!local)
375 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
376 "Need either path or local name.");
377
8f20b498 378 if (!image_name_is_valid(local))
1db33ce5 379 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
8f20b498 380 "Local name %s is not a suitable image name.",
1db33ce5
LP
381 local);
382
383 if (path) {
384 fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
385 if (fd < 0)
386 return log_error_errno(errno, "Failed to open directory '%s': %m", path);
387 }
388
67c7ee11
LP
389 if (arg_image_class == IMAGE_MACHINE && (arg_image_class & ~(IMPORT_FORCE|IMPORT_READ_ONLY)) == 0) {
390 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportFileSystem");
391 if (r < 0)
392 return bus_log_create_error(r);
393
394 r = sd_bus_message_append(
395 m,
396 "hsbb",
397 fd >= 0 ? fd : STDIN_FILENO,
398 local,
399 FLAGS_SET(arg_import_flags, IMPORT_FORCE),
400 FLAGS_SET(arg_import_flags, IMPORT_READ_ONLY));
401 } else {
402 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportFileSystemEx");
403 if (r < 0)
404 return bus_log_create_error(r);
405
406 r = sd_bus_message_append(
407 m,
408 "hsst",
409 fd >= 0 ? fd : STDIN_FILENO,
410 local,
411 image_class_to_string(arg_image_class),
412 (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY));
413 }
1db33ce5
LP
414 if (r < 0)
415 return bus_log_create_error(r);
416
1db33ce5
LP
417
418 return transfer_image_common(bus, m);
419}
420
421static void determine_compression_from_filename(const char *p) {
422 if (arg_format)
423 return;
424
425 if (!p)
426 return;
427
428 if (endswith(p, ".xz"))
429 arg_format = "xz";
430 else if (endswith(p, ".gz"))
431 arg_format = "gzip";
432 else if (endswith(p, ".bz2"))
433 arg_format = "bzip2";
434}
435
436static int export_tar(int argc, char *argv[], void *userdata) {
437 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
438 _cleanup_close_ int fd = -EBADF;
439 const char *local = NULL, *path = NULL;
440 sd_bus *bus = ASSERT_PTR(userdata);
441 int r;
442
67c7ee11
LP
443 r = settle_image_class();
444 if (r < 0)
445 return r;
446
1db33ce5 447 local = argv[1];
8f20b498 448 if (!image_name_is_valid(local))
1db33ce5 449 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
8f20b498 450 "Image name %s is not valid.", local);
1db33ce5
LP
451
452 if (argc >= 3)
453 path = argv[2];
454 path = empty_or_dash_to_null(path);
455
456 if (path) {
457 determine_compression_from_filename(path);
458
459 fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666);
460 if (fd < 0)
461 return log_error_errno(errno, "Failed to open %s: %m", path);
462 }
463
67c7ee11
LP
464 if (arg_image_class == IMAGE_MACHINE && arg_import_flags == 0) {
465 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportTar");
466 if (r < 0)
467 return bus_log_create_error(r);
468
469 r = sd_bus_message_append(
470 m,
471 "shs",
472 local,
473 fd >= 0 ? fd : STDOUT_FILENO,
474 arg_format);
475 } else {
476 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportTarEx");
477 if (r < 0)
478 return bus_log_create_error(r);
479
480 r = sd_bus_message_append(
481 m,
482 "sshst",
483 local,
484 image_class_to_string(arg_image_class),
485 fd >= 0 ? fd : STDOUT_FILENO,
486 arg_format,
487 /* flags= */ 0);
488 }
1db33ce5
LP
489 if (r < 0)
490 return bus_log_create_error(r);
491
492 return transfer_image_common(bus, m);
493}
494
495static int export_raw(int argc, char *argv[], void *userdata) {
496 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
497 _cleanup_close_ int fd = -EBADF;
498 const char *local = NULL, *path = NULL;
499 sd_bus *bus = ASSERT_PTR(userdata);
500 int r;
501
67c7ee11
LP
502 r = settle_image_class();
503 if (r < 0)
504 return r;
505
1db33ce5 506 local = argv[1];
8f20b498 507 if (!image_name_is_valid(local))
1db33ce5 508 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
8f20b498 509 "Image name %s is not valid.", local);
1db33ce5
LP
510
511 if (argc >= 3)
512 path = argv[2];
513 path = empty_or_dash_to_null(path);
514
515 if (path) {
516 determine_compression_from_filename(path);
517
518 fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666);
519 if (fd < 0)
520 return log_error_errno(errno, "Failed to open %s: %m", path);
521 }
522
67c7ee11
LP
523 if (arg_image_class == IMAGE_MACHINE && arg_import_flags == 0) {
524 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportRaw");
525 if (r < 0)
526 return bus_log_create_error(r);
527
528 r = sd_bus_message_append(
529 m,
530 "shs",
531 local,
532 fd >= 0 ? fd : STDOUT_FILENO,
533 arg_format);
534 } else {
535 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportRawEx");
536 if (r < 0)
537 return bus_log_create_error(r);
538
539 r = sd_bus_message_append(
540 m,
541 "sshst",
542 local,
543 image_class_to_string(arg_image_class),
544 fd >= 0 ? fd : STDOUT_FILENO,
545 arg_format,
546 /* flags= */ 0);
547 }
1db33ce5
LP
548 if (r < 0)
549 return bus_log_create_error(r);
550
551 return transfer_image_common(bus, m);
552}
553
554static int pull_tar(int argc, char *argv[], void *userdata) {
555 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
556 _cleanup_free_ char *l = NULL, *ll = NULL;
557 const char *local, *remote;
558 sd_bus *bus = ASSERT_PTR(userdata);
559 int r;
560
67c7ee11
LP
561 r = settle_image_class();
562 if (r < 0)
563 return r;
564
1db33ce5
LP
565 remote = argv[1];
566 if (!http_url_is_valid(remote) && !file_url_is_valid(remote))
567 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
568 "URL '%s' is not valid.", remote);
569
570 if (argc >= 3)
571 local = argv[2];
572 else {
573 r = import_url_last_component(remote, &l);
574 if (r < 0)
575 return log_error_errno(r, "Failed to get final component of URL: %m");
576
577 local = l;
578 }
579
580 local = empty_or_dash_to_null(local);
581
582 if (local) {
583 r = tar_strip_suffixes(local, &ll);
584 if (r < 0)
585 return log_oom();
586
587 local = ll;
588
8f20b498 589 if (!image_name_is_valid(local))
1db33ce5 590 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
8f20b498 591 "Local name %s is not a suitable image name.",
1db33ce5
LP
592 local);
593 }
594
b146afc4 595 if (arg_image_class == IMAGE_MACHINE && (arg_image_class & ~IMPORT_FORCE) == 0) {
67c7ee11
LP
596 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTar");
597 if (r < 0)
598 return bus_log_create_error(r);
599
600 r = sd_bus_message_append(
601 m,
602 "sssb",
603 remote,
604 local,
605 import_verify_to_string(arg_verify),
606 FLAGS_SET(arg_import_flags, IMPORT_FORCE));
607 } else {
608 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTarEx");
609 if (r < 0)
610 return bus_log_create_error(r);
611
612 r = sd_bus_message_append(
613 m,
614 "sssst",
615 remote,
616 local,
617 image_class_to_string(arg_image_class),
618 import_verify_to_string(arg_verify),
b146afc4 619 (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD));
67c7ee11 620 }
1db33ce5
LP
621 if (r < 0)
622 return bus_log_create_error(r);
623
624 return transfer_image_common(bus, m);
625}
626
627static int pull_raw(int argc, char *argv[], void *userdata) {
628 _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
629 _cleanup_free_ char *l = NULL, *ll = NULL;
630 const char *local, *remote;
631 sd_bus *bus = ASSERT_PTR(userdata);
632 int r;
633
67c7ee11
LP
634 r = settle_image_class();
635 if (r < 0)
636 return r;
637
1db33ce5
LP
638 remote = argv[1];
639 if (!http_url_is_valid(remote) && !file_url_is_valid(remote))
640 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
641 "URL '%s' is not valid.", remote);
642
643 if (argc >= 3)
644 local = argv[2];
645 else {
646 r = import_url_last_component(remote, &l);
647 if (r < 0)
648 return log_error_errno(r, "Failed to get final component of URL: %m");
649
650 local = l;
651 }
652
653 local = empty_or_dash_to_null(local);
654
655 if (local) {
656 r = raw_strip_suffixes(local, &ll);
657 if (r < 0)
658 return log_oom();
659
660 local = ll;
661
8f20b498 662 if (!image_name_is_valid(local))
1db33ce5 663 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
8f20b498 664 "Local name %s is not a suitable image name.",
1db33ce5
LP
665 local);
666 }
667
b146afc4 668 if (arg_image_class == IMAGE_MACHINE && (arg_image_class & ~IMPORT_FORCE) == 0) {
67c7ee11
LP
669 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRaw");
670 if (r < 0)
671 return bus_log_create_error(r);
672
673 r = sd_bus_message_append(
674 m,
675 "sssb",
676 remote,
677 local,
678 import_verify_to_string(arg_verify),
679 FLAGS_SET(arg_import_flags, IMPORT_FORCE));
680 } else {
681 r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRawEx");
682 if (r < 0)
683 return bus_log_create_error(r);
684
685 r = sd_bus_message_append(
686 m,
687 "sssst",
688 remote,
689 local,
690 image_class_to_string(arg_image_class),
691 import_verify_to_string(arg_verify),
b146afc4 692 (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD));
67c7ee11 693 }
1db33ce5
LP
694 if (r < 0)
695 return bus_log_create_error(r);
696
697 return transfer_image_common(bus, m);
698}
699
1db33ce5 700static int list_transfers(int argc, char *argv[], void *userdata) {
1db33ce5 701 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
8229e226
LP
702 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
703 _cleanup_(table_unrefp) Table *t = NULL;
704 sd_bus *bus = ASSERT_PTR(userdata);
1db33ce5
LP
705 int r;
706
707 pager_open(arg_pager_flags);
708
67c7ee11 709 bool ex;
420b8ce7 710 r = bus_call_method(bus, bus_import_mgr, "ListTransfersEx", &error, &reply, "st", image_class_to_string(arg_image_class), UINT64_C(0));
67c7ee11
LP
711 if (r < 0) {
712 if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) {
713 sd_bus_error_free(&error);
1db33ce5 714
67c7ee11
LP
715 r = bus_call_method(bus, bus_import_mgr, "ListTransfers", &error, &reply, NULL);
716 }
717 if (r < 0)
718 return log_error_errno(r, "Could not get transfers: %s", bus_error_message(&error, r));
719
720 ex = false;
721 r = sd_bus_message_enter_container(reply, 'a', "(usssdo)");
722 } else {
723 ex = true;
724 r = sd_bus_message_enter_container(reply, 'a', "(ussssdo)");
725 }
1db33ce5
LP
726 if (r < 0)
727 return bus_log_parse_error(r);
728
67c7ee11 729 t = table_new("id", "progress", "type", "class", "local", "remote");
8229e226
LP
730 if (!t)
731 return log_oom();
1db33ce5 732
67c7ee11 733 (void) table_set_sort(t, (size_t) 4, (size_t) 0);
8229e226 734 table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
1db33ce5 735
8229e226 736 for (;;) {
67c7ee11 737 const char *type, *remote, *local, *class = "machine";
8229e226
LP
738 double progress;
739 uint32_t id;
1db33ce5 740
67c7ee11
LP
741 if (ex)
742 r = sd_bus_message_read(reply, "(ussssdo)", &id, &type, &remote, &local, &class, &progress, NULL);
743 else
744 r = sd_bus_message_read(reply, "(usssdo)", &id, &type, &remote, &local, &progress, NULL);
8229e226
LP
745 if (r < 0)
746 return bus_log_parse_error(r);
747 if (r == 0)
748 break;
1db33ce5 749
420b8ce7
LP
750 /* Ideally we use server-side filtering. But if the server can't do it, we need to do it client side */
751 if (arg_image_class >= 0 && image_class_from_string(class) != arg_image_class)
752 continue;
753
8229e226
LP
754 r = table_add_many(
755 t,
756 TABLE_UINT32, id,
757 TABLE_SET_ALIGN_PERCENT, 100);
758 if (r < 0)
759 return table_log_add_error(r);
1db33ce5 760
8229e226
LP
761 if (progress < 0)
762 r = table_add_many(
763 t,
764 TABLE_EMPTY,
765 TABLE_SET_ALIGN_PERCENT, 100);
766 else
767 r = table_add_many(
768 t,
769 TABLE_PERCENT, (int) (progress * 100),
770 TABLE_SET_ALIGN_PERCENT, 100);
771 if (r < 0)
772 return table_log_add_error(r);
773 r = table_add_many(
774 t,
775 TABLE_STRING, type,
67c7ee11 776 TABLE_STRING, class,
8229e226
LP
777 TABLE_STRING, local,
778 TABLE_STRING, remote,
779 TABLE_SET_URL, remote);
780 if (r < 0)
781 return table_log_add_error(r);
1db33ce5 782 }
1db33ce5
LP
783
784 r = sd_bus_message_exit_container(reply);
785 if (r < 0)
786 return bus_log_parse_error(r);
787
8229e226
LP
788 if (!table_isempty(t)) {
789 r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
790 if (r < 0)
791 return log_error_errno(r, "Failed to output table: %m");
792 }
1db33ce5
LP
793
794 if (arg_legend) {
8229e226
LP
795 if (!table_isempty(t))
796 printf("\n%zu transfers listed.\n", table_get_rows(t) - 1);
1db33ce5
LP
797 else
798 printf("No transfers.\n");
799 }
800
801 return 0;
802}
803
804static int cancel_transfer(int argc, char *argv[], void *userdata) {
805 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
806 sd_bus *bus = ASSERT_PTR(userdata);
807 int r;
808
809 polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
810
811 for (int i = 1; i < argc; i++) {
812 uint32_t id;
813
814 r = safe_atou32(argv[i], &id);
815 if (r < 0)
816 return log_error_errno(r, "Failed to parse transfer id: %s", argv[i]);
817
818 r = bus_call_method(bus, bus_import_mgr, "CancelTransfer", &error, NULL, "u", id);
819 if (r < 0)
820 return log_error_errno(r, "Could not cancel transfer: %s", bus_error_message(&error, r));
821 }
822
823 return 0;
824}
825
826static int help(int argc, char *argv[], void *userdata) {
827 _cleanup_free_ char *link = NULL;
828 int r;
829
830 pager_open(arg_pager_flags);
831
832 r = terminal_urlify_man("importctl", "1", &link);
833 if (r < 0)
834 return log_oom();
835
836 printf("%1$s [OPTIONS...] COMMAND ...\n\n"
67c7ee11
LP
837 "%5$sDownload, import or export disk images%6$s\n"
838 "\n%3$sCommands:%4$s\n"
1db33ce5
LP
839 " pull-tar URL [NAME] Download a TAR container image\n"
840 " pull-raw URL [NAME] Download a RAW container or VM image\n"
841 " import-tar FILE [NAME] Import a local TAR container image\n"
842 " import-raw FILE [NAME] Import a local RAW container or VM image\n"
843 " import-fs DIRECTORY [NAME] Import a local directory container image\n"
844 " export-tar NAME [FILE] Export a TAR container image locally\n"
845 " export-raw NAME [FILE] Export a RAW container or VM image locally\n"
67c7ee11
LP
846 " list-transfers Show list of transfers in progress\n"
847 " cancel-transfer [ID...] Cancel a transfer\n"
1db33ce5
LP
848 "\n%3$sOptions:%4$s\n"
849 " -h --help Show this help\n"
850 " --version Show package version\n"
851 " --no-pager Do not pipe output into a pager\n"
852 " --no-legend Do not show the headers and footers\n"
853 " --no-ask-password Do not ask for system passwords\n"
854 " -H --host=[USER@]HOST Operate on remote host\n"
855 " -M --machine=CONTAINER Operate on local container\n"
67c7ee11 856 " --read-only Create read-only image\n"
1db33ce5 857 " -q --quiet Suppress output\n"
8229e226
LP
858 " --json=pretty|short|off Generate JSON output\n"
859 " -j Equvilant to --json=pretty on TTY, --json=short\n"
860 " otherwise\n"
1db33ce5
LP
861 " --verify=MODE Verification mode for downloaded images (no,\n"
862 " checksum, signature)\n"
863 " --format=xz|gzip|bzip2 Desired output format for export\n"
67c7ee11
LP
864 " --force Install image even if already exists\n"
865 " -m --class=machine Install as machine image\n"
866 " -P --class=portable Install as portable service image\n"
867 " -S --class=sysext Install as system extension image\n"
868 " -C --class=confext Install as configuration extension image\n"
b146afc4
LP
869 " --keep-download=BOOL Control whether to keep pristine copy of download\n"
870 " -N Shortcut for --keep-download=no\n"
1db33ce5
LP
871 "\nSee the %2$s for details.\n",
872 program_invocation_short_name,
873 link,
874 ansi_underline(),
875 ansi_normal(),
876 ansi_highlight(),
877 ansi_normal());
878
879 return 0;
880}
881
882static int parse_argv(int argc, char *argv[]) {
883
884 enum {
885 ARG_VERSION = 0x100,
886 ARG_NO_PAGER,
887 ARG_NO_LEGEND,
888 ARG_NO_ASK_PASSWORD,
889 ARG_READ_ONLY,
8229e226 890 ARG_JSON,
1db33ce5
LP
891 ARG_VERIFY,
892 ARG_FORCE,
893 ARG_FORMAT,
67c7ee11 894 ARG_CLASS,
b146afc4 895 ARG_KEEP_DOWNLOAD,
1db33ce5
LP
896 };
897
898 static const struct option options[] = {
899 { "help", no_argument, NULL, 'h' },
900 { "version", no_argument, NULL, ARG_VERSION },
901 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
902 { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
903 { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
904 { "host", required_argument, NULL, 'H' },
905 { "machine", required_argument, NULL, 'M' },
906 { "read-only", no_argument, NULL, ARG_READ_ONLY },
8229e226 907 { "json", required_argument, NULL, ARG_JSON },
1db33ce5
LP
908 { "quiet", no_argument, NULL, 'q' },
909 { "verify", required_argument, NULL, ARG_VERIFY },
910 { "force", no_argument, NULL, ARG_FORCE },
911 { "format", required_argument, NULL, ARG_FORMAT },
67c7ee11 912 { "class", required_argument, NULL, ARG_CLASS },
b146afc4 913 { "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD },
1db33ce5
LP
914 {}
915 };
916
917 int c, r;
918
919 assert(argc >= 0);
920 assert(argv);
921
922 for (;;) {
b146afc4 923 c = getopt_long(argc, argv, "hH:M:jqmPSCN", options, NULL);
1db33ce5
LP
924 if (c < 0)
925 break;
926
927 switch (c) {
928
929 case 'h':
930 return help(0, NULL, NULL);
931
932 case ARG_VERSION:
933 return version();
934
935 case ARG_NO_PAGER:
936 arg_pager_flags |= PAGER_DISABLE;
937 break;
938
939 case ARG_NO_LEGEND:
940 arg_legend = false;
941 break;
942
943 case ARG_NO_ASK_PASSWORD:
944 arg_ask_password = false;
945 break;
946
947 case 'H':
948 arg_transport = BUS_TRANSPORT_REMOTE;
949 arg_host = optarg;
950 break;
951
952 case 'M':
953 arg_transport = BUS_TRANSPORT_MACHINE;
954 arg_host = optarg;
955 break;
956
957 case ARG_READ_ONLY:
67c7ee11 958 arg_import_flags |= IMPORT_READ_ONLY;
b146afc4 959 arg_import_flags_mask |= IMPORT_READ_ONLY;
1db33ce5
LP
960 break;
961
962 case 'q':
963 arg_quiet = true;
964 break;
965
966 case ARG_VERIFY:
967 if (streq(optarg, "help")) {
968 DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX);
969 return 0;
970 }
971
972 r = import_verify_from_string(optarg);
973 if (r < 0)
974 return log_error_errno(r, "Failed to parse --verify= setting: %s", optarg);
975 arg_verify = r;
976 break;
977
978 case ARG_FORCE:
67c7ee11 979 arg_import_flags |= IMPORT_FORCE;
b146afc4 980 arg_import_flags_mask |= IMPORT_FORCE;
1db33ce5
LP
981 break;
982
983 case ARG_FORMAT:
984 if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2"))
985 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
986 "Unknown format: %s", optarg);
987
988 arg_format = optarg;
989 break;
990
8229e226
LP
991 case ARG_JSON:
992 r = parse_json_argument(optarg, &arg_json_format_flags);
993 if (r <= 0)
994 return r;
995
996 arg_legend = false;
997 break;
998
999 case 'j':
1000 arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
1001 arg_legend = false;
1002 break;
1003
67c7ee11
LP
1004 case ARG_CLASS:
1005 arg_image_class = image_class_from_string(optarg);
1006 if (arg_image_class < 0)
1007 return log_error_errno(arg_image_class, "Failed to parse --class= parameter: %s", optarg);
1008 break;
1009
1010 case 'm':
1011 arg_image_class = IMAGE_MACHINE;
1012 break;
1013
1014 case 'P':
1015 arg_image_class = IMAGE_PORTABLE;
1016 break;
1017
1018 case 'S':
1019 arg_image_class = IMAGE_SYSEXT;
1020 break;
1021
1022 case 'C':
1023 arg_image_class = IMAGE_CONFEXT;
1024 break;
1025
b146afc4
LP
1026 case ARG_KEEP_DOWNLOAD:
1027 r = parse_boolean(optarg);
1028 if (r < 0)
1029 return log_error_errno(r, "Failed to parse --keep-download= value: %s", optarg);
1030
1031 SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r);
1032 arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD;
1033 break;
1034
1035 case 'N':
1036 arg_import_flags_mask &= ~IMPORT_PULL_KEEP_DOWNLOAD;
1037 arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD;
1038 break;
1039
1db33ce5
LP
1040 case '?':
1041 return -EINVAL;
1042
1043 default:
1044 assert_not_reached();
1045 }
1046 }
1047
1048 return 1;
1049}
1050
1051static int importctl_main(int argc, char *argv[], sd_bus *bus) {
1052
1053 static const Verb verbs[] = {
1054 { "help", VERB_ANY, VERB_ANY, 0, help },
1055 { "import-tar", 2, 3, 0, import_tar },
1056 { "import-raw", 2, 3, 0, import_raw },
1057 { "import-fs", 2, 3, 0, import_fs },
1058 { "export-tar", 2, 3, 0, export_tar },
1059 { "export-raw", 2, 3, 0, export_raw },
1060 { "pull-tar", 2, 3, 0, pull_tar },
1061 { "pull-raw", 2, 3, 0, pull_raw },
1062 { "list-transfers", VERB_ANY, 1, VERB_DEFAULT, list_transfers },
1063 { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer },
1064 {}
1065 };
1066
1067 return dispatch_verb(argc, argv, verbs, bus);
1068}
1069
1070static int run(int argc, char *argv[]) {
1071 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1072 int r;
1073
1074 setlocale(LC_ALL, "");
1075 log_setup();
1076
1077 r = parse_argv(argc, argv);
1078 if (r <= 0)
1079 return r;
1080
1081 r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, &bus);
1082 if (r < 0)
1083 return bus_log_connect_error(r, arg_transport);
1084
1085 (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
1086
1087 return importctl_main(argc, argv, bus);
1088}
1089
1090DEFINE_MAIN_FUNCTION(run);