/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include "sd-bus.h" #include "alloc-util.h" #include "build.h" #include "bus-error.h" #include "bus-locator.h" #include "bus-util.h" #include "discover-image.h" #include "fd-util.h" #include "format-table.h" #include "hostname-util.h" #include "import-common.h" #include "import-util.h" #include "locale-util.h" #include "log.h" #include "macro.h" #include "main-func.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" #include "pretty-print.h" #include "signal-util.h" #include "sort-util.h" #include "spawn-polkit-agent.h" #include "string-table.h" #include "verbs.h" #include "web-util.h" static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; static const char *arg_host = NULL; static ImportFlags arg_import_flags = 0; static ImportFlags arg_import_flags_mask = 0; /* Indicates which flags have been explicitly set to on or to off */ static bool arg_quiet = false; static bool arg_ask_password = true; static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; static const char* arg_format = NULL; static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static ImageClass arg_image_class = _IMAGE_CLASS_INVALID; #define PROGRESS_PREFIX "Total: " static int settle_image_class(void) { if (arg_image_class < 0) { _cleanup_free_ char *j = NULL; for (ImageClass class = 0; class < _IMAGE_CLASS_MAX; class++) if (strextendf_with_separator(&j, ", ", "%s (downloads to %s/)", image_class_to_string(class), image_root_to_string(class)) < 0) return log_oom(); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No image class specified, retry with --class= set to one of: %s.", j); } /* Keep the original pristine downloaded file as a copy only when dealing with machine images, * because unlike sysext/confext/portable they are typically modified during runtime. */ if (!FLAGS_SET(arg_import_flags_mask, IMPORT_PULL_KEEP_DOWNLOAD)) SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, arg_image_class == IMAGE_MACHINE); return 0; } typedef struct Context { const char *object_path; double progress; } Context; static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) { Context *c = ASSERT_PTR(userdata); const char *line; unsigned priority; int r; assert(m); if (!streq_ptr(c->object_path, sd_bus_message_get_path(m))) return 0; r = sd_bus_message_read(m, "us", &priority, &line); if (r < 0) { bus_log_parse_error(r); return 0; } if (arg_quiet && LOG_PRI(priority) >= LOG_INFO) return 0; if (!arg_quiet) clear_progress_bar(PROGRESS_PREFIX); log_full(priority, "%s", line); if (!arg_quiet) draw_progress_bar(PROGRESS_PREFIX, c->progress * 100); return 0; } static int match_progress_update(sd_bus_message *m, void *userdata, sd_bus_error *error) { Context *c = ASSERT_PTR(userdata); int r; assert(m); if (!streq_ptr(c->object_path, sd_bus_message_get_path(m))) return 0; r = sd_bus_message_read(m, "d", &c->progress); if (r < 0) { bus_log_parse_error(r); return 0; } if (!arg_quiet) draw_progress_bar(PROGRESS_PREFIX, c->progress * 100); return 0; } static int match_transfer_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { Context *c = ASSERT_PTR(userdata); const char *path, *result; uint32_t id; int r; assert(m); if (!arg_quiet) clear_progress_bar(PROGRESS_PREFIX); r = sd_bus_message_read(m, "uos", &id, &path, &result); if (r < 0) { bus_log_parse_error(r); return 0; } if (!streq_ptr(c->object_path, path)) return 0; sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), !streq_ptr(result, "done")); return 0; } static int transfer_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { assert(s); assert(si); if (!arg_quiet) clear_progress_bar(PROGRESS_PREFIX); if (!arg_quiet) log_info("Continuing download in the background. Use \"%s cancel-transfer %" PRIu32 "\" to abort transfer.", program_invocation_short_name, PTR_TO_UINT32(userdata)); sd_event_exit(sd_event_source_get_event(s), EINTR); return 0; } static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL, *slot_progress_update = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_event_unrefp) sd_event* event = NULL; Context c = {}; uint32_t id; int r; assert(bus); assert(m); polkit_agent_open_if_enabled(arg_transport, arg_ask_password); r = sd_event_default(&event); if (r < 0) return log_error_errno(r, "Failed to get event loop: %m"); r = sd_bus_attach_event(bus, event, 0); if (r < 0) return log_error_errno(r, "Failed to attach bus to event loop: %m"); r = bus_match_signal_async( bus, &slot_job_removed, bus_import_mgr, "TransferRemoved", match_transfer_removed, /* add_callback= */ NULL, &c); if (r < 0) return log_error_errno(r, "Failed to request match: %m"); r = sd_bus_match_signal_async( bus, &slot_log_message, "org.freedesktop.import1", /* object_path= */ NULL, "org.freedesktop.import1.Transfer", "LogMessage", match_log_message, /* add_callback= */ NULL, &c); if (r < 0) return log_error_errno(r, "Failed to request match: %m"); r = sd_bus_match_signal_async( bus, &slot_progress_update, "org.freedesktop.import1", /* object_path= */ NULL, "org.freedesktop.import1.Transfer", "ProgressUpdate", match_progress_update, /* add_callback= */ NULL, &c); if (r < 0) return log_error_errno(r, "Failed to request match: %m"); r = sd_bus_call(bus, m, 0, &error, &reply); if (r < 0) return log_error_errno(r, "Failed to transfer image: %s", bus_error_message(&error, r)); r = sd_bus_message_read(reply, "uo", &id, &c.object_path); if (r < 0) return bus_log_parse_error(r); if (!arg_quiet) { clear_progress_bar(PROGRESS_PREFIX); log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id); draw_progress_bar(PROGRESS_PREFIX, c.progress); } (void) sd_event_add_signal(event, NULL, SIGINT|SD_EVENT_SIGNAL_PROCMASK, transfer_signal_handler, UINT32_TO_PTR(id)); (void) sd_event_add_signal(event, NULL, SIGTERM|SD_EVENT_SIGNAL_PROCMASK, transfer_signal_handler, UINT32_TO_PTR(id)); r = sd_event_loop(event); if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); return -r; } static int import_tar(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *ll = NULL, *fn = NULL; const char *local = NULL, *path = NULL; _cleanup_close_ int fd = -EBADF; sd_bus *bus = ASSERT_PTR(userdata); int r; r = settle_image_class(); if (r < 0) return r; if (argc >= 2) path = empty_or_dash_to_null(argv[1]); if (argc >= 3) local = empty_or_dash_to_null(argv[2]); else if (path) { r = path_extract_filename(path, &fn); if (r < 0) return log_error_errno(r, "Cannot extract container name from filename: %m"); if (r == O_DIRECTORY) return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Path '%s' refers to directory, but we need a regular file: %m", path); local = fn; } if (!local) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Need either path or local name."); r = tar_strip_suffixes(local, &ll); if (r < 0) return log_oom(); local = ll; if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Local name %s is not a suitable image name.", local); if (path) { fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) return log_error_errno(errno, "Failed to open %s: %m", path); } if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~(IMPORT_FORCE|IMPORT_READ_ONLY)) == 0) { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportTar"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "hsbb", fd >= 0 ? fd : STDIN_FILENO, local, FLAGS_SET(arg_import_flags, IMPORT_FORCE), FLAGS_SET(arg_import_flags, IMPORT_READ_ONLY)); } else { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportTarEx"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "hsst", fd >= 0 ? fd : STDIN_FILENO, local, image_class_to_string(arg_image_class), (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY)); } if (r < 0) return bus_log_create_error(r); return transfer_image_common(bus, m); } static int import_raw(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *ll = NULL, *fn = NULL; const char *local = NULL, *path = NULL; _cleanup_close_ int fd = -EBADF; sd_bus *bus = ASSERT_PTR(userdata); int r; r = settle_image_class(); if (r < 0) return r; if (argc >= 2) path = empty_or_dash_to_null(argv[1]); if (argc >= 3) local = empty_or_dash_to_null(argv[2]); else if (path) { r = path_extract_filename(path, &fn); if (r < 0) return log_error_errno(r, "Cannot extract container name from filename: %m"); if (r == O_DIRECTORY) return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Path '%s' refers to directory, but we need a regular file: %m", path); local = fn; } if (!local) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Need either path or local name."); r = raw_strip_suffixes(local, &ll); if (r < 0) return log_oom(); local = ll; if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Local name %s is not a suitable image name.", local); if (path) { fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) return log_error_errno(errno, "Failed to open %s: %m", path); } if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~(IMPORT_FORCE|IMPORT_READ_ONLY)) == 0) { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportRaw"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "hsbb", fd >= 0 ? fd : STDIN_FILENO, local, FLAGS_SET(arg_import_flags, IMPORT_FORCE), FLAGS_SET(arg_import_flags, IMPORT_READ_ONLY)); } else { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportRawEx"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "hsst", fd >= 0 ? fd : STDIN_FILENO, local, image_class_to_string(arg_image_class), (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY)); } if (r < 0) return bus_log_create_error(r); return transfer_image_common(bus, m); } static int import_fs(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; const char *local = NULL, *path = NULL; _cleanup_free_ char *fn = NULL; _cleanup_close_ int fd = -EBADF; sd_bus *bus = ASSERT_PTR(userdata); int r; r = settle_image_class(); if (r < 0) return r; if (argc >= 2) path = empty_or_dash_to_null(argv[1]); if (argc >= 3) local = empty_or_dash_to_null(argv[2]); else if (path) { r = path_extract_filename(path, &fn); if (r < 0) return log_error_errno(r, "Cannot extract container name from filename: %m"); local = fn; } if (!local) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Need either path or local name."); if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Local name %s is not a suitable image name.", local); if (path) { fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC); if (fd < 0) return log_error_errno(errno, "Failed to open directory '%s': %m", path); } if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~(IMPORT_FORCE|IMPORT_READ_ONLY)) == 0) { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportFileSystem"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "hsbb", fd >= 0 ? fd : STDIN_FILENO, local, FLAGS_SET(arg_import_flags, IMPORT_FORCE), FLAGS_SET(arg_import_flags, IMPORT_READ_ONLY)); } else { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportFileSystemEx"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "hsst", fd >= 0 ? fd : STDIN_FILENO, local, image_class_to_string(arg_image_class), (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY)); } if (r < 0) return bus_log_create_error(r); return transfer_image_common(bus, m); } static void determine_compression_from_filename(const char *p) { if (arg_format) return; if (!p) return; if (endswith(p, ".xz")) arg_format = "xz"; else if (endswith(p, ".gz")) arg_format = "gzip"; else if (endswith(p, ".bz2")) arg_format = "bzip2"; } static int export_tar(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_close_ int fd = -EBADF; const char *local = NULL, *path = NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; r = settle_image_class(); if (r < 0) return r; local = argv[1]; if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Image name %s is not valid.", local); if (argc >= 3) path = argv[2]; path = empty_or_dash_to_null(path); if (path) { determine_compression_from_filename(path); fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); if (fd < 0) return log_error_errno(errno, "Failed to open %s: %m", path); } if (arg_image_class == IMAGE_MACHINE && arg_import_flags == 0) { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportTar"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "shs", local, fd >= 0 ? fd : STDOUT_FILENO, arg_format); } else { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportTarEx"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "sshst", local, image_class_to_string(arg_image_class), fd >= 0 ? fd : STDOUT_FILENO, arg_format, /* flags= */ UINT64_C(0)); } if (r < 0) return bus_log_create_error(r); return transfer_image_common(bus, m); } static int export_raw(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_close_ int fd = -EBADF; const char *local = NULL, *path = NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; r = settle_image_class(); if (r < 0) return r; local = argv[1]; if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Image name %s is not valid.", local); if (argc >= 3) path = argv[2]; path = empty_or_dash_to_null(path); if (path) { determine_compression_from_filename(path); fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); if (fd < 0) return log_error_errno(errno, "Failed to open %s: %m", path); } if (arg_image_class == IMAGE_MACHINE && arg_import_flags == 0) { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportRaw"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "shs", local, fd >= 0 ? fd : STDOUT_FILENO, arg_format); } else { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportRawEx"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "sshst", local, image_class_to_string(arg_image_class), fd >= 0 ? fd : STDOUT_FILENO, arg_format, /* flags= */ UINT64_C(0)); } if (r < 0) return bus_log_create_error(r); return transfer_image_common(bus, m); } static int pull_tar(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *l = NULL, *ll = NULL; const char *local, *remote; sd_bus *bus = ASSERT_PTR(userdata); int r; r = settle_image_class(); if (r < 0) return r; remote = argv[1]; if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "URL '%s' is not valid.", remote); if (argc >= 3) local = argv[2]; else { r = import_url_last_component(remote, &l); if (r < 0) return log_error_errno(r, "Failed to get final component of URL: %m"); local = l; } local = empty_or_dash_to_null(local); if (local) { r = tar_strip_suffixes(local, &ll); if (r < 0) return log_oom(); local = ll; if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Local name %s is not a suitable image name.", local); } if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTar"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "sssb", remote, local, import_verify_to_string(arg_verify), FLAGS_SET(arg_import_flags, IMPORT_FORCE)); } else { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTarEx"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "sssst", remote, local, image_class_to_string(arg_image_class), import_verify_to_string(arg_verify), (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)); } if (r < 0) return bus_log_create_error(r); return transfer_image_common(bus, m); } static int pull_raw(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *l = NULL, *ll = NULL; const char *local, *remote; sd_bus *bus = ASSERT_PTR(userdata); int r; r = settle_image_class(); if (r < 0) return r; remote = argv[1]; if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "URL '%s' is not valid.", remote); if (argc >= 3) local = argv[2]; else { r = import_url_last_component(remote, &l); if (r < 0) return log_error_errno(r, "Failed to get final component of URL: %m"); local = l; } local = empty_or_dash_to_null(local); if (local) { r = raw_strip_suffixes(local, &ll); if (r < 0) return log_oom(); local = ll; if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Local name %s is not a suitable image name.", local); } if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRaw"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "sssb", remote, local, import_verify_to_string(arg_verify), FLAGS_SET(arg_import_flags, IMPORT_FORCE)); } else { r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRawEx"); if (r < 0) return bus_log_create_error(r); r = sd_bus_message_append( m, "sssst", remote, local, image_class_to_string(arg_image_class), import_verify_to_string(arg_verify), (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)); } if (r < 0) return bus_log_create_error(r); return transfer_image_common(bus, m); } static int list_transfers(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *t = NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; pager_open(arg_pager_flags); bool ex; r = bus_call_method(bus, bus_import_mgr, "ListTransfersEx", &error, &reply, "st", image_class_to_string(arg_image_class), UINT64_C(0)); if (r < 0) { if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { sd_bus_error_free(&error); r = bus_call_method(bus, bus_import_mgr, "ListTransfers", &error, &reply, NULL); } if (r < 0) return log_error_errno(r, "Could not get transfers: %s", bus_error_message(&error, r)); ex = false; r = sd_bus_message_enter_container(reply, 'a', "(usssdo)"); } else { ex = true; r = sd_bus_message_enter_container(reply, 'a', "(ussssdo)"); } if (r < 0) return bus_log_parse_error(r); t = table_new("id", "progress", "type", "class", "local", "remote"); if (!t) return log_oom(); (void) table_set_sort(t, (size_t) 4, (size_t) 0); table_set_ersatz_string(t, TABLE_ERSATZ_DASH); for (;;) { const char *type, *remote, *local, *class = "machine"; double progress; uint32_t id; if (ex) r = sd_bus_message_read(reply, "(ussssdo)", &id, &type, &remote, &local, &class, &progress, NULL); else r = sd_bus_message_read(reply, "(usssdo)", &id, &type, &remote, &local, &progress, NULL); if (r < 0) return bus_log_parse_error(r); if (r == 0) break; /* Ideally we use server-side filtering. But if the server can't do it, we need to do it client side */ if (arg_image_class >= 0 && image_class_from_string(class) != arg_image_class) continue; r = table_add_many( t, TABLE_UINT32, id, TABLE_SET_ALIGN_PERCENT, 100); if (r < 0) return table_log_add_error(r); if (progress < 0) r = table_add_many( t, TABLE_EMPTY, TABLE_SET_ALIGN_PERCENT, 100); else r = table_add_many( t, TABLE_PERCENT, (int) (progress * 100), TABLE_SET_ALIGN_PERCENT, 100); if (r < 0) return table_log_add_error(r); r = table_add_many( t, TABLE_STRING, type, TABLE_STRING, class, TABLE_STRING, local, TABLE_STRING, remote, TABLE_SET_URL, remote); if (r < 0) return table_log_add_error(r); } r = sd_bus_message_exit_container(reply); if (r < 0) return bus_log_parse_error(r); if (!table_isempty(t)) { r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) return log_error_errno(r, "Failed to output table: %m"); } if (arg_legend) { if (!table_isempty(t)) printf("\n%zu transfers listed.\n", table_get_rows(t) - 1); else printf("No transfers.\n"); } return 0; } static int cancel_transfer(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; polkit_agent_open_if_enabled(arg_transport, arg_ask_password); for (int i = 1; i < argc; i++) { uint32_t id; r = safe_atou32(argv[i], &id); if (r < 0) return log_error_errno(r, "Failed to parse transfer id: %s", argv[i]); r = bus_call_method(bus, bus_import_mgr, "CancelTransfer", &error, NULL, "u", id); if (r < 0) return log_error_errno(r, "Could not cancel transfer: %s", bus_error_message(&error, r)); } return 0; } static int list_images(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(table_unrefp) Table *t = NULL; sd_bus *bus = ASSERT_PTR(userdata); int r; pager_open(arg_pager_flags); r = bus_call_method(bus, bus_import_mgr, "ListImages", &error, &reply, "st", image_class_to_string(arg_image_class), UINT64_C(0)); if (r < 0) return log_error_errno(r, "Could not list images: %s", bus_error_message(&error, r)); r = sd_bus_message_enter_container(reply, 'a', "(ssssbtttttt)"); if (r < 0) return bus_log_parse_error(r); t = table_new("class", "name", "type", "path", "ro", "crtime", "mtime", "usage", "usage-exclusive", "limit", "limit-exclusive"); if (!t) return log_oom(); (void) table_set_sort(t, (size_t) 0, (size_t) 1); table_set_ersatz_string(t, TABLE_ERSATZ_DASH); /* Hide the exclusive columns for now */ (void) table_hide_column_from_display(t, 8); (void) table_hide_column_from_display(t, 10); for (;;) { uint64_t crtime, mtime, usage, usage_exclusive, limit, limit_exclusive; const char *class, *name, *type, *path; int read_only; r = sd_bus_message_read(reply, "(ssssbtttttt)", &class, &name, &type, &path, &read_only, &crtime, &mtime, &usage, &usage_exclusive, &limit, &limit_exclusive); if (r < 0) return bus_log_parse_error(r); if (r == 0) break; r = table_add_many( t, TABLE_STRING, class, TABLE_STRING, name, TABLE_STRING, type, TABLE_PATH, path); if (r < 0) return table_log_add_error(r); if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) r = table_add_many( t, TABLE_STRING, read_only ? "ro" : "rw", TABLE_SET_COLOR, read_only ? ANSI_HIGHLIGHT_RED : ANSI_HIGHLIGHT_GREEN); else r = table_add_many( t, TABLE_BOOLEAN, read_only); if (r < 0) return table_log_add_error(r); r = table_add_many( t, TABLE_TIMESTAMP, crtime, TABLE_TIMESTAMP, mtime, TABLE_SIZE, usage, TABLE_SIZE, usage_exclusive, TABLE_SIZE, limit, TABLE_SIZE, limit_exclusive); if (r < 0) return table_log_add_error(r); } r = sd_bus_message_exit_container(reply); if (r < 0) return bus_log_parse_error(r); if (!table_isempty(t)) { r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) return log_error_errno(r, "Failed to output table: %m"); } if (arg_legend) { if (!table_isempty(t)) printf("\n%zu images listed.\n", table_get_rows(t) - 1); else printf("No images.\n"); } return 0; } static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; pager_open(arg_pager_flags); r = terminal_urlify_man("importctl", "1", &link); if (r < 0) return log_oom(); printf("%1$s [OPTIONS...] COMMAND ...\n\n" "%5$sDownload, import or export disk images%6$s\n" "\n%3$sCommands:%4$s\n" " pull-tar URL [NAME] Download a TAR container image\n" " pull-raw URL [NAME] Download a RAW container or VM image\n" " import-tar FILE [NAME] Import a local TAR container image\n" " import-raw FILE [NAME] Import a local RAW container or VM image\n" " import-fs DIRECTORY [NAME] Import a local directory container image\n" " export-tar NAME [FILE] Export a TAR container image locally\n" " export-raw NAME [FILE] Export a RAW container or VM image locally\n" " list-transfers Show list of transfers in progress\n" " cancel-transfer [ID...] Cancel a transfer\n" " list-images Show list of installed images\n" "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" " --version Show package version\n" " --no-pager Do not pipe output into a pager\n" " --no-legend Do not show the headers and footers\n" " --no-ask-password Do not ask for system passwords\n" " -H --host=[USER@]HOST Operate on remote host\n" " -M --machine=CONTAINER Operate on local container\n" " --read-only Create read-only image\n" " -q --quiet Suppress output\n" " --json=pretty|short|off Generate JSON output\n" " -j Equvilant to --json=pretty on TTY, --json=short\n" " otherwise\n" " --verify=MODE Verification mode for downloaded images (no,\n" " checksum, signature)\n" " --format=xz|gzip|bzip2 Desired output format for export\n" " --force Install image even if already exists\n" " -m --class=machine Install as machine image\n" " -P --class=portable Install as portable service image\n" " -S --class=sysext Install as system extension image\n" " -C --class=confext Install as configuration extension image\n" " --keep-download=BOOL Control whether to keep pristine copy of download\n" " -N Shortcut for --keep-download=no\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, ansi_underline(), ansi_normal(), ansi_highlight(), ansi_normal()); return 0; } static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_NO_PAGER, ARG_NO_LEGEND, ARG_NO_ASK_PASSWORD, ARG_READ_ONLY, ARG_JSON, ARG_VERIFY, ARG_FORCE, ARG_FORMAT, ARG_CLASS, ARG_KEEP_DOWNLOAD, }; static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, { "host", required_argument, NULL, 'H' }, { "machine", required_argument, NULL, 'M' }, { "read-only", no_argument, NULL, ARG_READ_ONLY }, { "json", required_argument, NULL, ARG_JSON }, { "quiet", no_argument, NULL, 'q' }, { "verify", required_argument, NULL, ARG_VERIFY }, { "force", no_argument, NULL, ARG_FORCE }, { "format", required_argument, NULL, ARG_FORMAT }, { "class", required_argument, NULL, ARG_CLASS }, { "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD }, {} }; int c, r; assert(argc >= 0); assert(argv); for (;;) { c = getopt_long(argc, argv, "hH:M:jqmPSCN", options, NULL); if (c < 0) break; switch (c) { case 'h': return help(0, NULL, NULL); case ARG_VERSION: return version(); case ARG_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; case ARG_NO_LEGEND: arg_legend = false; break; case ARG_NO_ASK_PASSWORD: arg_ask_password = false; break; case 'H': arg_transport = BUS_TRANSPORT_REMOTE; arg_host = optarg; break; case 'M': arg_transport = BUS_TRANSPORT_MACHINE; arg_host = optarg; break; case ARG_READ_ONLY: arg_import_flags |= IMPORT_READ_ONLY; arg_import_flags_mask |= IMPORT_READ_ONLY; break; case 'q': arg_quiet = true; break; case ARG_VERIFY: if (streq(optarg, "help")) { DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX); return 0; } r = import_verify_from_string(optarg); if (r < 0) return log_error_errno(r, "Failed to parse --verify= setting: %s", optarg); arg_verify = r; break; case ARG_FORCE: arg_import_flags |= IMPORT_FORCE; arg_import_flags_mask |= IMPORT_FORCE; break; case ARG_FORMAT: if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown format: %s", optarg); arg_format = optarg; break; case ARG_JSON: r = parse_json_argument(optarg, &arg_json_format_flags); if (r <= 0) return r; arg_legend = false; break; case 'j': arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO; arg_legend = false; break; case ARG_CLASS: arg_image_class = image_class_from_string(optarg); if (arg_image_class < 0) return log_error_errno(arg_image_class, "Failed to parse --class= parameter: %s", optarg); break; case 'm': arg_image_class = IMAGE_MACHINE; break; case 'P': arg_image_class = IMAGE_PORTABLE; break; case 'S': arg_image_class = IMAGE_SYSEXT; break; case 'C': arg_image_class = IMAGE_CONFEXT; break; case ARG_KEEP_DOWNLOAD: r = parse_boolean(optarg); if (r < 0) return log_error_errno(r, "Failed to parse --keep-download= value: %s", optarg); SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r); arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD; break; case 'N': arg_import_flags_mask &= ~IMPORT_PULL_KEEP_DOWNLOAD; arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD; break; case '?': return -EINVAL; default: assert_not_reached(); } } return 1; } static int importctl_main(int argc, char *argv[], sd_bus *bus) { static const Verb verbs[] = { { "help", VERB_ANY, VERB_ANY, 0, help }, { "import-tar", 2, 3, 0, import_tar }, { "import-raw", 2, 3, 0, import_raw }, { "import-fs", 2, 3, 0, import_fs }, { "export-tar", 2, 3, 0, export_tar }, { "export-raw", 2, 3, 0, export_raw }, { "pull-tar", 2, 3, 0, pull_tar }, { "pull-raw", 2, 3, 0, pull_raw }, { "list-transfers", VERB_ANY, 1, VERB_DEFAULT, list_transfers }, { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer }, { "list-images", VERB_ANY, 1, 0, list_images }, {} }; return dispatch_verb(argc, argv, verbs, bus); } static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; setlocale(LC_ALL, ""); log_setup(); r = parse_argv(argc, argv); if (r <= 0) return r; r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, &bus); if (r < 0) return bus_log_connect_error(r, arg_transport); (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); return importctl_main(argc, argv, bus); } DEFINE_MAIN_FUNCTION(run);