]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
copy: add new flags that cause a seek to beginning of files before copying
authorLennart Poettering <lennart@amutable.com>
Tue, 10 Feb 2026 14:41:22 +0000 (15:41 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Mon, 23 Mar 2026 19:19:46 +0000 (19:19 +0000)
This is quite useful in various cases where we so far did this manually.

src/bootctl/bootctl-install.c
src/repart/repart.c
src/shared/copy.c
src/shared/copy.h
src/test/test-copy.c

index 3724a1cfb940260ea264be5be7036f4937854f14..c4693293ab9096b8ca92d3f5e1642f64c31b1829 100644 (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);
 
index eb334a7c4013d7d03fda6eaa4fc635a7859d3bc0..1bdeec6ea5644dc650d6c0d1c2da78bd5f1f57bc 100644 (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 {
index 445c246359a7abb472f3768d4421423f766d8a66..3ac05c15b7b2a7c5f36beca5838c53fc5c94ef50 100644 (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)
index 6e4a3b177b337a6ea55b3e96457297ec2e00187c..929973532678e0ce5841b597457c5724ce278c9e 100644 (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 {
index 758a597fc539a8391b3fdf13b4bcdc8af363f3b2..719301478246e1f4f570e12b36dd2ecf944f60af 100644 (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);