diff --git a/src/import/import-common.c b/src/import/import-common.c index 7633aadc53..327da2707e 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -6,6 +6,7 @@ #include "sd-event.h" #include "acl-util.h" +#include "blockdev-util.h" #include "capability-util.h" #include "compress.h" #include "dirent-util.h" @@ -432,3 +433,26 @@ int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags) { return 0; } + +int import_zero_fill(int fd, uint64_t offset, uint64_t size_max, uint64_t skip) { + int r; + + assert(fd >= 0); + assert(offset != UINT64_MAX); + assert(size_max != UINT64_MAX); + + /* Fill up whatever is left of the destination range (i.e. the range starting at 'offset' and + * 'size_max' bytes long, minus the first 'skip' bytes we already wrote) with zeroes, so that no + * remnants of previous, larger contents survive after our data. */ + + if (skip > size_max) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Number of bytes to skip larger than maximum size of destination range."); + if (offset > UINT64_MAX - skip) + return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), "Destination offset overflows."); + + r = blockdev_zero_out(fd, offset + skip, size_max - skip); + if (r < 0) + return log_error_errno(r, "Failed to fill up destination with zeroes: %m"); + + return 0; +} diff --git a/src/import/import-common.h b/src/import/import-common.h index 68525a777d..ea38a219e3 100644 --- a/src/import/import-common.h +++ b/src/import/import-common.h @@ -16,16 +16,17 @@ typedef enum ImportFlags { IMPORT_DIRECT = 1 << 6, /* import without rename games */ IMPORT_SYNC = 1 << 7, /* fsync() right before we are done */ IMPORT_FOREIGN_UID = 1 << 8, /* tar: go via nsresourced/mountfsd and make owned by foreign UID */ + IMPORT_ZERO_FILL = 1 << 9, /* raw: fill up the remainder of the destination range (as defined by offset/size max) with zeroes */ /* When pulling these flags are defined too */ - IMPORT_PULL_SETTINGS = 1 << 9, /* download .nspawn settings file */ - IMPORT_PULL_ROOTHASH = 1 << 10, /* only for raw: download .roothash file for verity */ - IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 11, /* only for raw: download .roothash.p7s file for verity */ - IMPORT_PULL_VERITY = 1 << 12, /* only for raw: download .verity file for verity */ + IMPORT_PULL_SETTINGS = 1 << 10, /* download .nspawn settings file */ + IMPORT_PULL_ROOTHASH = 1 << 11, /* only for raw: download .roothash file for verity */ + IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 12, /* only for raw: download .roothash.p7s file for verity */ + IMPORT_PULL_VERITY = 1 << 13, /* only for raw: download .verity file for verity */ /* The supported flags for the tar and raw importing */ IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC|IMPORT_FOREIGN_UID, - IMPORT_FLAGS_MASK_RAW = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_CONVERT_QCOW2|IMPORT_DIRECT|IMPORT_SYNC, + IMPORT_FLAGS_MASK_RAW = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_CONVERT_QCOW2|IMPORT_DIRECT|IMPORT_SYNC|IMPORT_ZERO_FILL, /* The supported flags for the tar, raw, oci pulling */ IMPORT_PULL_FLAGS_MASK_TAR = IMPORT_FLAGS_MASK_TAR|IMPORT_PULL_KEEP_DOWNLOAD|IMPORT_PULL_SETTINGS, @@ -49,3 +50,5 @@ int import_allocate_event_with_signals(sd_event **ret); int import_make_foreign_userns(int *userns_fd); int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags); + +int import_zero_fill(int fd, uint64_t offset, uint64_t size_max, uint64_t skip); diff --git a/src/import/import-raw.c b/src/import/import-raw.c index 62aaf6bf86..3c22e954eb 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -217,6 +217,12 @@ static int raw_import_finish(RawImport *i) { assert(i); assert(i->output_fd >= 0); + if (FLAGS_SET(i->flags, IMPORT_ZERO_FILL)) { + r = import_zero_fill(i->output_fd, i->offset, i->size_max, i->written_uncompressed); + if (r < 0) + return r; + } + /* Nothing of what is below applies to block devices */ if (S_ISBLK(i->output_stat.st_mode)) { @@ -510,6 +516,7 @@ int raw_import_start( assert(local); assert(!(flags & ~IMPORT_FLAGS_MASK_RAW)); assert(offset == UINT64_MAX || FLAGS_SET(flags, IMPORT_DIRECT)); + assert(!FLAGS_SET(flags, IMPORT_ZERO_FILL) || (offset != UINT64_MAX && size_max != UINT64_MAX)); if (!import_validate_local(local, flags)) return -EINVAL; diff --git a/src/import/import.c b/src/import/import.c index 190ad6079f..2e9378cba2 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -343,6 +343,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { SET_FLAG(arg_import_flags, IMPORT_SYNC, r); break; + OPTION_LONG("zero-fill", "BOOL", "Controls whether to fill up the destination range with zeroes"): + r = parse_boolean_argument("--zero-fill=", opts.arg, NULL); + if (r < 0) + return r; + SET_FLAG(arg_import_flags, IMPORT_ZERO_FILL, r); + break; + OPTION_LONG("offset", "BYTES", "Offset to seek to in destination"): { uint64_t u; @@ -397,6 +404,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_import_flags, IMPORT_DIRECT)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode."); + if (FLAGS_SET(arg_import_flags, IMPORT_ZERO_FILL) && (arg_offset == UINT64_MAX || arg_size_max == UINT64_MAX)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Zero filling only supported if both --offset= and --size-max= are specified."); + if (!arg_image_root) { r = image_root_pick(arg_runtime_scope < 0 ? RUNTIME_SCOPE_SYSTEM : arg_runtime_scope, arg_class, /* runtime= */ false, &arg_image_root); if (r < 0) diff --git a/src/import/pull-job.c b/src/import/pull-job.c index a819911ca8..85bdf79f1a 100644 --- a/src/import/pull-job.c +++ b/src/import/pull-job.c @@ -12,6 +12,7 @@ #include "fd-util.h" #include "format-util.h" #include "hexdecoct.h" +#include "import-common.h" #include "io-util.h" #include "log.h" #include "parse-util.h" @@ -458,6 +459,12 @@ static int pull_job_curl_on_finished(CurlSlot *slot, CURL *curl, CURLcode result return pull_job_finish(j, log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Checksum of downloaded resource does not match expected checksum, yikes.")); } + if (j->disk_fd >= 0 && j->zero_fill) { + r = import_zero_fill(j->disk_fd, j->offset, j->uncompressed_max, j->written_uncompressed); + if (r < 0) + return pull_job_finish(j, r); + } + /* Do a couple of finishing disk operations, but only if we are the sole owner of the file (i.e. no * offset is specified, which indicates we only own the file partially) */ diff --git a/src/import/pull-job.h b/src/import/pull-job.h index 2314cc2452..2be0158b28 100644 --- a/src/import/pull-job.h +++ b/src/import/pull-job.h @@ -76,6 +76,7 @@ typedef struct PullJob { bool sync; bool force_memory; + bool zero_fill; char *authentication_challenge; } PullJob; diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index 6a61a38960..309e12b01e 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -810,6 +810,7 @@ int raw_pull_start( assert((verify < 0) || !iovec_is_set(checksum)); assert(!(flags & ~IMPORT_PULL_FLAGS_MASK_RAW)); assert(offset == UINT64_MAX || FLAGS_SET(flags, IMPORT_DIRECT)); + assert(!FLAGS_SET(flags, IMPORT_ZERO_FILL) || (offset != UINT64_MAX && size_max != UINT64_MAX)); assert(!(flags & (IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY)) || !(flags & IMPORT_DIRECT)); assert(!(flags & (IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY)) || !iovec_is_set(checksum)); @@ -859,6 +860,7 @@ int raw_pull_start( p->raw_job->uncompressed_max = size_max; if (offset != UINT64_MAX) p->raw_job->offset = p->offset = offset; + p->raw_job->zero_fill = FLAGS_SET(flags, IMPORT_ZERO_FILL); if (!FLAGS_SET(flags, IMPORT_DIRECT)) { r = pull_find_old_etags(url, p->image_root, DT_REG, ".raw-", ".raw", &p->raw_job->old_etags); diff --git a/src/import/pull.c b/src/import/pull.c index 7f5a90196f..a6e1b1694f 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -459,6 +459,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { SET_FLAG(arg_import_flags, IMPORT_SYNC, r); break; + OPTION_LONG("zero-fill", "BOOL", "Controls whether to fill up the destination range with zeroes"): + r = parse_boolean_argument("--zero-fill=", opts.arg, NULL); + if (r < 0) + return r; + SET_FLAG(arg_import_flags, IMPORT_ZERO_FILL, r); + break; + OPTION_LONG("offset", "BYTES", "Offset to seek to in destination"): { uint64_t u; @@ -523,6 +530,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_import_flags, IMPORT_DIRECT)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode."); + if (FLAGS_SET(arg_import_flags, IMPORT_ZERO_FILL) && (arg_offset == UINT64_MAX || arg_size_max == UINT64_MAX)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Zero filling only supported if both --offset= and --size-max= are specified."); + if (iovec_is_set(&arg_checksum) && (arg_import_flags & (IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY)) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Literal checksum verification only supported if no associated files are downloaded.");