copy: add new flags that cause a seek to beginning of files before copying

This is quite useful in various cases where we so far did this manually.
This commit is contained in:
Lennart Poettering
2026-02-10 15:41:22 +01:00
committed by Luca Boccassi
parent 6fb5ec3dd1
commit 5adfd44c9e
5 changed files with 21 additions and 24 deletions

View File

@@ -554,10 +554,7 @@ static int copy_file_with_version_check(
* might be left at the end of the file. (Resetting before rather than after a copy attempt is safer
* because a previous attempt might have failed half-way, leaving the file offset at some undefined
* place.) */
if (lseek(source_fd, 0, SEEK_SET) < 0)
return log_error_errno(errno, "Failed to seek in \"%s\": %m", source_path);
r = copy_bytes(source_fd, write_fd, UINT64_MAX, COPY_REFLINK);
r = copy_bytes(source_fd, write_fd, UINT64_MAX, COPY_REFLINK|COPY_SEEK0_SOURCE);
if (r < 0)
return log_error_errno(r, "Failed to copy data from \"%s\" to \"%s\": %m", source_path, dest_path);

View File

@@ -5061,9 +5061,6 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget
if (lseek(whole_fd, p->offset, SEEK_SET) < 0)
return log_error_errno(errno, "Failed to seek to partition offset: %m");
if (lseek(t->fd, 0, SEEK_SET) < 0)
return log_error_errno(errno, "Failed to seek to start of temporary file: %m");
if (fstat(t->fd, &st) < 0)
return log_error_errno(errno, "Failed to stat temporary file: %m");
@@ -5072,7 +5069,7 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget
"Partition %" PRIu64 "'s contents (%s) don't fit in the partition (%s).",
p->partno, FORMAT_BYTES(st.st_size), FORMAT_BYTES(p->new_size));
r = copy_bytes(t->fd, whole_fd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_FSYNC);
r = copy_bytes(t->fd, whole_fd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_FSYNC|COPY_SEEK0_SOURCE);
if (r < 0)
return log_error_errno(r, "Failed to copy bytes to partition: %m");
} else {

View File

@@ -193,16 +193,26 @@ int copy_bytes_full(
if (fdt < 0)
return fdt;
if (FLAGS_SET(copy_flags, COPY_SEEK0_SOURCE) &&
lseek(fdf, 0, SEEK_SET) < 0)
return -errno;
if (FLAGS_SET(copy_flags, COPY_SEEK0_TARGET) &&
lseek(fdt, 0, SEEK_SET) < 0)
return -errno;
/* Try btrfs reflinks first. This only works on regular, seekable files, hence let's check the file offsets of
* source and destination first. */
if ((copy_flags & COPY_REFLINK)) {
off_t foffset;
foffset = lseek(fdf, 0, SEEK_CUR);
/* In reflink mode we need to know where the current file offset is, but if we just seeked to
* 0 anyway, we can suppress that. */
foffset = FLAGS_SET(copy_flags, COPY_SEEK0_SOURCE) ? 0 : lseek(fdf, 0, SEEK_CUR);
if (foffset >= 0) {
off_t toffset;
toffset = lseek(fdt, 0, SEEK_CUR);
toffset = FLAGS_SET(copy_flags, COPY_SEEK0_TARGET) ? 0 : lseek(fdt, 0, SEEK_CUR);
if (toffset >= 0) {
if (foffset == 0 && toffset == 0 && max_bytes == UINT64_MAX)

View File

@@ -34,6 +34,8 @@ typedef enum CopyFlags {
COPY_NOCOW_AFTER = 1 << 20,
COPY_PRESERVE_FS_VERITY = 1 << 21, /* Preserve fs-verity when copying. */
COPY_MERGE_APPLY_STAT = 1 << 22, /* When we reuse an existing directory inode, apply source ownership/mode/xattrs/timestamps */
COPY_SEEK0_SOURCE = 1 << 23, /* Seek back to start of source file before copying */
COPY_SEEK0_TARGET = 1 << 24, /* Seek back to start of target file before copying */
} CopyFlags;
typedef enum DenyType {

View File

@@ -364,9 +364,7 @@ static void test_copy_bytes_regular_file_one(const char *src, bool try_reflink,
/* Make sure the file is now higher than max_bytes */
assert_se(ftruncate(fd2, max_bytes + 1) == 0);
assert_se(lseek(fd2, 0, SEEK_SET) == 0);
r = copy_bytes(fd2, fd3, max_bytes, try_reflink ? COPY_REFLINK : 0);
r = copy_bytes(fd2, fd3, max_bytes, COPY_SEEK0_SOURCE | (try_reflink ? COPY_REFLINK : 0));
if (max_bytes == UINT64_MAX)
assert_se(r == 0);
else
@@ -460,9 +458,8 @@ TEST_RET(copy_holes) {
assert_se(lseek(fd, 0, SEEK_END) == 2 * blksz);
/* Only ftruncate() can create holes at the end of a file. */
assert_se(ftruncate(fd, 3 * blksz) >= 0);
assert_se(lseek(fd, 0, SEEK_SET) >= 0);
assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_HOLES) >= 0);
assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_SEEK0_SOURCE|COPY_HOLES) >= 0);
/* Test that the hole starts at the beginning of the file. */
assert_se(lseek(fd_copy, 0, SEEK_HOLE) == 0);
@@ -526,26 +523,20 @@ TEST_RET(copy_holes_with_gaps) {
assert_se(st.st_size == 3 * blksz);
/* Copy to the middle of the second hole */
assert_se(lseek(fd, 0, SEEK_SET) >= 0);
assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0);
assert_se(ftruncate(fd_copy, 0) >= 0);
assert_se(copy_bytes(fd, fd_copy, 4 * blksz, COPY_HOLES) >= 0);
assert_se(copy_bytes(fd, fd_copy, 4 * blksz, COPY_SEEK0_SOURCE|COPY_SEEK0_TARGET|COPY_HOLES) >= 0);
ASSERT_OK_ERRNO(fstat(fd_copy, &st));
assert_se(st.st_size == 4 * blksz);
/* Copy to the end of the second hole */
assert_se(lseek(fd, 0, SEEK_SET) >= 0);
assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0);
assert_se(ftruncate(fd_copy, 0) >= 0);
assert_se(copy_bytes(fd, fd_copy, 5 * blksz, COPY_HOLES) >= 0);
assert_se(copy_bytes(fd, fd_copy, 5 * blksz, COPY_SEEK0_SOURCE|COPY_SEEK0_TARGET|COPY_HOLES) >= 0);
ASSERT_OK_ERRNO(fstat(fd_copy, &st));
assert_se(st.st_size == 5 * blksz);
/* Copy everything */
assert_se(lseek(fd, 0, SEEK_SET) >= 0);
assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0);
assert_se(ftruncate(fd_copy, 0) >= 0);
assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_HOLES) >= 0);
assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_SEEK0_SOURCE|COPY_SEEK0_TARGET|COPY_HOLES) >= 0);
ASSERT_OK_ERRNO(fstat(fd_copy, &st));
assert_se(st.st_size == 6 * blksz);