From: Michael A Cassaniti Date: Fri, 11 Aug 2023 06:41:56 +0000 (+1000) Subject: file-io: Fix copying sparse files X-Git-Tag: v255-rc1~750 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c2dfcbd48e780e49bfc11f1c34077f6c98891e40;p=thirdparty%2Fsystemd.git file-io: Fix copying sparse files 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. --- diff --git a/src/shared/copy.c b/src/shared/copy.c index 7e47dc002c1..d8355a837ce 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -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. */ diff --git a/src/test/test-copy.c b/src/test/test-copy.c index 72aea4efb6d..e22a620ade7 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -11,10 +11,12 @@ #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;