return 0;
}
+static int create_hole(int fd, off_t size) {
+ off_t offset;
+ off_t end;
+
+ offset = lseek(fd, 0, SEEK_CUR);
+ if (offset < 0)
+ return -errno;
+
+ end = lseek(fd, 0, SEEK_END);
+ if (end < 0)
+ return -errno;
+
+ /* If we're not at the end of the target file, punch a hole in the existing space using fallocate(). */
+
+ if (offset < end && fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, offset, MIN(size, end - offset)) < 0)
+ return -errno;
+
+ if (end - offset >= size) {
+ /* If we've created the full hole, set the file pointer to the end of the hole we created and exit. */
+ if (lseek(fd, offset + size, SEEK_SET) < 0)
+ return -errno;
+
+ return 0;
+ }
+
+ /* If we haven't created the full hole, use ftruncate() to grow the file (and the hole) to the
+ * required size and move the file pointer to the end of the file. */
+
+ size -= end - offset;
+
+ if (ftruncate(fd, end + size) < 0)
+ return -errno;
+
+ if (lseek(fd, 0, SEEK_END) < 0)
+ return -errno;
+
+ return 0;
+}
+
int copy_bytes_full(
int fdf, int fdt,
uint64_t max_bytes,
/* 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 && lseek(fdt, e - c, SEEK_CUR) < 0)
- return -errno;
+ if (e > c) {
+ r = create_hole(fdt, e - c);
+ if (r < 0)
+ return r;
+ }
c = e; /* Set c to the start of the data segment. */
char fn[] = "/var/tmp/test-copy-hole-fd-XXXXXX";
char fn_copy[] = "/var/tmp/test-copy-hole-fd-XXXXXX";
struct stat stat;
+ off_t blksz;
int r, fd, fd_copy;
fd = mkostemp_safe(fn);
return log_tests_skipped("Filesystem doesn't support hole punching");
assert_se(r >= 0);
- /* We need to make sure to create a large enough hole and to write some data after it, otherwise
- * filesystems (btrfs) might silently discard it. */
+ assert_se(fstat(fd, &stat) >= 0);
+ blksz = stat.st_blksize;
+ char buf[blksz];
- assert_se(lseek(fd, 1024 * 1024, SEEK_CUR) >= 0);
- assert_se(write(fd, "abc", strlen("abc")) >= 0);
+ /* We need to make sure to create hole in multiples of the block size, otherwise filesystems (btrfs)
+ * might silently truncate/extend the holes. */
+
+ assert_se(lseek(fd, blksz, SEEK_CUR) >= 0);
+ assert_se(write(fd, buf, blksz) >= 0);
+ 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);
/* Test that the hole starts at the beginning of the file. */
assert_se(lseek(fd_copy, 0, SEEK_HOLE) == 0);
/* Test that the hole has the expected size. */
- assert_se(lseek(fd_copy, 0, SEEK_DATA) == 1024 * 1024);
+ assert_se(lseek(fd_copy, 0, SEEK_DATA) == blksz);
+ assert_se(lseek(fd_copy, blksz, SEEK_HOLE) == 2 * blksz);
+ assert_se(lseek(fd_copy, 2 * blksz, SEEK_DATA) < 0 && errno == ENXIO);
/* Test that the copied file has the correct size. */
assert_se(fstat(fd_copy, &stat) >= 0);
- assert_se(stat.st_size == 1024 * 1024 + strlen("abc"));
+ assert_se(stat.st_size == 3 * blksz);
close(fd);
close(fd_copy);