]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
import: add new "--direct" mode + add controls for turning certain features on/off
authorLennart Poettering <lennart@poettering.net>
Fri, 22 Jan 2021 16:40:51 +0000 (17:40 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 17 Aug 2021 08:08:58 +0000 (10:08 +0200)
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.

src/import/import-common.c
src/import/import-common.h
src/import/import-raw.c
src/import/import-raw.h
src/import/import-tar.c
src/import/import.c

index f77564c41dc53f8fa8ca5cf3270083f42a37639c..53aed6a514cc0bf7a516a85bc145c91b987bec2c 100644 (file)
@@ -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;
+}
index d7e8fc485f31e87f0776afa50b3698d5c76044d1..36052a295226104b78fb56921067e0265167f149 100644 (file)
@@ -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);
index 649cffb55f1021b4a5a966f5711ba4208832163d..9a87dd6f10a32e6ec9831363f9c77b70338f15d8 100644 (file)
@@ -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;
 }
index e99703c15597c1d0dd27da288e7fbb0e1eaa206f..63384eb171f539903983f5e5cb23ec4c835e7164 100644 (file)
@@ -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);
index 7c4e5127a9fb495e475910dab7464ebedfb3ffdc..bb67862d620039bb6e7bdcb1379d81af127f9815 100644 (file)
@@ -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;
 }
index cc90732d7188dca0aa1d413c7ed25192aaffe792..87ed8767e56d02732ce660046971f0c8df9d4030 100644 (file)
 
 #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);