From: Lennart Poettering Date: Tue, 10 Feb 2026 14:41:22 +0000 (+0100) Subject: copy: add new flags that cause a seek to beginning of files before copying X-Git-Tag: v261-rc1~762 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5adfd44c9e7a7485e93b799399589e45d09fffea;p=thirdparty%2Fsystemd.git 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. --- diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 3724a1cfb94..c4693293ab9 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -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); diff --git a/src/repart/repart.c b/src/repart/repart.c index eb334a7c401..1bdeec6ea56 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -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 { diff --git a/src/shared/copy.c b/src/shared/copy.c index 445c246359a..3ac05c15b7b 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -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) diff --git a/src/shared/copy.h b/src/shared/copy.h index 6e4a3b177b3..92997353267 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -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 { diff --git a/src/test/test-copy.c b/src/test/test-copy.c index 758a597fc53..71930147824 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -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);