summaryrefslogtreecommitdiffstats
path: root/src/import
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2021-01-22 17:40:51 +0100
committerLennart Poettering <lennart@poettering.net>2021-08-17 10:08:58 +0200
commitd32a5841fb1aabc0e2fd138d78b5f66d31b3ba77 (patch)
tree43da4bbf04be82965f3a4f4657e8fd03ecd96ca1 /src/import
parentshared: add generic helper tools for installing files/dir trees (diff)
downloadsystemd-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.c36
-rw-r--r--src/import/import-common.h18
-rw-r--r--src/import/import-raw.c217
-rw-r--r--src/import/import-raw.h2
-rw-r--r--src/import/import-tar.c91
-rw-r--r--src/import/import.c379
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);