diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:40 +0000 |
commit | fc53809803cd2bc2434e312b19a18fa36776da12 (patch) | |
tree | b4b43bd6538f51965ce32856e9c053d0f90919c8 /src/import | |
parent | Adding upstream version 255.5. (diff) | |
download | systemd-fc53809803cd2bc2434e312b19a18fa36776da12.tar.xz systemd-fc53809803cd2bc2434e312b19a18fa36776da12.zip |
Adding upstream version 256.upstream/256
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/import')
-rw-r--r-- | src/import/curl-util.c | 45 | ||||
-rw-r--r-- | src/import/curl-util.h | 1 | ||||
-rw-r--r-- | src/import/export.c | 60 | ||||
-rw-r--r-- | src/import/import-common.c | 7 | ||||
-rw-r--r-- | src/import/import-common.h | 37 | ||||
-rw-r--r-- | src/import/import-fs.c | 28 | ||||
-rw-r--r-- | src/import/import-raw.c | 3 | ||||
-rw-r--r-- | src/import/import-tar.c | 3 | ||||
-rw-r--r-- | src/import/import.c | 42 | ||||
-rw-r--r-- | src/import/importctl.c | 1245 | ||||
-rw-r--r-- | src/import/importd.c | 673 | ||||
-rw-r--r-- | src/import/meson.build | 30 | ||||
-rw-r--r-- | src/import/org.freedesktop.import1.conf | 36 | ||||
-rw-r--r-- | src/import/org.freedesktop.import1.policy | 22 | ||||
-rw-r--r-- | src/import/pull-common.c | 19 | ||||
-rw-r--r-- | src/import/pull-common.h | 21 | ||||
-rw-r--r-- | src/import/pull-job.c | 6 | ||||
-rw-r--r-- | src/import/pull-raw.c | 168 | ||||
-rw-r--r-- | src/import/pull-raw.h | 2 | ||||
-rw-r--r-- | src/import/pull-tar.c | 137 | ||||
-rw-r--r-- | src/import/pull-tar.h | 2 | ||||
-rw-r--r-- | src/import/pull.c | 119 | ||||
-rw-r--r-- | src/import/qcow2-util.c | 2 |
23 files changed, 2262 insertions, 446 deletions
diff --git a/src/import/curl-util.c b/src/import/curl-util.c index 94f718d..1628f83 100644 --- a/src/import/curl-util.c +++ b/src/import/curl-util.c @@ -10,20 +10,28 @@ #include "version.h" static void curl_glue_check_finished(CurlGlue *g) { - CURLMsg *msg; - int k = 0; + int r; assert(g); + /* sd_event_get_exit_code() returns -ENODATA if no exit was scheduled yet */ + r = sd_event_get_exit_code(g->event, /* ret_code= */ NULL); + if (r >= 0) + return; /* exit scheduled? Then don't process this anymore */ + if (r != -ENODATA) + log_debug_errno(r, "Unexpected error while checking for event loop exit code, ignoring: %m"); + + CURLMsg *msg; + int k = 0; msg = curl_multi_info_read(g->curl, &k); if (!msg) return; - if (msg->msg != CURLMSG_DONE) - return; - - if (g->on_finished) + if (msg->msg == CURLMSG_DONE && g->on_finished) g->on_finished(g, msg->easy_handle, msg->data.result); + + /* This is a queue, process another item soon, but do so in a later event loop iteration. */ + (void) sd_event_source_set_enabled(g->defer, SD_EVENT_ONESHOT); } static int curl_glue_on_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) { @@ -126,6 +134,13 @@ static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata assert(curl); + /* Don't configure timer anymore when the event loop is dead already. */ + if (g->timer) { + sd_event *event_loop = sd_event_source_get_event(g->timer); + if (event_loop && sd_event_get_state(event_loop) == SD_EVENT_FINISHED) + return 0; + } + if (timeout_ms < 0) { if (g->timer) { if (sd_event_source_set_enabled(g->timer, SD_EVENT_OFF) < 0) @@ -153,6 +168,15 @@ static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata return 0; } +static int curl_glue_on_defer(sd_event_source *s, void *userdata) { + CurlGlue *g = ASSERT_PTR(userdata); + + assert(s); + + curl_glue_check_finished(g); + return 0; +} + CurlGlue *curl_glue_unref(CurlGlue *g) { sd_event_source *io; @@ -167,7 +191,8 @@ CurlGlue *curl_glue_unref(CurlGlue *g) { hashmap_free(g->ios); - sd_event_source_unref(g->timer); + sd_event_source_disable_unref(g->timer); + sd_event_source_disable_unref(g->defer); sd_event_unref(g->event); return mfree(g); } @@ -211,6 +236,12 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { if (curl_multi_setopt(g->curl, CURLMOPT_TIMERFUNCTION, curl_glue_timer_callback) != CURLM_OK) return -EINVAL; + r = sd_event_add_defer(g->event, &g->defer, curl_glue_on_defer, g); + if (r < 0) + return r; + + (void) sd_event_source_set_description(g->defer, "curl-defer"); + *glue = TAKE_PTR(g); return 0; diff --git a/src/import/curl-util.h b/src/import/curl-util.h index 6b4f992..cef0b26 100644 --- a/src/import/curl-util.h +++ b/src/import/curl-util.h @@ -16,6 +16,7 @@ struct CurlGlue { CURLM *curl; sd_event_source *timer; Hashmap *ios; + sd_event_source *defer; void (*on_finished)(CurlGlue *g, CURL *curl, CURLcode code); void *userdata; diff --git a/src/import/export.c b/src/import/export.c index 7e941a2..cdb1d62 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -14,6 +14,7 @@ #include "fd-util.h" #include "fs-util.h" #include "hostname-util.h" +#include "import-common.h" #include "import-util.h" #include "main-func.h" #include "signal-util.h" @@ -22,6 +23,7 @@ #include "verbs.h" static ImportCompressType arg_compress = IMPORT_COMPRESS_UNKNOWN; +static ImageClass arg_class = IMAGE_MACHINE; static void determine_compression_from_filename(const char *p) { @@ -43,12 +45,6 @@ static void determine_compression_from_filename(const char *p) { arg_compress = IMPORT_COMPRESS_UNCOMPRESSED; } -static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - log_notice("Transfer aborted."); - sd_event_exit(sd_event_source_get_event(s), EINTR); - return 0; -} - static void on_tar_finished(TarExport *export, int error, void *userdata) { sd_event *event = userdata; assert(export); @@ -67,12 +63,13 @@ static int export_tar(int argc, char *argv[], void *userdata) { _cleanup_close_ int open_fd = -EBADF; int r, fd; - if (hostname_is_valid(argv[1], 0)) { - r = image_find(IMAGE_MACHINE, argv[1], NULL, &image); + local = argv[1]; + if (image_name_is_valid(local)) { + r = image_find(arg_class, local, NULL, &image); if (r == -ENOENT) - return log_error_errno(r, "Machine image %s not found.", argv[1]); + return log_error_errno(r, "Image %s not found.", local); if (r < 0) - return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]); + return log_error_errno(r, "Failed to look for image %s: %m", local); local = image->path; } else @@ -101,13 +98,9 @@ static int export_tar(int argc, char *argv[], void *userdata) { log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress)); } - r = sd_event_default(&event); + r = import_allocate_event_with_signals(&event); if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); - (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); + return r; r = tar_export_new(&export, event, on_tar_finished, event); if (r < 0) @@ -143,12 +136,13 @@ static int export_raw(int argc, char *argv[], void *userdata) { _cleanup_close_ int open_fd = -EBADF; int r, fd; - if (hostname_is_valid(argv[1], 0)) { - r = image_find(IMAGE_MACHINE, argv[1], NULL, &image); + local = argv[1]; + if (image_name_is_valid(local)) { + r = image_find(arg_class, local, NULL, &image); if (r == -ENOENT) - return log_error_errno(r, "Machine image %s not found.", argv[1]); + return log_error_errno(r, "Image %s not found.", local); if (r < 0) - return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]); + return log_error_errno(r, "Failed to look for image %s: %m", local); local = image->path; } else @@ -177,13 +171,9 @@ static int export_raw(int argc, char *argv[], void *userdata) { log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress)); } - r = sd_event_default(&event); + r = import_allocate_event_with_signals(&event); if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); - (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); + return r; r = raw_export_new(&export, event, on_raw_finished, event); if (r < 0) @@ -203,14 +193,16 @@ static int export_raw(int argc, char *argv[], void *userdata) { static int help(int argc, char *argv[], void *userdata) { printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sExport container or virtual machine images.%5$s\n" + "\n%4$sExport disk images.%5$s\n" "\n%2$sCommands:%3$s\n" " tar NAME [FILE] Export a TAR image\n" " raw NAME [FILE] Export a RAW image\n" "\n%2$sOptions:%3$s\n" " -h --help Show this help\n" " --version Show package version\n" - " --format=FORMAT Select format\n\n", + " --format=FORMAT Select format\n" + " --class=CLASS Select image class (machine, sysext, confext,\n" + " portable)\n", program_invocation_short_name, ansi_underline(), ansi_normal(), @@ -225,12 +217,14 @@ static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_FORMAT, + ARG_CLASS, }; static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, { "format", required_argument, NULL, ARG_FORMAT }, + { "class", required_argument, NULL, ARG_CLASS }, {} }; @@ -263,6 +257,13 @@ static int parse_argv(int argc, char *argv[]) { "Unknown format: %s", optarg); break; + case ARG_CLASS: + arg_class = image_class_from_string(optarg); + if (arg_class < 0) + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); + + break; + case '?': return -EINVAL; @@ -288,8 +289,7 @@ static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/import/import-common.c b/src/import/import-common.c index 319aa07..09faf16 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -276,7 +276,7 @@ bool import_validate_local(const char *name, ImportFlags flags) { if (FLAGS_SET(flags, IMPORT_DIRECT)) return path_is_valid(name); - return hostname_is_valid(name, 0); + return image_name_is_valid(name); } static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { @@ -295,9 +295,8 @@ int import_allocate_event_with_signals(sd_event **ret) { if (r < 0) return log_error_errno(r, "Failed to allocate event loop: %m"); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); - (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); + (void) sd_event_add_signal(event, NULL, SIGTERM|SD_EVENT_SIGNAL_PROCMASK, interrupt_signal_handler, NULL); + (void) sd_event_add_signal(event, NULL, SIGINT|SD_EVENT_SIGNAL_PROCMASK, interrupt_signal_handler, NULL); *ret = TAKE_PTR(event); return 0; diff --git a/src/import/import-common.h b/src/import/import-common.h index 97fc16d..2198024 100644 --- a/src/import/import-common.h +++ b/src/import/import-common.h @@ -6,16 +6,33 @@ #include "sd-event.h" typedef enum ImportFlags { - IMPORT_FORCE = 1 << 0, /* replace existing image */ - IMPORT_READ_ONLY = 1 << 1, /* make generated image read-only */ - IMPORT_BTRFS_SUBVOL = 1 << 2, /* tar: preferably create images as btrfs subvols */ - IMPORT_BTRFS_QUOTA = 1 << 3, /* tar: set up btrfs quota for new subvolume as child of parent subvolume */ - IMPORT_CONVERT_QCOW2 = 1 << 4, /* raw: if we detect a qcow2 image, unpack it */ - IMPORT_DIRECT = 1 << 5, /* import without rename games */ - IMPORT_SYNC = 1 << 6, /* fsync() right before we are done */ - - IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC, - IMPORT_FLAGS_MASK_RAW = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_CONVERT_QCOW2|IMPORT_DIRECT|IMPORT_SYNC, + /* Public Flags (i.e. accessible via D-Bus, must stay stable! */ + IMPORT_FORCE = 1 << 0, /* replace existing image */ + IMPORT_READ_ONLY = 1 << 1, /* make generated image read-only */ + IMPORT_PULL_KEEP_DOWNLOAD = 1 << 2, /* keep a pristine copy of the downloaded file around */ + + /* Private flags */ + IMPORT_BTRFS_SUBVOL = 1 << 3, /* tar: preferably create images as btrfs subvols */ + IMPORT_BTRFS_QUOTA = 1 << 4, /* tar: set up btrfs quota for new subvolume as child of parent subvolume */ + IMPORT_CONVERT_QCOW2 = 1 << 5, /* raw: if we detect a qcow2 image, unpack it */ + IMPORT_DIRECT = 1 << 6, /* import without rename games */ + IMPORT_SYNC = 1 << 7, /* fsync() right before we are done */ + + /* When pulling these flags are defined too */ + IMPORT_PULL_SETTINGS = 1 << 8, /* download .nspawn settings file */ + IMPORT_PULL_ROOTHASH = 1 << 9, /* only for raw: download .roothash file for verity */ + IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 10, /* only for raw: download .roothash.p7s file for verity */ + IMPORT_PULL_VERITY = 1 << 11, /* only for raw: download .verity file for verity */ + + /* The supported flags for the tar and the raw importing */ + IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC, + IMPORT_FLAGS_MASK_RAW = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_CONVERT_QCOW2|IMPORT_DIRECT|IMPORT_SYNC, + + /* The supported flags for the tar and the raw pulling */ + IMPORT_PULL_FLAGS_MASK_TAR = IMPORT_FLAGS_MASK_TAR|IMPORT_PULL_KEEP_DOWNLOAD|IMPORT_PULL_SETTINGS, + IMPORT_PULL_FLAGS_MASK_RAW = IMPORT_FLAGS_MASK_RAW|IMPORT_PULL_KEEP_DOWNLOAD|IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY, + + _IMPORT_FLAGS_INVALID = -EINVAL, } ImportFlags; int import_fork_tar_c(const char *path, pid_t *ret); diff --git a/src/import/import-fs.c b/src/import/import-fs.c index fd79c8f..44fc5be 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -31,7 +31,8 @@ static bool arg_btrfs_subvol = true; static bool arg_btrfs_quota = true; static bool arg_sync = true; static bool arg_direct = false; -static const char *arg_image_root = "/var/lib/machines"; +static const char *arg_image_root = NULL; +static ImageClass arg_class = IMAGE_MACHINE; typedef struct ProgressInfo { RateLimit limit; @@ -132,7 +133,7 @@ static int import_fs(int argc, char *argv[], void *userdata) { "Local path name '%s' is not valid.", final_path); } else { if (local) { - if (!hostname_is_valid(local, 0)) + if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Local image name '%s' is not valid.", local); } else @@ -143,7 +144,7 @@ static int import_fs(int argc, char *argv[], void *userdata) { return log_oom(); if (!arg_force) { - r = image_find(IMAGE_MACHINE, local, NULL, NULL); + r = image_find(arg_class, local, NULL, NULL); if (r < 0) { if (r != -ENOENT) return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); @@ -170,6 +171,8 @@ static int import_fs(int argc, char *argv[], void *userdata) { log_info("Importing '%s', saving as '%s'.", strempty(pretty), local); } + log_info("Operating on image directory '%s'.", arg_image_root); + if (!arg_sync) log_info("File system synchronization on completion is off."); @@ -266,7 +269,9 @@ static int help(int argc, char *argv[], void *userdata) { " instead of a directory\n" " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n" " subvolume\n" - " --sync=BOOL Controls whether to sync() before completing\n", + " --sync=BOOL Controls whether to sync() before completing\n" + " --class=CLASS Select image class (machine, sysext, confext,\n" + " portable)\n", program_invocation_short_name, ansi_underline(), ansi_normal(), @@ -287,6 +292,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_BTRFS_SUBVOL, ARG_BTRFS_QUOTA, ARG_SYNC, + ARG_CLASS, }; static const struct option options[] = { @@ -299,6 +305,7 @@ static int parse_argv(int argc, char *argv[]) { { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL }, { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA }, { "sync", required_argument, NULL, ARG_SYNC }, + { "class", required_argument, NULL, ARG_CLASS }, {} }; @@ -354,6 +361,13 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_CLASS: + arg_class = image_class_from_string(optarg); + if (arg_class < 0) + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); + + break; + case '?': return -EINVAL; @@ -361,6 +375,9 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + if (!arg_image_root) + arg_image_root = image_root_to_string(arg_class); + return 1; } @@ -379,8 +396,7 @@ static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/import/import-raw.c b/src/import/import-raw.c index f7ed163..ee9b297 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -95,8 +95,9 @@ int raw_import_new( int r; assert(ret); + assert(image_root); - root = strdup(image_root ?: "/var/lib/machines"); + root = strdup(image_root); if (!root) return -ENOMEM; diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 9020270..39df11b 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -97,8 +97,9 @@ int tar_import_new( int r; assert(ret); + assert(image_root); - root = strdup(image_root ?: "/var/lib/machines"); + root = strdup(image_root); if (!root) return -ENOMEM; diff --git a/src/import/import.c b/src/import/import.c index a81617d..889cd63 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -25,9 +25,10 @@ #include "terminal-util.h" #include "verbs.h" -static const char *arg_image_root = "/var/lib/machines"; +static const char *arg_image_root = NULL; static ImportFlags arg_import_flags = IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC; static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX; +static ImageClass arg_class = IMAGE_MACHINE; static int normalize_local(const char *local, char **ret) { _cleanup_free_ char *ll = NULL; @@ -53,7 +54,7 @@ static int normalize_local(const char *local, char **ret) { "Local path name '%s' is not valid.", local); } else { if (local) { - if (!hostname_is_valid(local, 0)) + if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Local image name '%s' is not valid.", local); @@ -61,7 +62,7 @@ static int normalize_local(const char *local, char **ret) { local = "imported"; if (!FLAGS_SET(arg_import_flags, IMPORT_FORCE)) { - r = image_find(IMAGE_MACHINE, local, NULL, NULL); + r = image_find(arg_class, local, NULL, NULL); if (r < 0) { if (r != -ENOENT) return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); @@ -113,6 +114,12 @@ static int open_source(const char *path, const char *local, int *ret_open_fd) { log_info("Importing '%s', saving as '%s'.", strempty(pretty), local); } + if (!FLAGS_SET(arg_import_flags, IMPORT_DIRECT)) + log_info("Operating on image directory '%s'.", arg_image_root); + + if (!FLAGS_SET(arg_import_flags, IMPORT_SYNC)) + log_info("File system synchronization on completion is off."); + *ret_open_fd = TAKE_FD(open_fd); return retval; } @@ -160,15 +167,12 @@ static int import_tar(int argc, char *argv[], void *userdata) { fd = open_source(path, normalized, &open_fd); if (fd < 0) - return r; + return fd; r = import_allocate_event_with_signals(&event); if (r < 0) return r; - if (!FLAGS_SET(arg_import_flags, IMPORT_SYNC)) - log_info("File system synchronization on completion is off."); - r = tar_import_new(&import, event, arg_image_root, on_tar_finished, event); if (r < 0) return log_error_errno(r, "Failed to allocate importer: %m"); @@ -238,9 +242,6 @@ static int import_raw(int argc, char *argv[], void *userdata) { if (r < 0) return r; - if (!FLAGS_SET(arg_import_flags, IMPORT_SYNC)) - log_info("File system synchronization on completion is off."); - r = raw_import_new(&import, event, arg_image_root, on_raw_finished, event); if (r < 0) return log_error_errno(r, "Failed to allocate importer: %m"); @@ -266,7 +267,7 @@ static int import_raw(int argc, char *argv[], void *userdata) { static int help(int argc, char *argv[], void *userdata) { printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sImport container or virtual machine images.%5$s\n" + "\n%4$sImport disk images.%5$s\n" "\n%2$sCommands:%3$s\n" " tar FILE [NAME] Import a TAR image\n" " raw FILE [NAME] Import a RAW image\n" @@ -285,7 +286,9 @@ static int help(int argc, char *argv[], void *userdata) { " regular disk images\n" " --sync=BOOL Controls whether to sync() before completing\n" " --offset=BYTES Offset to seek to in destination\n" - " --size-max=BYTES Maximum number of bytes to write to destination\n", + " --size-max=BYTES Maximum number of bytes to write to destination\n" + " --class=CLASS Select image class (machine, sysext, confext,\n" + " portable)\n", program_invocation_short_name, ansi_underline(), ansi_normal(), @@ -309,6 +312,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_SYNC, ARG_OFFSET, ARG_SIZE_MAX, + ARG_CLASS, }; static const struct option options[] = { @@ -324,6 +328,7 @@ static int parse_argv(int argc, char *argv[]) { { "sync", required_argument, NULL, ARG_SYNC }, { "offset", required_argument, NULL, ARG_OFFSET }, { "size-max", required_argument, NULL, ARG_SIZE_MAX }, + { "class", required_argument, NULL, ARG_CLASS }, {} }; @@ -416,6 +421,13 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_CLASS: + arg_class = image_class_from_string(optarg); + if (arg_class < 0) + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); + + break; + case '?': return -EINVAL; @@ -432,6 +444,9 @@ static int parse_argv(int argc, char *argv[]) { if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_import_flags, IMPORT_DIRECT)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode."); + if (!arg_image_root) + arg_image_root = image_root_to_string(arg_class); + return 1; } @@ -475,8 +490,7 @@ static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); + log_setup(); parse_env(); diff --git a/src/import/importctl.c b/src/import/importctl.c new file mode 100644 index 0000000..f939d80 --- /dev/null +++ b/src/import/importctl.c @@ -0,0 +1,1245 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <getopt.h> + +#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); diff --git a/src/import/importd.c b/src/import/importd.c index e1a1ddc..3bfa3cd 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -6,21 +6,27 @@ #include "sd-bus.h" #include "alloc-util.h" +#include "build-path.h" #include "bus-common-errors.h" #include "bus-get-properties.h" #include "bus-log-control-api.h" #include "bus-polkit.h" #include "common-signal.h" #include "constants.h" +#include "daemon-util.h" +#include "discover-image.h" #include "env-util.h" +#include "event-util.h" #include "fd-util.h" #include "float.h" #include "hostname-util.h" +#include "import-common.h" #include "import-util.h" #include "machine-pool.h" #include "main-func.h" #include "missing_capability.h" #include "mkdir-label.h" +#include "os-util.h" #include "parse-util.h" #include "path-util.h" #include "percent-util.h" @@ -61,12 +67,11 @@ struct Transfer { char *remote; char *local; - bool force_local; - bool read_only; - + ImageClass class; + ImportFlags flags; char *format; - pid_t pid; + PidRef pidref; int log_fd; @@ -78,6 +83,7 @@ struct Transfer { unsigned n_canceled; unsigned progress_percent; + unsigned progress_percent_sent; int stdin_fd; int stdout_fd; @@ -105,11 +111,11 @@ struct Manager { static const char* const transfer_type_table[_TRANSFER_TYPE_MAX] = { [TRANSFER_IMPORT_TAR] = "import-tar", [TRANSFER_IMPORT_RAW] = "import-raw", - [TRANSFER_IMPORT_FS] = "import-fs", + [TRANSFER_IMPORT_FS] = "import-fs", [TRANSFER_EXPORT_TAR] = "export-tar", [TRANSFER_EXPORT_RAW] = "export-raw", - [TRANSFER_PULL_TAR] = "pull-tar", - [TRANSFER_PULL_RAW] = "pull-raw", + [TRANSFER_PULL_TAR] = "pull-tar", + [TRANSFER_PULL_RAW] = "pull-raw", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(transfer_type, TransferType); @@ -129,8 +135,7 @@ static Transfer *transfer_unref(Transfer *t) { free(t->format); free(t->object_path); - if (t->pid > 1) - sigkill_wait(t->pid); + pidref_done_sigkill_wait(&t->pidref); safe_close(t->log_fd); safe_close(t->stdin_fd); @@ -162,7 +167,8 @@ static int transfer_new(Manager *m, Transfer **ret) { .stdin_fd = -EBADF, .stdout_fd = -EBADF, .verify = _IMPORT_VERIFY_INVALID, - .progress_percent= UINT_MAX, + .progress_percent = UINT_MAX, + .progress_percent_sent = UINT_MAX, }; id = m->current_transfer_id + 1; @@ -213,7 +219,28 @@ static void transfer_send_log_line(Transfer *t, const char *line) { line); if (r < 0) log_warning_errno(r, "Cannot emit log message signal, ignoring: %m"); - } +} + +static void transfer_send_progress_update(Transfer *t) { + int r; + + assert(t); + + if (t->progress_percent_sent == t->progress_percent) + return; + + r = sd_bus_emit_signal( + t->manager->bus, + t->object_path, + "org.freedesktop.import1.Transfer", + "ProgressUpdate", + "d", + transfer_percent_as_double(t)); + if (r < 0) + log_warning_errno(r, "Cannot emit progress update signal, ignoring: %m"); + + t->progress_percent_sent = t->progress_percent; +} static void transfer_send_logs(Transfer *t, bool flush) { assert(t); @@ -300,7 +327,7 @@ static int transfer_cancel(Transfer *t) { assert(t); - r = kill_and_sigcont(t->pid, t->n_canceled < 3 ? SIGTERM : SIGKILL); + r = pidref_kill_and_sigcont(&t->pidref, t->n_canceled < 3 ? SIGTERM : SIGKILL); if (r < 0) return r; @@ -327,7 +354,7 @@ static int transfer_on_pid(sd_event_source *s, const siginfo_t *si, void *userda else log_error("Transfer process failed due to unknown reason."); - t->pid = 0; + pidref_done(&t->pidref); return transfer_finalize(t, success); } @@ -361,15 +388,17 @@ static int transfer_start(Transfer *t) { int r; assert(t); - assert(t->pid <= 0); + assert(!pidref_is_set(&t->pidref)); if (pipe2(pipefd, O_CLOEXEC) < 0) return -errno; - r = safe_fork_full("(sd-transfer)", - (int[]) { t->stdin_fd, t->stdout_fd < 0 ? pipefd[1] : t->stdout_fd, pipefd[1] }, - NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO, &t->pid); + r = pidref_safe_fork_full( + "(sd-transfer)", + (int[]) { t->stdin_fd, t->stdout_fd < 0 ? pipefd[1] : t->stdout_fd, pipefd[1] }, + /* except_fds= */ NULL, /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_REOPEN_LOG, + &t->pidref); if (r < 0) return r; if (r == 0) { @@ -378,6 +407,9 @@ static int transfer_start(Transfer *t) { NULL, /* tar, raw */ NULL, /* --verify= */ NULL, /* verify argument */ + NULL, /* --class= */ + NULL, /* class argument */ + NULL, /* --keep-download= */ NULL, /* maybe --force */ NULL, /* maybe --read-only */ NULL, /* if so: the actual URL */ @@ -387,7 +419,7 @@ static int transfer_start(Transfer *t) { NULL, /* local */ NULL }; - unsigned k = 0; + size_t k = 0; /* Child */ @@ -397,6 +429,10 @@ static int transfer_start(Transfer *t) { _exit(EXIT_FAILURE); } + r = setenv_systemd_log_level(); + if (r < 0) + log_warning_errno(r, "Failed to update $SYSTEMD_LOG_LEVEL, ignoring: %m"); + r = setenv_systemd_exec_pid(true); if (r < 0) log_warning_errno(r, "Failed to update $SYSTEMD_EXEC_PID, ignoring: %m"); @@ -453,9 +489,18 @@ static int transfer_start(Transfer *t) { cmd[k++] = import_verify_to_string(t->verify); } - if (t->force_local) + if (t->class != IMAGE_MACHINE) { + cmd[k++] = "--class"; + cmd[k++] = image_class_to_string(t->class); + } + + if (IN_SET(t->type, TRANSFER_PULL_TAR, TRANSFER_PULL_RAW)) + cmd[k++] = FLAGS_SET(t->flags, IMPORT_PULL_KEEP_DOWNLOAD) ? + "--keep-download=yes" : "--keep-download=no"; + + if (FLAGS_SET(t->flags, IMPORT_FORCE)) cmd[k++] = "--force"; - if (t->read_only) + if (FLAGS_SET(t->flags, IMPORT_READ_ONLY)) cmd[k++] = "--read-only"; if (t->format) { @@ -474,8 +519,15 @@ static int transfer_start(Transfer *t) { cmd[k++] = t->local; cmd[k] = NULL; - execv(cmd[0], (char * const *) cmd); - log_error_errno(errno, "Failed to execute %s tool: %m", cmd[0]); + assert(k < ELEMENTSOF(cmd)); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *joined = strv_join((char**) cmd, " "); + log_debug("Calling: %s", strnull(joined)); + } + + r = invoke_callout_binary(cmd[0], (char * const *) cmd); + log_error_errno(r, "Failed to execute %s tool: %m", cmd[0]); _exit(EXIT_FAILURE); } @@ -484,8 +536,13 @@ static int transfer_start(Transfer *t) { t->stdin_fd = safe_close(t->stdin_fd); - r = sd_event_add_child(t->manager->event, &t->pid_event_source, - t->pid, WEXITED, transfer_on_pid, t); + r = event_add_child_pidref( + t->manager->event, + &t->pid_event_source, + &t->pidref, + WEXITED, + transfer_on_pid, + t); if (r < 0) return r; @@ -527,7 +584,7 @@ static Manager *manager_unref(Manager *m) { hashmap_free(m->transfers); - bus_verify_polkit_async_registry_free(m->polkit_registry); + hashmap_free(m->polkit_registry); m->bus = sd_bus_flush_close_unref(m->bus); sd_event_unref(m->event); @@ -580,7 +637,7 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void } HASHMAP_FOREACH(t, m->transfers) - if (ucred->pid == t->pid) + if (ucred->pid == t->pidref.pid) break; if (!t) { @@ -605,6 +662,8 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void t->progress_percent = (unsigned) r; log_debug("Got percentage from client: %u%%", t->progress_percent); + + transfer_send_progress_update(t); return 0; } @@ -631,17 +690,11 @@ static int manager_new(Manager **ret) { if (r < 0) return r; - (void) sd_event_set_watchdog(m->event, true); - - r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); - if (r < 0) - return r; - - r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); + r = sd_event_set_signal_exit(m->event, true); if (r < 0) return r; - r = sd_event_add_signal(m->event, NULL, SIGRTMIN+18, sigrtmin18_handler, NULL); + r = sd_event_add_signal(m->event, NULL, (SIGRTMIN+18)|SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, NULL); if (r < 0) return r; @@ -649,6 +702,10 @@ static int manager_new(Manager **ret) { if (r < 0) log_debug_errno(r, "Failed allocate memory pressure event source, ignoring: %m"); + r = sd_event_set_watchdog(m->event, true); + if (r < 0) + log_debug_errno(r, "Failed to enable watchdog logic, ignoring: %m"); + r = sd_bus_default_system(&m->bus); if (r < 0) return r; @@ -693,22 +750,20 @@ static Transfer *manager_find(Manager *m, TransferType type, const char *remote) static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_error *error) { _cleanup_(transfer_unrefp) Transfer *t = NULL; - int fd, force, read_only, r; - const char *local, *object; + ImageClass class = _IMAGE_CLASS_INVALID; Manager *m = ASSERT_PTR(userdata); + const char *local; TransferType type; struct stat st; - uint32_t id; + uint64_t flags; + int fd, r; assert(msg); r = bus_verify_polkit_async( msg, - CAP_SYS_ADMIN, "org.freedesktop.import1.import", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -716,7 +771,36 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ if (r == 0) return 1; /* Will call us back */ - r = sd_bus_message_read(msg, "hsbb", &fd, &local, &force, &read_only); + if (endswith(sd_bus_message_get_member(msg), "Ex")) { + const char *sclass; + + r = sd_bus_message_read(msg, "hsst", &fd, &local, &sclass, &flags); + if (r < 0) + return r; + + class = image_class_from_string(sclass); + if (class < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Image class '%s' not known", sclass); + + if (flags & ~(IMPORT_READ_ONLY|IMPORT_FORCE)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Flags 0x%" PRIx64 " invalid", flags); + } else { + int force, read_only; + + r = sd_bus_message_read(msg, "hsbb", &fd, &local, &force, &read_only); + if (r < 0) + return r; + + class = IMAGE_MACHINE; + + flags = 0; + SET_FLAG(flags, IMPORT_FORCE, force); + SET_FLAG(flags, IMPORT_READ_ONLY, read_only); + } + + r = fd_verify_safe_flags(fd); if (r < 0) return r; @@ -726,15 +810,17 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ if (!S_ISREG(st.st_mode) && !S_ISFIFO(st.st_mode)) return -EINVAL; - if (!hostname_is_valid(local, 0)) + if (!image_name_is_valid(local)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Local name %s is invalid", local); + "Local image name %s is invalid", local); - r = setup_machine_directory(error, m->use_btrfs_subvol, m->use_btrfs_quota); - if (r < 0) - return r; + if (class == IMAGE_MACHINE) { + r = setup_machine_directory(error, m->use_btrfs_subvol, m->use_btrfs_quota); + if (r < 0) + return r; + } - type = streq_ptr(sd_bus_message_get_member(msg), "ImportTar") ? + type = startswith(sd_bus_message_get_member(msg), "ImportTar") ? TRANSFER_IMPORT_TAR : TRANSFER_IMPORT_RAW; r = transfer_new(m, &t); @@ -742,8 +828,8 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ return r; t->type = type; - t->force_local = force; - t->read_only = read_only; + t->class = class; + t->flags = flags; t->local = strdup(local); if (!t->local) @@ -757,29 +843,28 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ if (r < 0) return r; - object = t->object_path; - id = t->id; - t = NULL; + r = sd_bus_reply_method_return(msg, "uo", t->id, t->object_path); + if (r < 0) + return r; - return sd_bus_reply_method_return(msg, "uo", id, object); + TAKE_PTR(t); + return 1; } static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *error) { _cleanup_(transfer_unrefp) Transfer *t = NULL; - int fd, force, read_only, r; - const char *local, *object; + ImageClass class = _IMAGE_CLASS_INVALID; Manager *m = ASSERT_PTR(userdata); - uint32_t id; + const char *local; + uint64_t flags; + int fd, r; assert(msg); r = bus_verify_polkit_async( msg, - CAP_SYS_ADMIN, "org.freedesktop.import1.import", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -787,7 +872,36 @@ static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *e if (r == 0) return 1; /* Will call us back */ - r = sd_bus_message_read(msg, "hsbb", &fd, &local, &force, &read_only); + if (endswith(sd_bus_message_get_member(msg), "Ex")) { + const char *sclass; + + r = sd_bus_message_read(msg, "hsst", &fd, &local, &sclass, &flags); + if (r < 0) + return r; + + class = image_class_from_string(sclass); + if (class < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Image class '%s' not known", sclass); + + if (flags & ~(IMPORT_READ_ONLY|IMPORT_FORCE)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Flags 0x%" PRIx64 " invalid", flags); + } else { + int force, read_only; + + r = sd_bus_message_read(msg, "hsbb", &fd, &local, &force, &read_only); + if (r < 0) + return r; + + class = IMAGE_MACHINE; + + flags = 0; + SET_FLAG(flags, IMPORT_FORCE, force); + SET_FLAG(flags, IMPORT_READ_ONLY, read_only); + } + + r = fd_verify_safe_flags_full(fd, O_DIRECTORY); if (r < 0) return r; @@ -795,21 +909,23 @@ static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *e if (r < 0) return r; - if (!hostname_is_valid(local, 0)) + if (!image_name_is_valid(local)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Local name %s is invalid", local); + "Local image name %s is invalid", local); - r = setup_machine_directory(error, m->use_btrfs_subvol, m->use_btrfs_quota); - if (r < 0) - return r; + if (class == IMAGE_MACHINE) { + r = setup_machine_directory(error, m->use_btrfs_subvol, m->use_btrfs_quota); + if (r < 0) + return r; + } r = transfer_new(m, &t); if (r < 0) return r; t->type = TRANSFER_IMPORT_FS; - t->force_local = force; - t->read_only = read_only; + t->class = class; + t->flags = flags; t->local = strdup(local); if (!t->local) @@ -823,31 +939,30 @@ static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *e if (r < 0) return r; - object = t->object_path; - id = t->id; - t = NULL; + r = sd_bus_reply_method_return(msg, "uo", t->id, t->object_path); + if (r < 0) + return r; - return sd_bus_reply_method_return(msg, "uo", id, object); + TAKE_PTR(t); + return 1; } static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_error *error) { _cleanup_(transfer_unrefp) Transfer *t = NULL; - int fd, r; - const char *local, *object, *format; + ImageClass class = _IMAGE_CLASS_INVALID; Manager *m = ASSERT_PTR(userdata); + const char *local, *format; TransferType type; + uint64_t flags; struct stat st; - uint32_t id; + int fd, r; assert(msg); r = bus_verify_polkit_async( msg, - CAP_SYS_ADMIN, "org.freedesktop.import1.export", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -855,13 +970,37 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ if (r == 0) return 1; /* Will call us back */ - r = sd_bus_message_read(msg, "shs", &local, &fd, &format); - if (r < 0) - return r; + if (endswith(sd_bus_message_get_member(msg), "Ex")) { + const char *sclass; + + r = sd_bus_message_read(msg, "sshst", &local, &sclass, &fd, &format, &flags); + if (r < 0) + return r; - if (!hostname_is_valid(local, 0)) + class = image_class_from_string(sclass); + if (class < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Image class '%s' not known", sclass); + + if (flags != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Flags 0x%" PRIx64 " invalid", flags); + } else { + r = sd_bus_message_read(msg, "shs", &local, &fd, &format); + if (r < 0) + return r; + + class = IMAGE_MACHINE; + flags = 0; + } + + if (!image_name_is_valid(local)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Local name %s is invalid", local); + "Local image name %s is invalid", local); + + r = fd_verify_safe_flags(fd); + if (r < 0) + return r; if (fstat(fd, &st) < 0) return -errno; @@ -869,7 +1008,7 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ if (!S_ISREG(st.st_mode) && !S_ISFIFO(st.st_mode)) return -EINVAL; - type = streq_ptr(sd_bus_message_get_member(msg), "ExportTar") ? + type = startswith(sd_bus_message_get_member(msg), "ExportTar") ? TRANSFER_EXPORT_TAR : TRANSFER_EXPORT_RAW; r = transfer_new(m, &t); @@ -877,6 +1016,8 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ return r; t->type = type; + t->class = class; + t->flags = flags; if (!isempty(format)) { t->format = strdup(format); @@ -896,31 +1037,30 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ if (r < 0) return r; - object = t->object_path; - id = t->id; - t = NULL; + r = sd_bus_reply_method_return(msg, "uo", t->id, t->object_path); + if (r < 0) + return r; - return sd_bus_reply_method_return(msg, "uo", id, object); + TAKE_PTR(t); + return 1; } static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_error *error) { _cleanup_(transfer_unrefp) Transfer *t = NULL; - const char *remote, *local, *verify, *object; + ImageClass class = _IMAGE_CLASS_INVALID; + const char *remote, *local, *verify; Manager *m = ASSERT_PTR(userdata); - ImportVerify v; TransferType type; - int force, r; - uint32_t id; + uint64_t flags; + ImportVerify v; + int r; assert(msg); r = bus_verify_polkit_async( msg, - CAP_SYS_ADMIN, "org.freedesktop.import1.pull", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -928,9 +1068,33 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er if (r == 0) return 1; /* Will call us back */ - r = sd_bus_message_read(msg, "sssb", &remote, &local, &verify, &force); - if (r < 0) - return r; + if (endswith(sd_bus_message_get_member(msg), "Ex")) { + const char *sclass; + + r = sd_bus_message_read(msg, "sssst", &remote, &local, &sclass, &verify, &flags); + if (r < 0) + return r; + + class = image_class_from_string(sclass); + if (class < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Image class '%s' not known", sclass); + + if (flags & ~(IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Flags 0x%" PRIx64 " invalid", flags); + } else { + int force; + + r = sd_bus_message_read(msg, "sssb", &remote, &local, &verify, &force); + if (r < 0) + return r; + + class = IMAGE_MACHINE; + + flags = 0; + SET_FLAG(flags, IMPORT_FORCE, force); + } if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, @@ -938,9 +1102,9 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er if (isempty(local)) local = NULL; - else if (!hostname_is_valid(local, 0)) + else if (!image_name_is_valid(local)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Local name %s is invalid", local); + "Local image name %s is invalid", local); if (isempty(verify)) v = IMPORT_VERIFY_SIGNATURE; @@ -950,11 +1114,13 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify); - r = setup_machine_directory(error, m->use_btrfs_subvol, m->use_btrfs_quota); - if (r < 0) - return r; + if (class == IMAGE_MACHINE) { + r = setup_machine_directory(error, m->use_btrfs_subvol, m->use_btrfs_quota); + if (r < 0) + return r; + } - type = streq_ptr(sd_bus_message_get_member(msg), "PullTar") ? + type = startswith(sd_bus_message_get_member(msg), "PullTar") ? TRANSFER_PULL_TAR : TRANSFER_PULL_RAW; if (manager_find(m, type, remote)) @@ -967,7 +1133,8 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er t->type = type; t->verify = v; - t->force_local = force; + t->flags = flags; + t->class = class; t->remote = strdup(remote); if (!t->remote) @@ -983,40 +1150,81 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er if (r < 0) return r; - object = t->object_path; - id = t->id; - t = NULL; + r = sd_bus_reply_method_return(msg, "uo", t->id, t->object_path); + if (r < 0) + return r; - return sd_bus_reply_method_return(msg, "uo", id, object); + TAKE_PTR(t); + return 1; } static int method_list_transfers(sd_bus_message *msg, void *userdata, sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; Manager *m = ASSERT_PTR(userdata); + ImageClass class = _IMAGE_CLASS_INVALID; Transfer *t; int r; assert(msg); + bool ex = endswith(sd_bus_message_get_member(msg), "Ex"); + if (ex) { + const char *sclass; + uint64_t flags; + + r = sd_bus_message_read(msg, "st", &sclass, &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (!isempty(sclass)) { + class = image_class_from_string(sclass); + if (class < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Image class '%s' not known", sclass); + } + + if (flags != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Flags 0x%" PRIx64 " invalid", flags); + } + r = sd_bus_message_new_method_return(msg, &reply); if (r < 0) return r; - r = sd_bus_message_open_container(reply, 'a', "(usssdo)"); + if (ex) + r = sd_bus_message_open_container(reply, 'a', "(ussssdo)"); + else + r = sd_bus_message_open_container(reply, 'a', "(usssdo)"); if (r < 0) return r; HASHMAP_FOREACH(t, m->transfers) { - r = sd_bus_message_append( - reply, - "(usssdo)", - t->id, - transfer_type_to_string(t->type), - t->remote, - t->local, - transfer_percent_as_double(t), - t->object_path); + if (class >= 0 && class != t->class) + continue; + + if (ex) + r = sd_bus_message_append( + reply, + "(ussssdo)", + t->id, + transfer_type_to_string(t->type), + t->remote, + t->local, + image_class_to_string(t->class), + transfer_percent_as_double(t), + t->object_path); + else + r = sd_bus_message_append( + reply, + "(usssdo)", + t->id, + transfer_type_to_string(t->type), + t->remote, + t->local, + transfer_percent_as_double(t), + t->object_path); if (r < 0) return r; } @@ -1036,11 +1244,8 @@ static int method_cancel(sd_bus_message *msg, void *userdata, sd_bus_error *erro r = bus_verify_polkit_async( msg, - CAP_SYS_ADMIN, "org.freedesktop.import1.pull", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &t->manager->polkit_registry, error); if (r < 0) @@ -1065,11 +1270,8 @@ static int method_cancel_transfer(sd_bus_message *msg, void *userdata, sd_bus_er r = bus_verify_polkit_async( msg, - CAP_SYS_ADMIN, - "org.freedesktop.import1.pull", - NULL, - false, - UID_INVALID, + "org.freedesktop.import1.cancel", + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -1094,6 +1296,86 @@ static int method_cancel_transfer(sd_bus_message *msg, void *userdata, sd_bus_er return sd_bus_reply_method_return(msg, NULL); } +static int method_list_images(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + ImageClass class = _IMAGE_CLASS_INVALID; + int r; + + assert(msg); + + const char *sclass; + uint64_t flags; + + r = sd_bus_message_read(msg, "st", &sclass, &flags); + if (r < 0) + return r; + + if (!isempty(sclass)) { + class = image_class_from_string(sclass); + if (class < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Image class '%s' not known", sclass); + } + + if (flags != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Flags 0x%" PRIx64 " invalid", flags); + + r = sd_bus_message_new_method_return(msg, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(ssssbtttttt)"); + if (r < 0) + return r; + + for (ImageClass c = class < 0 ? 0 : class; + class < 0 ? (c < _IMAGE_CLASS_MAX) : (c == class); + c++) { + + _cleanup_(hashmap_freep) Hashmap *h = NULL; + + h = hashmap_new(&image_hash_ops); + if (!h) + return -ENOMEM; + + r = image_discover(c, /* root= */ NULL, h); + if (r < 0) { + if (class >= 0) + return r; + + log_warning_errno(r, "Failed to discover images of type %s: %m", image_class_to_string(c)); + continue; + } + + Image *i; + HASHMAP_FOREACH(i, h) { + r = sd_bus_message_append( + reply, + "(ssssbtttttt)", + image_class_to_string(i->class), + i->name, + image_type_to_string(i->type), + i->path, + i->read_only, + i->crtime, + i->mtime, + i->usage, + i->usage_exclusive, + i->limit, + i->limit_exclusive); + if (r < 0) + return r; + } + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + static int property_get_progress( sd_bus *bus, const char *path, @@ -1196,6 +1478,10 @@ static const sd_bus_vtable transfer_vtable[] = { SD_BUS_PARAM(priority) SD_BUS_PARAM(line), 0), + SD_BUS_SIGNAL_WITH_NAMES("ProgressUpdate", + "d", + SD_BUS_PARAM(progress), + 0), SD_BUS_VTABLE_END, }; @@ -1221,6 +1507,17 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_import_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ImportTarEx", + "hsst", + SD_BUS_PARAM(fd) + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_import_tar_or_raw, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("ImportRaw", "hsbb", SD_BUS_PARAM(fd) @@ -1232,6 +1529,17 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_import_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ImportRawEx", + "hsst", + SD_BUS_PARAM(fd) + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_import_tar_or_raw, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("ImportFileSystem", "hsbb", SD_BUS_PARAM(fd) @@ -1243,6 +1551,17 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_import_fs, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ImportFileSystemEx", + "hsst", + SD_BUS_PARAM(fd) + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_import_fs, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("ExportTar", "shs", SD_BUS_PARAM(local_name) @@ -1253,6 +1572,18 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_export_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ExportTarEx", + "sshst", + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(fd) + SD_BUS_PARAM(format) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_export_tar_or_raw, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("ExportRaw", "shs", SD_BUS_PARAM(local_name) @@ -1263,6 +1594,18 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_export_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ExportRawEx", + "sshst", + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(fd) + SD_BUS_PARAM(format) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_export_tar_or_raw, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("PullTar", "sssb", SD_BUS_PARAM(url) @@ -1274,6 +1617,18 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("PullTarEx", + "sssst", + SD_BUS_PARAM(url) + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(verify_mode) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_pull_tar_or_raw, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("PullRaw", "sssb", SD_BUS_PARAM(url) @@ -1285,18 +1640,46 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("PullRawEx", + "sssst", + SD_BUS_PARAM(url) + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(verify_mode) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_pull_tar_or_raw, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("ListTransfers", NULL,, "a(usssdo)", SD_BUS_PARAM(transfers), method_list_transfers, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ListTransfersEx", + "st", + SD_BUS_PARAM(class) + SD_BUS_PARAM(flags), + "a(ussssdo)", + SD_BUS_PARAM(transfers), + method_list_transfers, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("CancelTransfer", "u", SD_BUS_PARAM(transfer_id), NULL,, method_cancel_transfer, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ListImages", + "st", + SD_BUS_PARAM(class) + SD_BUS_PARAM(flags), + "a(ssssbtttttt)", + SD_BUS_PARAM(images), + method_list_images, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_SIGNAL_WITH_NAMES("TransferNew", "uo", @@ -1350,18 +1733,6 @@ static bool manager_check_idle(void *userdata) { return hashmap_isempty(m->transfers); } -static int manager_run(Manager *m) { - assert(m); - - return bus_event_loop_with_idle( - m->event, - m->bus, - "org.freedesktop.import1", - DEFAULT_EXIT_USEC, - manager_check_idle, - m); -} - static void manager_parse_env(Manager *m) { int r; @@ -1400,7 +1771,7 @@ static int run(int argc, char *argv[]) { umask(0022); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0); r = manager_new(&m); if (r < 0) @@ -1412,7 +1783,17 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - r = manager_run(m); + r = sd_notify(false, NOTIFY_READY); + if (r < 0) + log_warning_errno(r, "Failed to send readiness notification, ignoring: %m"); + + r = bus_event_loop_with_idle( + m->event, + m->bus, + "org.freedesktop.import1", + DEFAULT_EXIT_USEC, + manager_check_idle, + m); if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); diff --git a/src/import/meson.build b/src/import/meson.build index 3f0acf8..184dd7b 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -1,5 +1,9 @@ # SPDX-License-Identifier: LGPL-2.1-or-later +if conf.get('ENABLE_IMPORTD') != 1 + subdir_done() +endif + systemd_importd_sources = files( 'importd.c', ) @@ -100,6 +104,12 @@ executables += [ 'link_with' : common_libs, 'dependencies' : common_deps, }, + executable_template + { + 'name' : 'importctl', + 'public' : true, + 'conditions' : ['ENABLE_IMPORTD'], + 'sources' : files('importctl.c'), + }, test_template + { 'sources' : files( 'test-qcow2.c', @@ -111,15 +121,13 @@ executables += [ }, ] -if conf.get('ENABLE_IMPORTD') == 1 - install_data('org.freedesktop.import1.conf', - install_dir : dbuspolicydir) - install_data('org.freedesktop.import1.service', - install_dir : dbussystemservicedir) - install_data('org.freedesktop.import1.policy', - install_dir : polkitpolicydir) +install_data('org.freedesktop.import1.conf', + install_dir : dbuspolicydir) +install_data('org.freedesktop.import1.service', + install_dir : dbussystemservicedir) +install_data('org.freedesktop.import1.policy', + install_dir : polkitpolicydir) - install_data('import-pubring.gpg', - install_dir : libexecdir) - # TODO: shouldn't this be in pkgdatadir? -endif +install_data('import-pubring.gpg', + install_dir : libexecdir) +# TODO: shouldn't this be in pkgdatadir? diff --git a/src/import/org.freedesktop.import1.conf b/src/import/org.freedesktop.import1.conf index d252ff6..f99ec56 100644 --- a/src/import/org.freedesktop.import1.conf +++ b/src/import/org.freedesktop.import1.conf @@ -44,6 +44,10 @@ <allow send_destination="org.freedesktop.import1" send_interface="org.freedesktop.import1.Manager" + send_member="ListTransfersEx"/> + + <allow send_destination="org.freedesktop.import1" + send_interface="org.freedesktop.import1.Manager" send_member="CancelTransfer"/> <allow send_destination="org.freedesktop.import1" @@ -52,32 +56,64 @@ <allow send_destination="org.freedesktop.import1" send_interface="org.freedesktop.import1.Manager" + send_member="ImportTarEx"/> + + <allow send_destination="org.freedesktop.import1" + send_interface="org.freedesktop.import1.Manager" send_member="ImportRaw"/> <allow send_destination="org.freedesktop.import1" send_interface="org.freedesktop.import1.Manager" + send_member="ImportRawEx"/> + + <allow send_destination="org.freedesktop.import1" + send_interface="org.freedesktop.import1.Manager" send_member="ImportFileSystem"/> <allow send_destination="org.freedesktop.import1" send_interface="org.freedesktop.import1.Manager" + send_member="ImportFileSystemEx"/> + + <allow send_destination="org.freedesktop.import1" + send_interface="org.freedesktop.import1.Manager" send_member="ExportTar"/> <allow send_destination="org.freedesktop.import1" send_interface="org.freedesktop.import1.Manager" + send_member="ExportTarEx"/> + + <allow send_destination="org.freedesktop.import1" + send_interface="org.freedesktop.import1.Manager" send_member="ExportRaw"/> <allow send_destination="org.freedesktop.import1" send_interface="org.freedesktop.import1.Manager" + send_member="ExportRawEx"/> + + <allow send_destination="org.freedesktop.import1" + send_interface="org.freedesktop.import1.Manager" send_member="PullTar"/> <allow send_destination="org.freedesktop.import1" send_interface="org.freedesktop.import1.Manager" + send_member="PullTarEx"/> + + <allow send_destination="org.freedesktop.import1" + send_interface="org.freedesktop.import1.Manager" send_member="PullRaw"/> <allow send_destination="org.freedesktop.import1" + send_interface="org.freedesktop.import1.Manager" + send_member="PullRawEx"/> + + <allow send_destination="org.freedesktop.import1" send_interface="org.freedesktop.import1.Transfer" send_member="Cancel"/> + <allow send_destination="org.freedesktop.import1" + send_interface="org.freedesktop.import1.Transfer" + send_member="ListImages"/> + <allow receive_sender="org.freedesktop.import1"/> </policy> diff --git a/src/import/org.freedesktop.import1.policy b/src/import/org.freedesktop.import1.policy index 88e436d..45c11de 100644 --- a/src/import/org.freedesktop.import1.policy +++ b/src/import/org.freedesktop.import1.policy @@ -19,8 +19,8 @@ <vendor_url>https://systemd.io</vendor_url> <action id="org.freedesktop.import1.import"> - <description gettext-domain="systemd">Import a VM or container image</description> - <message gettext-domain="systemd">Authentication is required to import a VM or container image</message> + <description gettext-domain="systemd">Import a disk image</description> + <message gettext-domain="systemd">Authentication is required to import an image</message> <defaults> <allow_any>auth_admin</allow_any> <allow_inactive>auth_admin</allow_inactive> @@ -29,8 +29,8 @@ </action> <action id="org.freedesktop.import1.export"> - <description gettext-domain="systemd">Export a VM or container image</description> - <message gettext-domain="systemd">Authentication is required to export a VM or container image</message> + <description gettext-domain="systemd">Export a disk image</description> + <message gettext-domain="systemd">Authentication is required to export disk image</message> <defaults> <allow_any>auth_admin</allow_any> <allow_inactive>auth_admin</allow_inactive> @@ -39,8 +39,18 @@ </action> <action id="org.freedesktop.import1.pull"> - <description gettext-domain="systemd">Download a VM or container image</description> - <message gettext-domain="systemd">Authentication is required to download a VM or container image</message> + <description gettext-domain="systemd">Download a disk image</description> + <message gettext-domain="systemd">Authentication is required to download a disk image</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + </action> + + <action id="org.freedesktop.import1.cancel"> + <description gettext-domain="systemd">Cancel transfer of a disk image</description> + <message gettext-domain="systemd">Authentication is required to cancel the ongoing transfer of a disk image</message> <defaults> <allow_any>auth_admin</allow_any> <allow_inactive>auth_admin</allow_inactive> diff --git a/src/import/pull-common.c b/src/import/pull-common.c index 5e1ea20..9a2ced0 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -7,6 +7,7 @@ #include "capability-util.h" #include "copy.h" #include "dirent-util.h" +#include "discover-image.h" #include "escape.h" #include "fd-util.h" #include "hostname-util.h" @@ -37,11 +38,9 @@ int pull_find_old_etags( int r; assert(url); + assert(image_root); assert(etags); - if (!image_root) - image_root = "/var/lib/machines"; - _cleanup_free_ char *escaped_url = xescape(url, FILENAME_ESCAPE); if (!escaped_url) return -ENOMEM; @@ -128,11 +127,9 @@ int pull_make_path(const char *url, const char *etag, const char *image_root, co char *path; assert(url); + assert(image_root); assert(ret); - if (!image_root) - image_root = "/var/lib/machines"; - escaped_url = xescape(url, FILENAME_ESCAPE); if (!escaped_url) return -ENOMEM; @@ -550,7 +547,6 @@ int pull_verify(ImportVerify verify, log_debug("Main download is a checksum file, can't validate its checksum with itself, skipping."); verify_job = main_job; } else { - PullJob *j; assert(main_job->calc_checksum); assert(main_job->checksum); assert(checksum_job); @@ -560,7 +556,8 @@ int pull_verify(ImportVerify verify, return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Checksum is empty, cannot verify."); - FOREACH_POINTER(j, main_job, settings_job, roothash_job, roothash_signature_job, verity_job) { + PullJob *j; + FOREACH_ARGUMENT(j, main_job, settings_job, roothash_job, roothash_signature_job, verity_job) { r = verify_one(checksum_job, j); if (r < 0) return r; @@ -643,12 +640,12 @@ int pull_job_restart_with_sha256sum(PullJob *j, char **ret) { return 1; } -bool pull_validate_local(const char *name, PullFlags flags) { +bool pull_validate_local(const char *name, ImportFlags flags) { - if (FLAGS_SET(flags, PULL_DIRECT)) + if (FLAGS_SET(flags, IMPORT_DIRECT)) return path_is_valid(name); - return hostname_is_valid(name, 0); + return image_name_is_valid(name); } int pull_url_needs_checksum(const char *url) { diff --git a/src/import/pull-common.h b/src/import/pull-common.h index 475613a..82e2526 100644 --- a/src/import/pull-common.h +++ b/src/import/pull-common.h @@ -3,27 +3,10 @@ #include <stdbool.h> +#include "import-common.h" #include "import-util.h" #include "pull-job.h" -typedef enum PullFlags { - PULL_FORCE = 1 << 0, /* replace existing image */ - PULL_READ_ONLY = 1 << 1, /* make generated image read-only */ - PULL_SETTINGS = 1 << 2, /* download .nspawn settings file */ - PULL_ROOTHASH = 1 << 3, /* only for raw: download .roothash file for verity */ - PULL_ROOTHASH_SIGNATURE = 1 << 4, /* only for raw: download .roothash.p7s file for verity */ - PULL_VERITY = 1 << 5, /* only for raw: download .verity file for verity */ - PULL_BTRFS_SUBVOL = 1 << 6, /* tar: preferably create images as btrfs subvols */ - PULL_BTRFS_QUOTA = 1 << 7, /* tar: set up btrfs quota for new subvolume as child of parent subvolume */ - PULL_CONVERT_QCOW2 = 1 << 8, /* raw: if we detect a qcow2 image, unpack it */ - PULL_DIRECT = 1 << 9, /* download without rename games */ - PULL_SYNC = 1 << 10, /* fsync() right before we are done */ - - /* The supported flags for the tar and the raw pulling */ - PULL_FLAGS_MASK_TAR = PULL_FORCE|PULL_READ_ONLY|PULL_SETTINGS|PULL_BTRFS_SUBVOL|PULL_BTRFS_QUOTA|PULL_DIRECT|PULL_SYNC, - PULL_FLAGS_MASK_RAW = PULL_FORCE|PULL_READ_ONLY|PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY|PULL_CONVERT_QCOW2|PULL_DIRECT|PULL_SYNC, -} PullFlags; - int pull_find_old_etags(const char *url, const char *root, int dt, const char *prefix, const char *suffix, char ***etags); int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret); @@ -44,6 +27,6 @@ int verification_style_from_url(const char *url, VerificationStyle *style); int pull_job_restart_with_sha256sum(PullJob *job, char **ret); -bool pull_validate_local(const char *name, PullFlags flags); +bool pull_validate_local(const char *name, ImportFlags flags); int pull_url_needs_checksum(const char *url); diff --git a/src/import/pull-job.c b/src/import/pull-job.c index bed7e64..8482551 100644 --- a/src/import/pull-job.c +++ b/src/import/pull-job.c @@ -187,7 +187,7 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { } } - r = log_error_errno( + r = log_notice_errno( status == 404 ? SYNTHETIC_ERRNO(ENOMEDIUM) : SYNTHETIC_ERRNO(EIO), /* Make the most common error recognizable */ "HTTP request to %s failed with code %li.", j->url, status); goto finish; @@ -431,7 +431,9 @@ static int pull_job_open_disk(PullJob *j) { return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize hash context."); #else - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return log_error_errno(r, "Failed to load libgcrypt: %m"); if (gcry_md_open(&j->checksum_ctx, GCRY_MD_SHA256, 0) != 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index 66c3f65..f3e6b3a 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -42,7 +42,7 @@ struct RawPull { sd_event *event; CurlGlue *glue; - PullFlags flags; + ImportFlags flags; ImportVerify verify; char *image_root; @@ -60,7 +60,7 @@ struct RawPull { void *userdata; char *local; /* In PULL_DIRECT mode the path we are supposed to place things in, otherwise the - * machine name of the final copy we make */ + * image name of the final copy we make */ char *final_path; char *temp_path; @@ -127,8 +127,9 @@ int raw_pull_new( int r; assert(ret); + assert(image_root); - root = strdup(image_root ?: "/var/lib/machines"); + root = strdup(image_root); if (!root) return -ENOMEM; @@ -244,9 +245,9 @@ static int raw_pull_maybe_convert_qcow2(RawPull *i) { assert(i); assert(i->raw_job); - assert(!FLAGS_SET(i->flags, PULL_DIRECT)); + assert(!FLAGS_SET(i->flags, IMPORT_DIRECT)); - if (!FLAGS_SET(i->flags, PULL_CONVERT_QCOW2)) + if (!FLAGS_SET(i->flags, IMPORT_CONVERT_QCOW2)) return 0; assert(i->final_path); @@ -310,7 +311,7 @@ static int raw_pull_copy_auxiliary_file( const char *suffix, char **path /* input + output (!) */) { - const char *local; + _cleanup_free_ char *local = NULL; int r; assert(i); @@ -321,21 +322,29 @@ static int raw_pull_copy_auxiliary_file( if (r < 0) return r; - local = strjoina(i->image_root, "/", i->local, suffix); + local = strjoin(i->image_root, "/", i->local, suffix); + if (!local) + return log_oom(); - r = copy_file_atomic( - *path, - local, - 0644, - COPY_REFLINK | - (FLAGS_SET(i->flags, PULL_FORCE) ? COPY_REPLACE : 0) | - (FLAGS_SET(i->flags, PULL_SYNC) ? COPY_FSYNC_FULL : 0)); + if (FLAGS_SET(i->flags, IMPORT_PULL_KEEP_DOWNLOAD)) + r = copy_file_atomic( + *path, + local, + 0644, + COPY_REFLINK | + (FLAGS_SET(i->flags, IMPORT_FORCE) ? COPY_REPLACE : 0) | + (FLAGS_SET(i->flags, IMPORT_SYNC) ? COPY_FSYNC_FULL : 0)); + else + r = install_file(AT_FDCWD, *path, + AT_FDCWD, local, + (i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); if (r == -EEXIST) log_warning_errno(r, "File %s already exists, not replacing.", local); else if (r == -ENOENT) log_debug_errno(r, "Skipping creation of auxiliary file, since none was found."); else if (r < 0) - log_warning_errno(r, "Failed to copy file %s, ignoring: %m", local); + log_warning_errno(r, "Failed to install file %s, ignoring: %m", local); else log_info("Created new file %s.", local); @@ -344,14 +353,12 @@ static int raw_pull_copy_auxiliary_file( static int raw_pull_make_local_copy(RawPull *i) { _cleanup_(unlink_and_freep) char *tp = NULL; - _cleanup_free_ char *f = NULL; - _cleanup_close_ int dfd = -EBADF; - const char *p; + _cleanup_free_ char *p = NULL; int r; assert(i); assert(i->raw_job); - assert(!FLAGS_SET(i->flags, PULL_DIRECT)); + assert(!FLAGS_SET(i->flags, IMPORT_DIRECT)); if (!i->local) return 0; @@ -374,62 +381,73 @@ static int raw_pull_make_local_copy(RawPull *i) { return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m"); } - p = strjoina(i->image_root, "/", i->local, ".raw"); - - r = tempfn_random(p, NULL, &f); - if (r < 0) + p = strjoin(i->image_root, "/", i->local, ".raw"); + if (!p) return log_oom(); - dfd = open(f, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); - if (dfd < 0) - return log_error_errno(errno, "Failed to create writable copy of image: %m"); + const char *source; + if (FLAGS_SET(i->flags, IMPORT_PULL_KEEP_DOWNLOAD)) { + _cleanup_close_ int dfd = -EBADF; + _cleanup_free_ char *f = NULL; - tp = TAKE_PTR(f); + r = tempfn_random(p, NULL, &f); + if (r < 0) + return log_oom(); - /* Turn off COW writing. This should greatly improve performance on COW file systems like btrfs, - * since it reduces fragmentation caused by not allowing in-place writes. */ - (void) import_set_nocow_and_log(dfd, tp); + dfd = open(f, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); + if (dfd < 0) + return log_error_errno(errno, "Failed to create writable copy of image: %m"); - r = copy_bytes(i->raw_job->disk_fd, dfd, UINT64_MAX, COPY_REFLINK); - if (r < 0) - return log_error_errno(r, "Failed to make writable copy of image: %m"); + tp = TAKE_PTR(f); - (void) copy_times(i->raw_job->disk_fd, dfd, COPY_CRTIME); - (void) copy_xattr(i->raw_job->disk_fd, NULL, dfd, NULL, 0); + /* Turn off COW writing. This should greatly improve performance on COW file systems like btrfs, + * since it reduces fragmentation caused by not allowing in-place writes. */ + (void) import_set_nocow_and_log(dfd, tp); - dfd = safe_close(dfd); + r = copy_bytes(i->raw_job->disk_fd, dfd, UINT64_MAX, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to make writable copy of image: %m"); + + (void) copy_times(i->raw_job->disk_fd, dfd, COPY_CRTIME); + (void) copy_xattr(i->raw_job->disk_fd, NULL, dfd, NULL, 0); + + dfd = safe_close(dfd); + + source = tp; + } else + source = i->final_path; - r = install_file(AT_FDCWD, tp, + r = install_file(AT_FDCWD, source, AT_FDCWD, p, - (i->flags & PULL_FORCE ? INSTALL_REPLACE : 0) | - (i->flags & PULL_READ_ONLY ? INSTALL_READ_ONLY : 0) | - (i->flags & PULL_SYNC ? INSTALL_FSYNC_FULL : 0)); + (i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) | + (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0)); if (r < 0) - return log_error_errno(errno, "Failed to move local image into place '%s': %m", p); + return log_error_errno(r, "Failed to move local image into place '%s': %m", p); tp = mfree(tp); log_info("Created new local image '%s'.", i->local); - if (FLAGS_SET(i->flags, PULL_SETTINGS)) { + if (FLAGS_SET(i->flags, IMPORT_PULL_SETTINGS)) { r = raw_pull_copy_auxiliary_file(i, ".nspawn", &i->settings_path); if (r < 0) return r; } - if (FLAGS_SET(i->flags, PULL_ROOTHASH)) { + if (FLAGS_SET(i->flags, IMPORT_PULL_ROOTHASH)) { r = raw_pull_copy_auxiliary_file(i, ".roothash", &i->roothash_path); if (r < 0) return r; } - if (FLAGS_SET(i->flags, PULL_ROOTHASH_SIGNATURE)) { + if (FLAGS_SET(i->flags, IMPORT_PULL_ROOTHASH_SIGNATURE)) { r = raw_pull_copy_auxiliary_file(i, ".roothash.p7s", &i->roothash_signature_path); if (r < 0) return r; } - if (FLAGS_SET(i->flags, PULL_VERITY)) { + if (FLAGS_SET(i->flags, IMPORT_PULL_VERITY)) { r = raw_pull_copy_auxiliary_file(i, ".verity", &i->verity_path); if (r < 0) return r; @@ -485,7 +503,7 @@ static int raw_pull_rename_auxiliary_file( AT_FDCWD, *temp_path, AT_FDCWD, *path, INSTALL_READ_ONLY| - (i->flags & PULL_SYNC ? INSTALL_FSYNC_FULL : 0)); + (i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0)); if (r < 0) return log_error_errno(r, "Failed to move '%s' into place: %m", *path); @@ -495,7 +513,6 @@ static int raw_pull_rename_auxiliary_file( static void raw_pull_job_on_finished(PullJob *j) { RawPull *i; - PullJob *jj; int r; assert(j); @@ -568,8 +585,9 @@ static void raw_pull_job_on_finished(PullJob *j) { } } + PullJob *jj; /* Let's close these auxiliary files now, we don't need access to them anymore. */ - FOREACH_POINTER(jj, i->settings_job, i->roothash_job, i->roothash_signature_job, i->verity_job) + FOREACH_ARGUMENT(jj, i->settings_job, i->roothash_job, i->roothash_signature_job, i->verity_job) pull_job_close_disk_fd(jj); if (!i->raw_job->etag_exists) { @@ -588,7 +606,7 @@ static void raw_pull_job_on_finished(PullJob *j) { goto finish; } - if (i->flags & PULL_DIRECT) { + if (i->flags & IMPORT_DIRECT) { assert(!i->settings_job); assert(!i->roothash_job); assert(!i->roothash_signature_job); @@ -599,8 +617,8 @@ static void raw_pull_job_on_finished(PullJob *j) { if (i->local) { r = install_file(AT_FDCWD, i->local, AT_FDCWD, NULL, - ((i->flags & PULL_READ_ONLY) && i->offset == UINT64_MAX ? INSTALL_READ_ONLY : 0) | - (i->flags & PULL_SYNC ? INSTALL_FSYNC_FULL : 0)); + ((i->flags & IMPORT_READ_ONLY) && i->offset == UINT64_MAX ? INSTALL_READ_ONLY : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0)); if (r < 0) { log_error_errno(r, "Failed to finalize raw file to '%s': %m", i->local); goto finish; @@ -627,8 +645,8 @@ static void raw_pull_job_on_finished(PullJob *j) { r = install_file(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, - INSTALL_READ_ONLY| - (i->flags & PULL_SYNC ? INSTALL_FSYNC_FULL : 0)); + (i->flags & IMPORT_PULL_KEEP_DOWNLOAD ? INSTALL_READ_ONLY : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0)); if (r < 0) { log_error_errno(r, "Failed to move raw file to '%s': %m", i->final_path); goto finish; @@ -694,7 +712,7 @@ static int raw_pull_job_on_open_disk_generic( assert(extra); assert(temp_path); - assert(!FLAGS_SET(i->flags, PULL_DIRECT)); + assert(!FLAGS_SET(i->flags, IMPORT_DIRECT)); if (!*temp_path) { r = tempfn_random_child(i->image_root, extra, temp_path); @@ -722,7 +740,7 @@ static int raw_pull_job_on_open_disk_raw(PullJob *j) { assert(i->raw_job == j); assert(j->disk_fd < 0); - if (i->flags & PULL_DIRECT) { + if (i->flags & IMPORT_DIRECT) { if (!i->local) { /* If no local name specified, the pull job will write its data to stdout */ j->disk_fd = STDOUT_FILENO; @@ -816,11 +834,10 @@ int raw_pull_start( const char *local, uint64_t offset, uint64_t size_max, - PullFlags flags, + ImportFlags flags, ImportVerify verify, const char *checksum) { - PullJob *j; int r; assert(i); @@ -828,10 +845,10 @@ int raw_pull_start( assert(verify == _IMPORT_VERIFY_INVALID || verify < _IMPORT_VERIFY_MAX); assert(verify == _IMPORT_VERIFY_INVALID || verify >= 0); assert((verify < 0) || !checksum); - assert(!(flags & ~PULL_FLAGS_MASK_RAW)); - assert(offset == UINT64_MAX || FLAGS_SET(flags, PULL_DIRECT)); - assert(!(flags & (PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY)) || !(flags & PULL_DIRECT)); - assert(!(flags & (PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY)) || !checksum); + assert(!(flags & ~IMPORT_PULL_FLAGS_MASK_RAW)); + assert(offset == UINT64_MAX || FLAGS_SET(flags, IMPORT_DIRECT)); + assert(!(flags & (IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY)) || !(flags & IMPORT_DIRECT)); + assert(!(flags & (IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY)) || !checksum); if (!http_url_is_valid(url) && !file_url_is_valid(url)) return -EINVAL; @@ -881,7 +898,7 @@ int raw_pull_start( if (offset != UINT64_MAX) i->raw_job->offset = i->offset = offset; - if (!FLAGS_SET(flags, PULL_DIRECT)) { + if (!FLAGS_SET(flags, IMPORT_DIRECT)) { r = pull_find_old_etags(url, i->image_root, DT_REG, ".raw-", ".raw", &i->raw_job->old_etags); if (r < 0) return r; @@ -899,7 +916,7 @@ int raw_pull_start( if (r < 0) return r; - if (FLAGS_SET(flags, PULL_SETTINGS)) { + if (FLAGS_SET(flags, IMPORT_PULL_SETTINGS)) { r = pull_make_auxiliary_job( &i->settings_job, url, @@ -914,7 +931,7 @@ int raw_pull_start( return r; } - if (FLAGS_SET(flags, PULL_ROOTHASH)) { + if (FLAGS_SET(flags, IMPORT_PULL_ROOTHASH)) { r = pull_make_auxiliary_job( &i->roothash_job, url, @@ -929,7 +946,7 @@ int raw_pull_start( return r; } - if (FLAGS_SET(flags, PULL_ROOTHASH_SIGNATURE)) { + if (FLAGS_SET(flags, IMPORT_PULL_ROOTHASH_SIGNATURE)) { r = pull_make_auxiliary_job( &i->roothash_signature_job, url, @@ -944,7 +961,7 @@ int raw_pull_start( return r; } - if (FLAGS_SET(flags, PULL_VERITY)) { + if (FLAGS_SET(flags, IMPORT_PULL_VERITY)) { r = pull_make_auxiliary_job( &i->verity_job, url, @@ -959,20 +976,21 @@ int raw_pull_start( return r; } - FOREACH_POINTER(j, - i->raw_job, - i->checksum_job, - i->signature_job, - i->settings_job, - i->roothash_job, - i->roothash_signature_job, - i->verity_job) { + PullJob *j; + FOREACH_ARGUMENT(j, + i->raw_job, + i->checksum_job, + i->signature_job, + i->settings_job, + i->roothash_job, + i->roothash_signature_job, + i->verity_job) { if (!j) continue; j->on_progress = raw_pull_job_on_progress; - j->sync = FLAGS_SET(flags, PULL_SYNC); + j->sync = FLAGS_SET(flags, IMPORT_SYNC); r = pull_job_begin(j); if (r < 0) diff --git a/src/import/pull-raw.h b/src/import/pull-raw.h index b39e4e2..e50ba32 100644 --- a/src/import/pull-raw.h +++ b/src/import/pull-raw.h @@ -16,4 +16,4 @@ RawPull* raw_pull_unref(RawPull *pull); DEFINE_TRIVIAL_CLEANUP_FUNC(RawPull*, raw_pull_unref); -int raw_pull_start(RawPull *pull, const char *url, const char *local, uint64_t offset, uint64_t size_max, PullFlags flags, ImportVerify verify, const char *checksum); +int raw_pull_start(RawPull *pull, const char *url, const char *local, uint64_t offset, uint64_t size_max, ImportFlags flags, ImportVerify verify, const char *checksum); diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index c32fc29..7fc71fe 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -41,7 +41,7 @@ struct TarPull { sd_event *event; CurlGlue *glue; - PullFlags flags; + ImportFlags flags; ImportVerify verify; char *image_root; @@ -106,9 +106,10 @@ int tar_pull_new( _cleanup_free_ char *root = NULL; int r; + assert(image_root); assert(ret); - root = strdup(image_root ?: "/var/lib/machines"); + root = strdup(image_root); if (!root) return -ENOMEM; @@ -219,7 +220,8 @@ static int tar_pull_determine_path( static int tar_pull_make_local_copy(TarPull *i) { _cleanup_(rm_rf_subvolume_and_freep) char *t = NULL; - const char *p; + _cleanup_free_ char *p = NULL; + const char *source; int r; assert(i); @@ -230,30 +232,37 @@ static int tar_pull_make_local_copy(TarPull *i) { assert(i->final_path); - p = prefix_roota(i->image_root, i->local); + p = path_join(i->image_root, i->local); + if (!p) + return log_oom(); - r = tempfn_random(p, NULL, &t); - if (r < 0) - return log_error_errno(r, "Failed to generate temporary filename for %s: %m", p); - - if (i->flags & PULL_BTRFS_SUBVOL) - r = btrfs_subvol_snapshot_at( - AT_FDCWD, i->final_path, - AT_FDCWD, t, - (i->flags & PULL_BTRFS_QUOTA ? BTRFS_SNAPSHOT_QUOTA : 0)| - BTRFS_SNAPSHOT_FALLBACK_COPY| - BTRFS_SNAPSHOT_FALLBACK_DIRECTORY| - BTRFS_SNAPSHOT_RECURSIVE); - else - r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to create local image: %m"); + if (FLAGS_SET(i->flags, IMPORT_PULL_KEEP_DOWNLOAD)) { + r = tempfn_random(p, NULL, &t); + if (r < 0) + return log_error_errno(r, "Failed to generate temporary filename for %s: %m", p); + + if (i->flags & IMPORT_BTRFS_SUBVOL) + r = btrfs_subvol_snapshot_at( + AT_FDCWD, i->final_path, + AT_FDCWD, t, + (i->flags & IMPORT_BTRFS_QUOTA ? BTRFS_SNAPSHOT_QUOTA : 0)| + BTRFS_SNAPSHOT_FALLBACK_COPY| + BTRFS_SNAPSHOT_FALLBACK_DIRECTORY| + BTRFS_SNAPSHOT_RECURSIVE); + else + r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to create local image: %m"); - r = install_file(AT_FDCWD, t, + source = t; + } else + source = i->final_path; + + r = install_file(AT_FDCWD, source, AT_FDCWD, p, - (i->flags & PULL_FORCE ? INSTALL_REPLACE : 0) | - (i->flags & PULL_READ_ONLY ? INSTALL_READ_ONLY : 0) | - (i->flags & PULL_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) | + (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); if (r < 0) return log_error_errno(r, "Failed to install local image '%s': %m", p); @@ -261,29 +270,37 @@ static int tar_pull_make_local_copy(TarPull *i) { log_info("Created new local image '%s'.", i->local); - if (FLAGS_SET(i->flags, PULL_SETTINGS)) { - const char *local_settings; + if (FLAGS_SET(i->flags, IMPORT_PULL_SETTINGS)) { + _cleanup_free_ char *local_settings = NULL; assert(i->settings_job); r = tar_pull_determine_path(i, ".nspawn", &i->settings_path); if (r < 0) return r; - local_settings = strjoina(i->image_root, "/", i->local, ".nspawn"); + local_settings = strjoin(i->image_root, "/", i->local, ".nspawn"); + if (!local_settings) + return log_oom(); - r = copy_file_atomic( - i->settings_path, - local_settings, - 0664, - COPY_REFLINK | - (FLAGS_SET(i->flags, PULL_FORCE) ? COPY_REPLACE : 0) | - (FLAGS_SET(i->flags, PULL_SYNC) ? COPY_FSYNC_FULL : 0)); + if (FLAGS_SET(i->flags, IMPORT_PULL_KEEP_DOWNLOAD)) + r = copy_file_atomic( + i->settings_path, + local_settings, + 0664, + COPY_REFLINK | + (FLAGS_SET(i->flags, IMPORT_FORCE) ? COPY_REPLACE : 0) | + (FLAGS_SET(i->flags, IMPORT_SYNC) ? COPY_FSYNC_FULL : 0)); + else + r = install_file(AT_FDCWD, i->settings_path, + AT_FDCWD, local_settings, + (i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); if (r == -EEXIST) log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings); else if (r == -ENOENT) log_debug_errno(r, "Skipping creation of settings file, since none was found."); else if (r < 0) - log_warning_errno(r, "Failed to copy settings files %s, ignoring: %m", local_settings); + log_warning_errno(r, "Failed to install settings files %s, ignoring: %m", local_settings); else log_info("Created new settings file %s.", local_settings); } @@ -392,7 +409,7 @@ static void tar_pull_job_on_finished(PullJob *j) { goto finish; } - if (i->flags & PULL_DIRECT) { + if (i->flags & IMPORT_DIRECT) { assert(!i->settings_job); assert(i->local); assert(!i->temp_path); @@ -406,8 +423,8 @@ static void tar_pull_job_on_finished(PullJob *j) { r = install_file( AT_FDCWD, i->local, AT_FDCWD, NULL, - (i->flags & PULL_READ_ONLY) ? INSTALL_READ_ONLY : 0 | - (i->flags & PULL_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_READ_ONLY) ? INSTALL_READ_ONLY : 0 | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); if (r < 0) { log_error_errno(r, "Failed to finalize '%s': %m", i->local); goto finish; @@ -432,8 +449,8 @@ static void tar_pull_job_on_finished(PullJob *j) { r = install_file( AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, - INSTALL_READ_ONLY| - (i->flags & PULL_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_PULL_KEEP_DOWNLOAD ? INSTALL_READ_ONLY : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); if (r < 0) { log_error_errno(r, "Failed to rename to final image name to %s: %m", i->final_path); goto finish; @@ -460,7 +477,7 @@ static void tar_pull_job_on_finished(PullJob *j) { AT_FDCWD, i->settings_temp_path, AT_FDCWD, i->settings_path, INSTALL_READ_ONLY| - (i->flags & PULL_SYNC ? INSTALL_FSYNC_FULL : 0)); + (i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0)); if (r < 0) { log_error_errno(r, "Failed to rename settings file to %s: %m", i->settings_path); goto finish; @@ -498,7 +515,7 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) { assert(i->tar_job == j); assert(i->tar_pid <= 0); - if (i->flags & PULL_DIRECT) + if (i->flags & IMPORT_DIRECT) where = i->local; else { if (!i->temp_path) { @@ -512,20 +529,20 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) { (void) mkdir_parents_label(where, 0700); - if (FLAGS_SET(i->flags, PULL_DIRECT|PULL_FORCE)) + if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE)) (void) rm_rf(where, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - if (i->flags & PULL_BTRFS_SUBVOL) + if (i->flags & IMPORT_BTRFS_SUBVOL) r = btrfs_subvol_make_fallback(AT_FDCWD, where, 0755); else r = RET_NERRNO(mkdir(where, 0755)); - if (r == -EEXIST && (i->flags & PULL_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, + if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, * because in that case our temporary path collided */ r = 0; if (r < 0) return log_error_errno(r, "Failed to create directory/subvolume %s: %m", where); - if (r > 0 && (i->flags & PULL_BTRFS_QUOTA)) { /* actually btrfs subvol */ - if (!(i->flags & PULL_DIRECT)) + if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */ + if (!(i->flags & IMPORT_DIRECT)) (void) import_assign_pool_quota_and_warn(i->image_root); (void) import_assign_pool_quota_and_warn(where); } @@ -577,20 +594,19 @@ int tar_pull_start( TarPull *i, const char *url, const char *local, - PullFlags flags, + ImportFlags flags, ImportVerify verify, const char *checksum) { - PullJob *j; int r; assert(i); assert(verify == _IMPORT_VERIFY_INVALID || verify < _IMPORT_VERIFY_MAX); assert(verify == _IMPORT_VERIFY_INVALID || verify >= 0); assert((verify < 0) || !checksum); - assert(!(flags & ~PULL_FLAGS_MASK_TAR)); - assert(!(flags & PULL_SETTINGS) || !(flags & PULL_DIRECT)); - assert(!(flags & PULL_SETTINGS) || !checksum); + assert(!(flags & ~IMPORT_PULL_FLAGS_MASK_TAR)); + assert(!(flags & IMPORT_PULL_SETTINGS) || !(flags & IMPORT_DIRECT)); + assert(!(flags & IMPORT_PULL_SETTINGS) || !checksum); if (!http_url_is_valid(url) && !file_url_is_valid(url)) return -EINVAL; @@ -621,7 +637,7 @@ int tar_pull_start( i->tar_job->on_open_disk = tar_pull_job_on_open_disk_tar; i->tar_job->calc_checksum = checksum || IN_SET(verify, IMPORT_VERIFY_CHECKSUM, IMPORT_VERIFY_SIGNATURE); - if (!FLAGS_SET(flags, PULL_DIRECT)) { + if (!FLAGS_SET(flags, IMPORT_DIRECT)) { r = pull_find_old_etags(url, i->image_root, DT_DIR, ".tar-", NULL, &i->tar_job->old_etags); if (r < 0) return r; @@ -641,7 +657,7 @@ int tar_pull_start( return r; /* Set up download job for the settings file (.nspawn) */ - if (FLAGS_SET(flags, PULL_SETTINGS)) { + if (FLAGS_SET(flags, IMPORT_PULL_SETTINGS)) { r = pull_make_auxiliary_job( &i->settings_job, url, @@ -656,17 +672,18 @@ int tar_pull_start( return r; } - FOREACH_POINTER(j, - i->tar_job, - i->checksum_job, - i->signature_job, - i->settings_job) { + PullJob *j; + FOREACH_ARGUMENT(j, + i->tar_job, + i->checksum_job, + i->signature_job, + i->settings_job) { if (!j) continue; j->on_progress = tar_pull_job_on_progress; - j->sync = FLAGS_SET(flags, PULL_SYNC); + j->sync = FLAGS_SET(flags, IMPORT_SYNC); r = pull_job_begin(j); if (r < 0) diff --git a/src/import/pull-tar.h b/src/import/pull-tar.h index e54c01c..1a5b740 100644 --- a/src/import/pull-tar.h +++ b/src/import/pull-tar.h @@ -16,4 +16,4 @@ TarPull* tar_pull_unref(TarPull *pull); DEFINE_TRIVIAL_CLEANUP_FUNC(TarPull*, tar_pull_unref); -int tar_pull_start(TarPull *pull, const char *url, const char *local, PullFlags flags, ImportVerify verify, const char *checksum); +int tar_pull_start(TarPull *pull, const char *url, const char *local, ImportFlags flags, ImportVerify verify, const char *checksum); diff --git a/src/import/pull.c b/src/import/pull.c index 38821b5..7c838a5 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -26,11 +26,12 @@ #include "verbs.h" #include "web-util.h" -static const char *arg_image_root = "/var/lib/machines"; +static const char *arg_image_root = NULL; static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; -static PullFlags arg_pull_flags = PULL_SETTINGS | PULL_ROOTHASH | PULL_ROOTHASH_SIGNATURE | PULL_VERITY | PULL_BTRFS_SUBVOL | PULL_BTRFS_QUOTA | PULL_CONVERT_QCOW2 | PULL_SYNC; +static ImportFlags arg_import_flags = IMPORT_PULL_SETTINGS | IMPORT_PULL_ROOTHASH | IMPORT_PULL_ROOTHASH_SIGNATURE | IMPORT_PULL_VERITY | IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC; static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX; static char *arg_checksum = NULL; +static ImageClass arg_class = IMAGE_MACHINE; STATIC_DESTRUCTOR_REGISTER(arg_checksum, freep); @@ -38,7 +39,7 @@ static int normalize_local(const char *local, const char *url, char **ret) { _cleanup_free_ char *ll = NULL; int r; - if (arg_pull_flags & PULL_DIRECT) { + if (arg_import_flags & IMPORT_DIRECT) { if (!local) log_debug("Writing downloaded data to STDOUT."); @@ -58,13 +59,13 @@ static int normalize_local(const char *local, const char *url, char **ret) { } else if (local) { - if (!hostname_is_valid(local, 0)) + if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Local image name '%s' is not valid.", local); - if (!FLAGS_SET(arg_pull_flags, PULL_FORCE)) { - r = image_find(IMAGE_MACHINE, local, NULL, NULL); + if (!FLAGS_SET(arg_import_flags, IMPORT_FORCE)) { + r = image_find(arg_class, local, NULL, NULL); if (r < 0) { if (r != -ENOENT) return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); @@ -89,6 +90,12 @@ static int normalize_local(const char *local, const char *url, char **ret) { } else log_info("Pulling '%s'.", url); + if (!FLAGS_SET(arg_import_flags, IMPORT_DIRECT)) + log_info("Operating on image directory '%s'.", arg_image_root); + + if (!FLAGS_SET(arg_import_flags, IMPORT_SYNC)) + log_info("File system synchronization on completion is off."); + *ret = TAKE_PTR(ll); return 0; } @@ -130,7 +137,7 @@ static int pull_tar(int argc, char *argv[], void *userdata) { local = ll; } - if (!local && FLAGS_SET(arg_pull_flags, PULL_DIRECT)) + if (!local && FLAGS_SET(arg_import_flags, IMPORT_DIRECT)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Pulling tar images to STDOUT is not supported."); r = normalize_local(local, url, &normalized); @@ -141,9 +148,6 @@ static int pull_tar(int argc, char *argv[], void *userdata) { if (r < 0) return r; - if (!FLAGS_SET(arg_pull_flags, PULL_SYNC)) - log_info("File system synchronization on completion is off."); - r = tar_pull_new(&pull, event, arg_image_root, on_tar_finished, event); if (r < 0) return log_error_errno(r, "Failed to allocate puller: %m"); @@ -152,7 +156,7 @@ static int pull_tar(int argc, char *argv[], void *userdata) { pull, url, normalized, - arg_pull_flags & PULL_FLAGS_MASK_TAR, + arg_import_flags & IMPORT_PULL_FLAGS_MASK_TAR, arg_verify, arg_checksum); if (r < 0) @@ -211,9 +215,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) { if (r < 0) return r; - if (!FLAGS_SET(arg_pull_flags, PULL_SYNC)) - log_info("File system synchronization on completion is off."); - r = raw_pull_new(&pull, event, arg_image_root, on_raw_finished, event); + r = raw_pull_new(&pull, event, arg_image_root, on_raw_finished, event); if (r < 0) return log_error_errno(r, "Failed to allocate puller: %m"); @@ -223,7 +225,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) { normalized, arg_offset, arg_size_max, - arg_pull_flags & PULL_FLAGS_MASK_RAW, + arg_import_flags & IMPORT_PULL_FLAGS_MASK_RAW, arg_verify, arg_checksum); if (r < 0) @@ -240,7 +242,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) { static int help(int argc, char *argv[], void *userdata) { printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sDownload container or virtual machine images.%5$s\n" + "\n%4$sDownload disk images.%5$s\n" "\n%2$sCommands:%3$s\n" " tar URL [NAME] Download a TAR image\n" " raw URL [NAME] Download a RAW image\n" @@ -255,7 +257,7 @@ static int help(int argc, char *argv[], void *userdata) { " --roothash-signature=BOOL\n" " Download root hash signature file with image\n" " --verity=BOOL Download verity file with image\n" - " --image-root=PATH Image root directory\n\n" + " --image-root=PATH Image root directory\n" " --read-only Create a read-only image\n" " --direct Download directly to specified file\n" " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n" @@ -266,7 +268,11 @@ static int help(int argc, char *argv[], void *userdata) { " regular disk images\n" " --sync=BOOL Controls whether to sync() before completing\n" " --offset=BYTES Offset to seek to in destination\n" - " --size-max=BYTES Maximum number of bytes to write to destination\n", + " --size-max=BYTES Maximum number of bytes to write to destination\n" + " --class=CLASS Select image class (machine, sysext, confext,\n" + " portable)\n" + " --keep-download=BOOL Keep a copy pristine copy of the downloaded file\n" + " around\n", program_invocation_short_name, ansi_underline(), ansi_normal(), @@ -295,6 +301,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_SYNC, ARG_OFFSET, ARG_SIZE_MAX, + ARG_CLASS, + ARG_KEEP_DOWNLOAD, }; static const struct option options[] = { @@ -315,10 +323,13 @@ static int parse_argv(int argc, char *argv[]) { { "sync", required_argument, NULL, ARG_SYNC }, { "offset", required_argument, NULL, ARG_OFFSET }, { "size-max", required_argument, NULL, ARG_SIZE_MAX }, + { "class", required_argument, NULL, ARG_CLASS }, + { "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD }, {} }; int c, r; + bool auto_settings = true, auto_keep_download = true; assert(argc >= 0); assert(argv); @@ -334,7 +345,7 @@ static int parse_argv(int argc, char *argv[]) { return version(); case ARG_FORCE: - arg_pull_flags |= PULL_FORCE; + arg_import_flags |= IMPORT_FORCE; break; case ARG_IMAGE_ROOT: @@ -353,7 +364,7 @@ static int parse_argv(int argc, char *argv[]) { /* If this is not a valid verification mode, maybe it's a literally specified * SHA256 hash? We can handle that too... */ - r = unhexmem(optarg, (size_t) -1, &h, &n); + r = unhexmem(optarg, &h, &n); if (r < 0 || n == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid verification setting: %s", optarg); @@ -366,7 +377,7 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); free_and_replace(arg_checksum, hh); - arg_pull_flags &= ~(PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY); + arg_import_flags &= ~(IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY); arg_verify = _IMPORT_VERIFY_INVALID; } else arg_verify = v; @@ -379,7 +390,8 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_SETTINGS, r); + SET_FLAG(arg_import_flags, IMPORT_PULL_SETTINGS, r); + auto_settings = false; break; case ARG_ROOTHASH: @@ -387,11 +399,11 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_ROOTHASH, r); + SET_FLAG(arg_import_flags, IMPORT_PULL_ROOTHASH, r); /* If we were asked to turn off the root hash, implicitly also turn off the root hash signature */ if (!r) - SET_FLAG(arg_pull_flags, PULL_ROOTHASH_SIGNATURE, false); + SET_FLAG(arg_import_flags, IMPORT_PULL_ROOTHASH_SIGNATURE, false); break; case ARG_ROOTHASH_SIGNATURE: @@ -399,7 +411,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_ROOTHASH_SIGNATURE, r); + SET_FLAG(arg_import_flags, IMPORT_PULL_ROOTHASH_SIGNATURE, r); break; case ARG_VERITY: @@ -407,16 +419,16 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_VERITY, r); + SET_FLAG(arg_import_flags, IMPORT_PULL_VERITY, r); break; case ARG_READ_ONLY: - arg_pull_flags |= PULL_READ_ONLY; + arg_import_flags |= IMPORT_READ_ONLY; break; case ARG_DIRECT: - arg_pull_flags |= PULL_DIRECT; - arg_pull_flags &= ~(PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY); + arg_import_flags |= IMPORT_DIRECT; + arg_import_flags &= ~(IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY); break; case ARG_BTRFS_SUBVOL: @@ -424,7 +436,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_BTRFS_SUBVOL, r); + SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r); break; case ARG_BTRFS_QUOTA: @@ -432,7 +444,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_BTRFS_QUOTA, r); + SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r); break; case ARG_CONVERT_QCOW2: @@ -440,7 +452,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_CONVERT_QCOW2, r); + SET_FLAG(arg_import_flags, IMPORT_CONVERT_QCOW2, r); break; case ARG_SYNC: @@ -448,7 +460,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_SYNC, r); + SET_FLAG(arg_import_flags, IMPORT_SYNC, r); break; case ARG_OFFSET: { @@ -477,6 +489,22 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_CLASS: + arg_class = image_class_from_string(optarg); + if (arg_class < 0) + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); + + break; + + case ARG_KEEP_DOWNLOAD: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --keep-download= argument: %s", optarg); + + SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r); + auto_keep_download = false; + break; + case '?': return -EINVAL; @@ -490,12 +518,24 @@ static int parse_argv(int argc, char *argv[]) { !FILE_SIZE_VALID(arg_offset + arg_size_max))) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset und maximum size out of range."); - if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_pull_flags, PULL_DIRECT)) + if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_import_flags, IMPORT_DIRECT)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode."); - if (arg_checksum && (arg_pull_flags & (PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY)) != 0) + if (arg_checksum && (arg_import_flags & (IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY)) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Literal checksum verification only supported if no associated files are downloaded."); + if (!arg_image_root) + arg_image_root = image_root_to_string(arg_class); + + /* .nspawn settings files only really make sense for machine images, not for sysext/confext/portable */ + if (auto_settings && arg_class != IMAGE_MACHINE) + arg_import_flags &= ~IMPORT_PULL_SETTINGS; + + /* 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 (auto_keep_download) + SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, arg_class == IMAGE_MACHINE); + return 1; } @@ -507,19 +547,19 @@ static void parse_env(void) { r = getenv_bool("SYSTEMD_IMPORT_BTRFS_SUBVOL"); if (r >= 0) - SET_FLAG(arg_pull_flags, PULL_BTRFS_SUBVOL, r); + SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r); else if (r != -ENXIO) log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_BTRFS_SUBVOL: %m"); r = getenv_bool("SYSTEMD_IMPORT_BTRFS_QUOTA"); if (r >= 0) - SET_FLAG(arg_pull_flags, PULL_BTRFS_QUOTA, r); + SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r); else if (r != -ENXIO) log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_BTRFS_QUOTA: %m"); r = getenv_bool("SYSTEMD_IMPORT_SYNC"); if (r >= 0) - SET_FLAG(arg_pull_flags, PULL_SYNC, r); + SET_FLAG(arg_import_flags, IMPORT_SYNC, r); else if (r != -ENXIO) log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_SYNC: %m"); } @@ -539,8 +579,7 @@ static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); + log_setup(); parse_env(); diff --git a/src/import/qcow2-util.c b/src/import/qcow2-util.c index c70656b..fc34f30 100644 --- a/src/import/qcow2-util.c +++ b/src/import/qcow2-util.c @@ -273,7 +273,7 @@ int qcow2_convert(int qcow2_fd, int raw_fd) { if ((uint64_t) l != sz) return -EIO; - for (i = 0; i < HEADER_L1_SIZE(&header); i ++) { + for (i = 0; i < HEADER_L1_SIZE(&header); i++) { uint64_t l2_begin, j; r = normalize_offset(&header, l1_table[i], &l2_begin, NULL, NULL); |