]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
file-io: Fix copying sparse files
authorMichael A Cassaniti <michael@cassaniti.id.au>
Fri, 11 Aug 2023 06:41:56 +0000 (16:41 +1000)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 11 Aug 2023 12:30:54 +0000 (14:30 +0200)
This change makes sure a data copy using copy_bytes() does not exceed the
max_bytes value when using COPY_HOLES and max_bytes stops before the next
data section.

src/shared/copy.c
src/test/test-copy.c

index 7e47dc002c148d4e335933707ec9458da96810e5..d8355a837ce6d766e0c0650613372ff2ff0fe77d 100644 (file)
@@ -275,9 +275,22 @@ int copy_bytes_full(
                         /* If we're in a hole (current offset is not a data offset), create a hole of the
                          * same size in the target file. */
                         if (e > c) {
-                                r = create_hole(fdt, e - c);
+                                /* Make sure our new hole doesn't go over the maximum size we're allowed to copy. */
+                                n = MIN(max_bytes, (uint64_t) e - c);
+                                r = create_hole(fdt, n);
                                 if (r < 0)
                                         return r;
+
+                                /* Make sure holes are taken into account in the maximum size we're supposed to copy. */
+                                if (max_bytes != UINT64_MAX) {
+                                        max_bytes -= n;
+                                        if (max_bytes <= 0)
+                                                break;
+                                }
+
+                                /* Update the size we're supposed to copy in this iteration if needed. */
+                                if (m > max_bytes)
+                                        m = max_bytes;
                         }
 
                         c = e; /* Set c to the start of the data segment. */
index 72aea4efb6d957afc193304a48f6df822a99da13..e22a620ade7f835e73409e380dcc47772a6f0e9d 100644 (file)
 #include "fileio.h"
 #include "fs-util.h"
 #include "hexdecoct.h"
+#include "io-util.h"
 #include "log.h"
 #include "macro.h"
 #include "mkdir.h"
 #include "path-util.h"
+#include "random-util.h"
 #include "rm-rf.h"
 #include "string-util.h"
 #include "strv.h"
@@ -436,6 +438,77 @@ TEST_RET(copy_holes) {
         return 0;
 }
 
+TEST_RET(copy_holes_with_gaps) {
+        _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+        _cleanup_close_ int tfd = -EBADF, fd = -EBADF, fd_copy = -EBADF;
+        struct stat st;
+        off_t blksz;
+        char *buf;
+        int r;
+
+        assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0);
+        assert_se((fd = openat(tfd, "src", O_CREAT | O_RDWR, 0600)) >= 0);
+        assert_se((fd_copy = openat(tfd, "dst", O_CREAT | O_WRONLY, 0600)) >= 0);
+
+        assert_se(fstat(fd, &st) >= 0);
+        blksz = st.st_blksize;
+        buf = alloca_safe(blksz);
+        memset(buf, 1, blksz);
+
+        /* Create a file with:
+         *  - hole of 1 block
+         *  - data of 2 block
+         *  - hole of 2 blocks
+         *  - data of 1 block
+         *
+         * Since sparse files are based on blocks and not bytes, we need to make
+         * sure that the holes are aligned to the block size.
+         */
+
+        r = RET_NERRNO(fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, blksz));
+        if (ERRNO_IS_NOT_SUPPORTED(r))
+                return log_tests_skipped("Filesystem doesn't support hole punching");
+
+        assert_se(lseek(fd, blksz, SEEK_CUR) >= 0);
+        assert_se(loop_write(fd, buf, blksz, 0) >= 0);
+        assert_se(loop_write(fd, buf, blksz, 0) >= 0);
+        assert_se(lseek(fd, 2 * blksz, SEEK_CUR) >= 0);
+        assert_se(loop_write(fd, buf, blksz, 0) >= 0);
+        assert_se(lseek(fd, 0, SEEK_SET) >= 0);
+        assert_se(fsync(fd) >= 0);
+
+        /* Copy to the start of the second hole */
+        assert_se(copy_bytes(fd, fd_copy, 3 * blksz, COPY_HOLES) >= 0);
+        assert_se(fstat(fd_copy, &st) >= 0);
+        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(fstat(fd_copy, &st) >= 0);
+        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(fstat(fd_copy, &st) >= 0);
+        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(fstat(fd_copy, &st) >= 0);
+        assert_se(st.st_size == 6 * blksz);
+
+        return 0;
+}
+
 TEST(copy_lock) {
         _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
         _cleanup_close_ int tfd = -EBADF, fd = -EBADF;