diff options
author | Lennart Poettering <lennart@poettering.net> | 2021-01-22 17:40:51 +0100 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2021-08-17 10:08:58 +0200 |
commit | d32a5841fb1aabc0e2fd138d78b5f66d31b3ba77 (patch) | |
tree | 43da4bbf04be82965f3a4f4657e8fd03ecd96ca1 /src/import | |
parent | shared: add generic helper tools for installing files/dir trees (diff) | |
download | systemd-d32a5841fb1aabc0e2fd138d78b5f66d31b3ba77.tar.xz systemd-d32a5841fb1aabc0e2fd138d78b5f66d31b3ba77.zip |
import: add new "--direct" mode + add controls for turning certain features on/off
This reworks/modernizes the tar/raw import logic and adds the following
new features:
- Adds the ability to control btrfs subvol and quota behaviour which was
previously always on via an env var and cmdline arg
- Adds control whether to sync() stuff after writing it, similar via env
var + cmdline arg
- Similar, the QCOW2 unpacking logic that was previously the implied
default may now be controlled via env var + cmdline arg.
- adds a "direct" mode. In this mode, the systemd-import tool can be
used as a simple tool for decompressing/unpacking/installing arbitrary
files, without all the additional meta data and auxiliary resources,
i.e. outside of the immediate disk image context. Via the new
--offset= and --size-max= switches the downloaded data can be written
to specific locations of a file (which is particularly useful to use
the tool to download fs images and write them to a partition location
before actually creating the partition).
We'll later use the latter feature for "sysupdate" concept, where images
can be directly be written to partitions. That way the systemd-import
binary will be used as backend for both "systemd-importd" and
"systemd-sysupdate" and share most of the same code.
Diffstat (limited to 'src/import')
-rw-r--r-- | src/import/import-common.c | 36 | ||||
-rw-r--r-- | src/import/import-common.h | 18 | ||||
-rw-r--r-- | src/import/import-raw.c | 217 | ||||
-rw-r--r-- | src/import/import-raw.h | 2 | ||||
-rw-r--r-- | src/import/import-tar.c | 91 | ||||
-rw-r--r-- | src/import/import.c | 379 |
6 files changed, 543 insertions, 200 deletions
diff --git a/src/import/import-common.c b/src/import/import-common.c index f77564c41d..53aed6a514 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -13,6 +13,7 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" +#include "hostname-util.h" #include "import-common.h" #include "os-util.h" #include "process-util.h" @@ -327,3 +328,38 @@ int import_mangle_os_tree(const char *path) { return 0; } + +bool import_validate_local(const char *name, ImportFlags flags) { + + /* By default we insist on a valid hostname for naming images. But optionally we relax that, in which + * case it can be any path name */ + + if (FLAGS_SET(flags, IMPORT_DIRECT)) + return path_is_valid(name); + + return hostname_is_valid(name, 0); +} + +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; +} + +int import_allocate_event_with_signals(sd_event **ret) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int r; + + assert(ret); + + r = sd_event_default(&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); + + *ret = TAKE_PTR(event); + return 0; +} diff --git a/src/import/import-common.h b/src/import/import-common.h index d7e8fc485f..36052a2952 100644 --- a/src/import/import-common.h +++ b/src/import/import-common.h @@ -3,11 +3,19 @@ #include <sys/types.h> +#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_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 = IMPORT_FORCE|IMPORT_READ_ONLY, + 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, } ImportFlags; int import_make_read_only_fd(int fd); @@ -17,3 +25,7 @@ int import_fork_tar_c(const char *path, pid_t *ret); int import_fork_tar_x(const char *path, pid_t *ret); int import_mangle_os_tree(const char *path); + +bool import_validate_local(const char *name, ImportFlags flags); + +int import_allocate_event_with_signals(sd_event **ret); diff --git a/src/import/import-raw.c b/src/import/import-raw.c index 649cffb55f..9a87dd6f10 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -14,6 +14,7 @@ #include "import-common.h" #include "import-compress.h" #include "import-raw.h" +#include "install-file.h" #include "io-util.h" #include "machine-pool.h" #include "mkdir.h" @@ -52,23 +53,27 @@ struct RawImport { uint64_t written_compressed; uint64_t written_uncompressed; - struct stat st; + struct stat input_stat; + struct stat output_stat; unsigned last_percent; RateLimit progress_ratelimit; + + uint64_t offset; + uint64_t size_max; }; RawImport* raw_import_unref(RawImport *i) { if (!i) return NULL; - sd_event_unref(i->event); + sd_event_source_unref(i->input_event_source); unlink_and_free(i->temp_path); import_compress_free(&i->compress); - sd_event_source_unref(i->input_event_source); + sd_event_unref(i->event); safe_close(i->output_fd); @@ -107,6 +112,8 @@ int raw_import_new( .last_percent = UINT_MAX, .image_root = TAKE_PTR(root), .progress_ratelimit = { 100 * USEC_PER_MSEC, 1 }, + .offset = UINT64_MAX, + .size_max = UINT64_MAX, }; if (event) @@ -118,7 +125,6 @@ int raw_import_new( } *ret = TAKE_PTR(i); - return 0; } @@ -127,13 +133,13 @@ static void raw_import_report_progress(RawImport *i) { assert(i); /* We have no size information, unless the source is a regular file */ - if (!S_ISREG(i->st.st_mode)) + if (!S_ISREG(i->input_stat.st_mode)) return; - if (i->written_compressed >= (uint64_t) i->st.st_size) + if (i->written_compressed >= (uint64_t) i->input_stat.st_size) percent = 100; else - percent = (unsigned) ((i->written_compressed * UINT64_C(100)) / (uint64_t) i->st.st_size); + percent = (unsigned) ((i->written_compressed * UINT64_C(100)) / (uint64_t) i->input_stat.st_size); if (percent == i->last_percent) return; @@ -149,11 +155,18 @@ static void raw_import_report_progress(RawImport *i) { static int raw_import_maybe_convert_qcow2(RawImport *i) { _cleanup_close_ int converted_fd = -1; - _cleanup_free_ char *t = NULL; + _cleanup_(unlink_and_freep) char *t = NULL; + _cleanup_free_ char *f = NULL; int r; assert(i); + /* Do QCOW2 conversion if enabled and not in direct mode */ + if ((i->flags & (IMPORT_CONVERT_QCOW2|IMPORT_DIRECT)) != IMPORT_CONVERT_QCOW2) + return 0; + + assert(i->final_path); + r = qcow2_detect(i->output_fd); if (r < 0) return log_error_errno(r, "Failed to detect whether this is a QCOW2 image: %m"); @@ -161,26 +174,26 @@ static int raw_import_maybe_convert_qcow2(RawImport *i) { return 0; /* This is a QCOW2 image, let's convert it */ - r = tempfn_random(i->final_path, NULL, &t); + r = tempfn_random(i->final_path, NULL, &f); if (r < 0) return log_oom(); - converted_fd = open(t, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); + converted_fd = open(f, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); if (converted_fd < 0) - return log_error_errno(errno, "Failed to create %s: %m", t); + return log_error_errno(errno, "Failed to create %s: %m", f); + + t = TAKE_PTR(f); (void) import_set_nocow_and_log(converted_fd, t); log_info("Unpacking QCOW2 file."); r = qcow2_convert(i->output_fd, converted_fd); - if (r < 0) { - (void) unlink(t); + if (r < 0) return log_error_errno(r, "Failed to convert qcow2 image: %m"); - } - (void) unlink(i->temp_path); - free_and_replace(i->temp_path, t); + unlink_and_free(i->temp_path); + i->temp_path = TAKE_PTR(t); CLOSE_AND_REPLACE(i->output_fd, converted_fd); return 1; @@ -191,34 +204,45 @@ static int raw_import_finish(RawImport *i) { assert(i); assert(i->output_fd >= 0); - assert(i->temp_path); - assert(i->final_path); - /* In case this was a sparse file, make sure the file system is right */ - if (i->written_uncompressed > 0) { - if (ftruncate(i->output_fd, i->written_uncompressed) < 0) - return log_error_errno(errno, "Failed to truncate file: %m"); - } + /* Nothing of what is below applies to block devices */ + if (S_ISBLK(i->output_stat.st_mode)) { - r = raw_import_maybe_convert_qcow2(i); - if (r < 0) - return r; + if (i->flags & IMPORT_SYNC) { + if (fsync(i->output_fd) < 0) + return log_error_errno(errno, "Failed to synchronize block device: %m"); + } - if (S_ISREG(i->st.st_mode)) { - (void) copy_times(i->input_fd, i->output_fd, COPY_CRTIME); - (void) copy_xattr(i->input_fd, i->output_fd, 0); + return 0; } - if (i->flags & IMPORT_READ_ONLY) { - r = import_make_read_only_fd(i->output_fd); + assert(S_ISREG(i->output_stat.st_mode)); + + /* If an offset is specified we only are supposed to affect part of an existing output file or block + * device, thus don't manipulate file properties in that case */ + + if (i->offset == UINT64_MAX) { + /* In case this was a sparse file, make sure the file size is right */ + if (i->written_uncompressed > 0) { + if (ftruncate(i->output_fd, i->written_uncompressed) < 0) + return log_error_errno(errno, "Failed to truncate file: %m"); + } + + r = raw_import_maybe_convert_qcow2(i); if (r < 0) return r; - } - if (i->flags & IMPORT_FORCE) - (void) rm_rf(i->final_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + if (S_ISREG(i->input_stat.st_mode)) { + (void) copy_times(i->input_fd, i->output_fd, COPY_CRTIME); + (void) copy_xattr(i->input_fd, i->output_fd, 0); + } + } - r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); + r = install_file(AT_FDCWD, i->temp_path ?: i->local, + AT_FDCWD, i->final_path, + (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(r, "Failed to move image into place: %m"); @@ -231,26 +255,56 @@ static int raw_import_open_disk(RawImport *i) { int r; assert(i); - + assert(i->local); assert(!i->final_path); assert(!i->temp_path); assert(i->output_fd < 0); - i->final_path = strjoin(i->image_root, "/", i->local, ".raw"); - if (!i->final_path) - return log_oom(); + if (i->flags & IMPORT_DIRECT) { + (void) mkdir_parents_label(i->local, 0700); - r = tempfn_random(i->final_path, NULL, &i->temp_path); - if (r < 0) - return log_oom(); + /* In direct mode we just open/create the local path and truncate it (like shell > + * redirection would do it) — except if an offset was passed, in which case we are supposed + * to operate on a section of the file only, in which case we apparently work on an some + * existing thing (i.e. are not the sole thing stored in the file), in which case we will + * neither truncate nor create. */ + + i->output_fd = open(i->local, O_RDWR|O_NOCTTY|O_CLOEXEC|(i->offset == UINT64_MAX ? O_TRUNC|O_CREAT : 0), 0664); + if (i->output_fd < 0) + return log_error_errno(errno, "Failed to open destination '%s': %m", i->local); + + if (i->offset == UINT64_MAX) + (void) import_set_nocow_and_log(i->output_fd, i->local); + } else { + i->final_path = strjoin(i->image_root, "/", i->local, ".raw"); + if (!i->final_path) + return log_oom(); + + r = tempfn_random(i->final_path, NULL, &i->temp_path); + if (r < 0) + return log_oom(); - (void) mkdir_parents_label(i->temp_path, 0700); + (void) mkdir_parents_label(i->temp_path, 0700); - i->output_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); - if (i->output_fd < 0) - return log_error_errno(errno, "Failed to open destination %s: %m", i->temp_path); + i->output_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); + if (i->output_fd < 0) + return log_error_errno(errno, "Failed to open destination '%s': %m", i->temp_path); + + (void) import_set_nocow_and_log(i->output_fd, i->temp_path); + } + + if (fstat(i->output_fd, &i->output_stat) < 0) + return log_error_errno(errno, "Failed to stat() output file: %m"); + + if (!S_ISREG(i->output_stat.st_mode) && !S_ISBLK(i->output_stat.st_mode)) + return log_error_errno(SYNTHETIC_ERRNO(EBADFD), + "Target file is not a regular file or block device"); + + if (i->offset != UINT64_MAX) { + if (lseek(i->output_fd, i->offset, SEEK_SET) == (off_t) -1) + return log_error_errno(errno, "Failed to seek to offset: %m"); + } - (void) import_set_nocow_and_log(i->output_fd, i->temp_path); return 0; } @@ -265,7 +319,7 @@ static int raw_import_try_reflink(RawImport *i) { if (i->compress.type != IMPORT_COMPRESS_UNCOMPRESSED) return 0; - if (!S_ISREG(i->st.st_mode)) + if (!S_ISREG(i->input_stat.st_mode) || !S_ISREG(i->output_stat.st_mode)) return 0; p = lseek(i->input_fd, 0, SEEK_CUR); @@ -280,21 +334,57 @@ static int raw_import_try_reflink(RawImport *i) { if (r >= 0) return 1; + log_debug_errno(r, "Couldn't establish reflink, using copy: %m"); return 0; } static int raw_import_write(const void *p, size_t sz, void *userdata) { RawImport *i = userdata; - ssize_t n; + bool too_much = false; + int r; - n = sparse_write(i->output_fd, p, sz, 64); - if (n < 0) - return (int) n; - if ((size_t) n < sz) - return -EIO; + assert(i); + assert(p); + assert(sz > 0); + + if (i->written_uncompressed >= UINT64_MAX - sz) + return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), "File too large, overflow"); + + if (i->size_max != UINT64_MAX) { + if (i->written_uncompressed >= i->size_max) { + too_much = true; + goto finish; + } + + if (i->written_uncompressed + sz > i->size_max) { + too_much = true; + sz = i->size_max - i->written_uncompressed; /* since we have the data in memory + * already, we might as well write it to + * disk to the max */ + } + } + + /* Generate sparse file if we created/truncated the file */ + if (S_ISREG(i->output_stat.st_mode)) { + ssize_t n; + + n = sparse_write(i->output_fd, p, sz, 64); + if (n < 0) + return log_error_errno((int) n, "Failed to write file: %m"); + if ((size_t) n < sz) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write"); + } else { + r = loop_write(i->output_fd, p, sz, false); + if (r < 0) + return log_error_errno(r, "Failed to write file: %m"); + } i->written_uncompressed += sz; +finish: + if (too_much) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "File too large"); + return 0; } @@ -382,15 +472,22 @@ static int raw_import_on_defer(sd_event_source *s, void *userdata) { return raw_import_process(i); } -int raw_import_start(RawImport *i, int fd, const char *local, ImportFlags flags) { +int raw_import_start( + RawImport *i, + int fd, + const char *local, + uint64_t offset, + uint64_t size_max, + ImportFlags flags) { int r; assert(i); assert(fd >= 0); assert(local); - assert(!(flags & ~IMPORT_FLAGS_MASK)); + assert(!(flags & ~IMPORT_FLAGS_MASK_RAW)); + assert(offset == UINT64_MAX || FLAGS_SET(flags, IMPORT_DIRECT)); - if (!hostname_is_valid(local, 0)) + if (!import_validate_local(local, flags)) return -EINVAL; if (i->input_fd >= 0) @@ -405,8 +502,10 @@ int raw_import_start(RawImport *i, int fd, const char *local, ImportFlags flags) return r; i->flags = flags; + i->offset = offset; + i->size_max = size_max; - if (fstat(fd, &i->st) < 0) + if (fstat(fd, &i->input_stat) < 0) return -errno; r = sd_event_add_io(i->event, &i->input_event_source, fd, EPOLLIN, raw_import_on_input, i); @@ -422,5 +521,5 @@ int raw_import_start(RawImport *i, int fd, const char *local, ImportFlags flags) return r; i->input_fd = fd; - return r; + return 0; } diff --git a/src/import/import-raw.h b/src/import/import-raw.h index e99703c155..63384eb171 100644 --- a/src/import/import-raw.h +++ b/src/import/import-raw.h @@ -16,4 +16,4 @@ RawImport* raw_import_unref(RawImport *import); DEFINE_TRIVIAL_CLEANUP_FUNC(RawImport*, raw_import_unref); -int raw_import_start(RawImport *i, int fd, const char *local, ImportFlags flags); +int raw_import_start(RawImport *i, int fd, const char *local, uint64_t offset, uint64_t size_max, ImportFlags flags); diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 7c4e5127a9..bb67862d62 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -15,6 +15,7 @@ #include "import-common.h" #include "import-compress.h" #include "import-tar.h" +#include "install-file.h" #include "io-util.h" #include "machine-pool.h" #include "mkdir.h" @@ -54,7 +55,7 @@ struct TarImport { uint64_t written_compressed; uint64_t written_uncompressed; - struct stat st; + struct stat input_stat; pid_t tar_pid; @@ -136,13 +137,13 @@ static void tar_import_report_progress(TarImport *i) { assert(i); /* We have no size information, unless the source is a regular file */ - if (!S_ISREG(i->st.st_mode)) + if (!S_ISREG(i->input_stat.st_mode)) return; - if (i->written_compressed >= (uint64_t) i->st.st_size) + if (i->written_compressed >= (uint64_t) i->input_stat.st_size) percent = 100; else - percent = (unsigned) ((i->written_compressed * UINT64_C(100)) / (uint64_t) i->st.st_size); + percent = (unsigned) ((i->written_compressed * UINT64_C(100)) / (uint64_t) i->input_stat.st_size); if (percent == i->last_percent) return; @@ -157,12 +158,11 @@ static void tar_import_report_progress(TarImport *i) { } static int tar_import_finish(TarImport *i) { + const char *d; int r; assert(i); assert(i->tar_fd >= 0); - assert(i->temp_path); - assert(i->final_path); i->tar_fd = safe_close(i->tar_fd); @@ -175,22 +175,20 @@ static int tar_import_finish(TarImport *i) { return -EPROTO; } - r = import_mangle_os_tree(i->temp_path); + assert_se(d = i->temp_path ?: i->local); + + r = import_mangle_os_tree(d); if (r < 0) return r; - if (i->flags & IMPORT_READ_ONLY) { - r = import_make_read_only(i->temp_path); - if (r < 0) - return r; - } - - if (i->flags & IMPORT_FORCE) - (void) rm_rf(i->final_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - - r = rename_noreplace(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path); + r = install_file( + AT_FDCWD, d, + AT_FDCWD, i->final_path, + (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 move image into place: %m"); + return log_error_errno(r, "Failed to move '%s' into place: %m", i->final_path ?: i->local); i->temp_path = mfree(i->temp_path); @@ -198,33 +196,54 @@ static int tar_import_finish(TarImport *i) { } static int tar_import_fork_tar(TarImport *i) { + const char *d, *root; int r; assert(i); - + assert(i->local); assert(!i->final_path); assert(!i->temp_path); assert(i->tar_fd < 0); - i->final_path = path_join(i->image_root, i->local); - if (!i->final_path) - return log_oom(); + if (i->flags & IMPORT_DIRECT) { + d = i->local; + root = NULL; + } else { + i->final_path = path_join(i->image_root, i->local); + if (!i->final_path) + return log_oom(); - r = tempfn_random(i->final_path, NULL, &i->temp_path); - if (r < 0) - return log_oom(); + r = tempfn_random(i->final_path, NULL, &i->temp_path); + if (r < 0) + return log_oom(); + + d = i->temp_path; + root = i->image_root; + } + + assert(d); - (void) mkdir_parents_label(i->temp_path, 0700); + (void) mkdir_parents_label(d, 0700); - r = btrfs_subvol_make_fallback(i->temp_path, 0755); + if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE)) + (void) rm_rf(d, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + + if (i->flags & IMPORT_BTRFS_SUBVOL) + r = btrfs_subvol_make_fallback(d, 0755); + else + r = mkdir(d, 0755) < 0 ? -errno : 0; + 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", i->temp_path); - if (r > 0) { /* actually btrfs subvol */ - (void) import_assign_pool_quota_and_warn(i->image_root); - (void) import_assign_pool_quota_and_warn(i->temp_path); + return log_error_errno(r, "Failed to create directory/subvolume %s: %m", d); + if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */ + if (!(i->flags & IMPORT_DIRECT)) + (void) import_assign_pool_quota_and_warn(root); + (void) import_assign_pool_quota_and_warn(d); } - i->tar_fd = import_fork_tar_x(i->temp_path, &i->tar_pid); + i->tar_fd = import_fork_tar_x(d, &i->tar_pid); if (i->tar_fd < 0) return i->tar_fd; @@ -327,9 +346,9 @@ int tar_import_start(TarImport *i, int fd, const char *local, ImportFlags flags) assert(i); assert(fd >= 0); assert(local); - assert(!(flags & ~IMPORT_FLAGS_MASK)); + assert(!(flags & ~IMPORT_FLAGS_MASK_TAR)); - if (!hostname_is_valid(local, 0)) + if (!import_validate_local(local, flags)) return -EINVAL; if (i->input_fd >= 0) @@ -345,7 +364,7 @@ int tar_import_start(TarImport *i, int fd, const char *local, ImportFlags flags) i->flags = flags; - if (fstat(fd, &i->st) < 0) + if (fstat(fd, &i->input_stat) < 0) return -errno; r = sd_event_add_io(i->event, &i->input_event_source, fd, EPOLLIN, tar_import_on_input, i); @@ -361,5 +380,5 @@ int tar_import_start(TarImport *i, int fd, const char *local, ImportFlags flags) return r; i->input_fd = fd; - return r; + return 0; } diff --git a/src/import/import.c b/src/import/import.c index cc90732d71..87ed8767e5 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -8,26 +8,114 @@ #include "alloc-util.h" #include "discover-image.h" +#include "env-util.h" #include "fd-util.h" #include "fs-util.h" #include "hostname-util.h" #include "import-raw.h" #include "import-tar.h" #include "import-util.h" +#include "io-util.h" #include "main-func.h" +#include "parse-argument.h" +#include "parse-util.h" #include "signal-util.h" #include "string-util.h" +#include "terminal-util.h" #include "verbs.h" static const char *arg_image_root = "/var/lib/machines"; -static ImportFlags arg_import_flags = 0; +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 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); +static int normalize_local(const char *local, char **ret) { + _cleanup_free_ char *ll = NULL; + int r; + + assert(ret); + + if (arg_import_flags & IMPORT_DIRECT) { + + if (!local) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No local path specified."); + + if (!path_is_absolute(local)) { + ll = path_join(arg_image_root, local); + if (!ll) + return log_oom(); + + local = ll; + } + + if (!path_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Local path name '%s' is not valid.", local); + } else { + if (local) { + if (!hostname_is_valid(local, 0)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Local image name '%s' is not valid.", + local); + } else + local = "imported"; + + if (!FLAGS_SET(arg_import_flags, IMPORT_FORCE)) { + r = image_find(IMAGE_MACHINE, local, NULL, NULL); + if (r < 0) { + if (r != -ENOENT) + return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); + } else + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Image '%s' already exists.", + local); + } + } + + if (!ll) { + ll = strdup(local); + if (!ll) + return log_oom(); + } + + *ret = TAKE_PTR(ll); return 0; } +static int open_source(const char *path, const char *local, int *ret_open_fd) { + _cleanup_close_ int open_fd = -1; + int retval; + + assert(local); + assert(ret_open_fd); + + if (path) { + open_fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (open_fd < 0) + return log_error_errno(errno, "Failed to open raw image to import: %m"); + + retval = open_fd; + + if (arg_offset != UINT64_MAX) + log_info("Importing '%s', saving at offset %" PRIu64 " in '%s'.", path, arg_offset, local); + else + log_info("Importing '%s', saving as '%s'.", path, local); + } else { + _cleanup_free_ char *pretty = NULL; + + retval = STDIN_FILENO; + + (void) fd_get_path(STDIN_FILENO, &pretty); + + if (arg_offset != UINT64_MAX) + log_info("Importing '%s', saving at offset %" PRIu64 " in '%s'.", strempty(pretty), arg_offset, local); + else + log_info("Importing '%s', saving as '%s'.", strempty(pretty), local); + } + + *ret_open_fd = TAKE_FD(open_fd); + return retval; +} + static void on_tar_finished(TarImport *import, int error, void *userdata) { sd_event *event = userdata; assert(import); @@ -40,9 +128,9 @@ static void on_tar_finished(TarImport *import, int error, void *userdata) { static int import_tar(int argc, char *argv[], void *userdata) { _cleanup_(tar_import_unrefp) TarImport *import = NULL; + _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; const char *path = NULL, *local = NULL; - _cleanup_free_ char *ll = NULL; _cleanup_close_ int open_fd = -1; int r, fd; @@ -51,64 +139,44 @@ static int import_tar(int argc, char *argv[], void *userdata) { if (argc >= 3) local = empty_or_dash_to_null(argv[2]); - else if (path) - local = basename(path); + else if (path) { + _cleanup_free_ char *l = NULL; - if (local) { - r = tar_strip_suffixes(local, &ll); + r = path_extract_filename(path, &l); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from path '%s': %m", path); + + r = tar_strip_suffixes(l, &ll); if (r < 0) return log_oom(); local = ll; + } - if (!hostname_is_valid(local, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Local image name '%s' is not valid.", - local); - - if (!FLAGS_SET(arg_import_flags, IMPORT_FORCE)) { - r = image_find(IMAGE_MACHINE, local, NULL, NULL); - if (r < 0) { - if (r != -ENOENT) - return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); - } else - return log_error_errno(SYNTHETIC_ERRNO(EEXIST), - "Image '%s' already exists.", - local); - } - } else - local = "imported"; - - if (path) { - open_fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (open_fd < 0) - return log_error_errno(errno, "Failed to open tar image to import: %m"); - - fd = open_fd; - - log_info("Importing '%s', saving as '%s'.", path, local); - } else { - _cleanup_free_ char *pretty = NULL; + r = normalize_local(local, &normalized); + if (r < 0) + return r; - fd = STDIN_FILENO; + fd = open_source(path, normalized, &open_fd); + if (fd < 0) + return r; - (void) fd_get_path(fd, &pretty); - log_info("Importing '%s', saving as '%s'.", strna(pretty), local); - } - - 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"); + return r; - 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); + 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"); - r = tar_import_start(import, fd, local, arg_import_flags); + r = tar_import_start( + import, + fd, + normalized, + arg_import_flags & IMPORT_FLAGS_MASK_TAR); if (r < 0) return log_error_errno(r, "Failed to import image: %m"); @@ -132,9 +200,9 @@ static void on_raw_finished(RawImport *import, int error, void *userdata) { static int import_raw(int argc, char *argv[], void *userdata) { _cleanup_(raw_import_unrefp) RawImport *import = NULL; + _cleanup_free_ char *ll = NULL, *normalized = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; const char *path = NULL, *local = NULL; - _cleanup_free_ char *ll = NULL; _cleanup_close_ int open_fd = -1; int r, fd; @@ -143,64 +211,46 @@ static int import_raw(int argc, char *argv[], void *userdata) { if (argc >= 3) local = empty_or_dash_to_null(argv[2]); - else if (path) - local = basename(path); + else if (path) { + _cleanup_free_ char *l = NULL; + + r = path_extract_filename(path, &l); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from path '%s': %m", path); - if (local) { - r = raw_strip_suffixes(local, &ll); + r = raw_strip_suffixes(l, &ll); if (r < 0) return log_oom(); local = ll; + } - if (!hostname_is_valid(local, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Local image name '%s' is not valid.", - local); - - if (!FLAGS_SET(arg_import_flags, IMPORT_FORCE)) { - r = image_find(IMAGE_MACHINE, local, NULL, NULL); - if (r < 0) { - if (r != -ENOENT) - return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); - } else - return log_error_errno(SYNTHETIC_ERRNO(EEXIST), - "Image '%s' already exists.", - local); - } - } else - local = "imported"; - - if (path) { - open_fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (open_fd < 0) - return log_error_errno(errno, "Failed to open raw image to import: %m"); - - fd = open_fd; - - log_info("Importing '%s', saving as '%s'.", path, local); - } else { - _cleanup_free_ char *pretty = NULL; - - fd = STDIN_FILENO; + r = normalize_local(local, &normalized); + if (r < 0) + return r; - (void) fd_get_path(fd, &pretty); - log_info("Importing '%s', saving as '%s'.", strempty(pretty), local); - } + fd = open_source(path, normalized, &open_fd); + if (fd < 0) + return fd; - 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"); + return r; - 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); + 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"); - r = raw_import_start(import, fd, local, arg_import_flags); + r = raw_import_start( + import, + fd, + normalized, + arg_offset, + arg_size_max, + arg_import_flags & IMPORT_FLAGS_MASK_RAW); if (r < 0) return log_error_errno(r, "Failed to import image: %m"); @@ -214,17 +264,32 @@ static int import_raw(int argc, char *argv[], void *userdata) { static int help(int argc, char *argv[], void *userdata) { - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Import container or virtual machine images.\n\n" + printf("%1$s [OPTIONS...] {COMMAND} ...\n" + "\n%4$sImport container or virtual machine 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" + "\n%2$sOptions:%3$s\n" " -h --help Show this help\n" " --version Show package version\n" " --force Force creation of image\n" " --image-root=PATH Image root directory\n" - " --read-only Create a read-only image\n\n" - "Commands:\n" - " tar FILE [NAME] Import a TAR image\n" - " raw FILE [NAME] Import a RAW image\n", - program_invocation_short_name); + " --read-only Create a read-only image\n" + " --direct Import directly to specified file\n" + " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n" + " instead of a directory\n" + " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n" + " subvolume\n" + " --convert-qcow2=BOOL Controls whether to convert QCOW2 images to\n" + " 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", + program_invocation_short_name, + ansi_underline(), + ansi_normal(), + ansi_highlight(), + ansi_normal()); return 0; } @@ -236,6 +301,13 @@ static int parse_argv(int argc, char *argv[]) { ARG_FORCE, ARG_IMAGE_ROOT, ARG_READ_ONLY, + ARG_DIRECT, + ARG_BTRFS_SUBVOL, + ARG_BTRFS_QUOTA, + ARG_CONVERT_QCOW2, + ARG_SYNC, + ARG_OFFSET, + ARG_SIZE_MAX, }; static const struct option options[] = { @@ -244,10 +316,17 @@ static int parse_argv(int argc, char *argv[]) { { "force", no_argument, NULL, ARG_FORCE }, { "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, { "read-only", no_argument, NULL, ARG_READ_ONLY }, + { "direct", no_argument, NULL, ARG_DIRECT }, + { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL }, + { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA }, + { "convert-qcow2", required_argument, NULL, ARG_CONVERT_QCOW2 }, + { "sync", required_argument, NULL, ARG_SYNC }, + { "offset", required_argument, NULL, ARG_OFFSET }, + { "size-max", required_argument, NULL, ARG_SIZE_MAX }, {} }; - int c; + int r, c; assert(argc >= 0); assert(argv); @@ -274,6 +353,68 @@ static int parse_argv(int argc, char *argv[]) { arg_import_flags |= IMPORT_READ_ONLY; break; + case ARG_DIRECT: + arg_import_flags |= IMPORT_DIRECT; + break; + + case ARG_BTRFS_SUBVOL: + r = parse_boolean_argument("--btrfs-subvol=", optarg, NULL); + if (r < 0) + return r; + + SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r); + break; + + case ARG_BTRFS_QUOTA: + r = parse_boolean_argument("--btrfs-quota=", optarg, NULL); + if (r < 0) + return r; + + SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r); + break; + + case ARG_CONVERT_QCOW2: + r = parse_boolean_argument("--convert-qcow2=", optarg, NULL); + if (r < 0) + return r; + + SET_FLAG(arg_import_flags, IMPORT_CONVERT_QCOW2, r); + break; + + case ARG_SYNC: + r = parse_boolean_argument("--sync=", optarg, NULL); + if (r < 0) + return r; + + SET_FLAG(arg_import_flags, IMPORT_SYNC, r); + break; + + case ARG_OFFSET: { + uint64_t u; + + r = safe_atou64(optarg, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse --offset= argument: %s", optarg); + if (!FILE_SIZE_VALID(u)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", optarg); + + arg_offset = u; + break; + } + + case ARG_SIZE_MAX: { + uint64_t u; + + r = parse_size(optarg, 1024, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse --size-max= argument: %s", optarg); + if (!FILE_SIZE_VALID(u)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", optarg); + + arg_size_max = u; + break; + } + case '?': return -EINVAL; @@ -281,6 +422,15 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + /* Make sure offset+size is still in the valid range if both set */ + if (arg_offset != UINT64_MAX && arg_size_max != UINT64_MAX && + ((arg_size_max > (UINT64_MAX - arg_offset)) || + !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_import_flags, IMPORT_DIRECT)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode."); + return 1; } @@ -295,6 +445,31 @@ static int import_main(int argc, char *argv[]) { return dispatch_verb(argc, argv, verbs, NULL); } +static void parse_env(void) { + int r; + + /* Let's make these relatively low-level settings also controllable via env vars. User can then set + * them to systemd-import if they like to tweak behaviour */ + + r = getenv_bool("SYSTEMD_IMPORT_BTRFS_SUBVOL"); + if (r >= 0) + 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_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_import_flags, IMPORT_SYNC, r); + else if (r != -ENXIO) + log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_SYNC: %m"); +} + static int run(int argc, char *argv[]) { int r; @@ -302,9 +477,11 @@ static int run(int argc, char *argv[]) { log_parse_environment(); log_open(); + parse_env(); + r = parse_argv(argc, argv); if (r <= 0) - return 0; + return r; (void) ignore_signals(SIGPIPE); |