log_debug_errno(r, "Failed to re-enable copy-on-write for %s: %m, rewriting file", f->file->path);
- r = copy_file_atomic(f->file->path, f->file->path, f->file->mode, 0, FS_NOCOW_FL, COPY_REPLACE | COPY_FSYNC);
+ r = copy_file_atomic(f->file->path, f->file->path, f->file->mode, 0, FS_NOCOW_FL, COPY_REPLACE | COPY_FSYNC | COPY_HOLES);
if (r < 0) {
log_debug_errno(r, "Failed to rewrite %s: %m", f->file->path);
continue;
/* To see if we're in a hole, we search for the next data offset. */
e = lseek(fdf, c, SEEK_DATA);
- if (e < 0 && errno == ENXIO) {
+ if (e < 0 && errno == ENXIO)
/* If errno == ENXIO, that means we've reached the final hole of the file and
* that hole isn't followed by more data. */
e = lseek(fdf, 0, SEEK_END);
- if (e < 0)
- return -errno;
- } else if (e < 0)
+ if (e < 0)
return -errno;
- /* 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 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;
c = e; /* Set c to the start of the data segment. */
- /* After copying a potential hole, find the end of the data segment by looking for the next
- * hole. If we get ENXIO, we're at EOF. */
+ /* After copying a potential hole, find the end of the data segment by looking for
+ * the next hole. If we get ENXIO, we're at EOF. */
e = lseek(fdf, c, SEEK_HOLE);
- if (e < 0 && errno == ENXIO)
- break;
- else if (e < 0)
+ if (e < 0) {
+ if (errno == ENXIO)
+ break;
return -errno;
+ }
/* SEEK_HOLE modifies the file offset so we need to move back to the initial offset. */
if (lseek(fdf, c, SEEK_SET) < 0)
assert_se(!isempty(a));
}
+TEST_RET(copy_holes) {
+ char fn[] = "/var/tmp/test-copy-hole-fd-XXXXXX";
+ char fn_copy[] = "/var/tmp/test-copy-hole-fd-XXXXXX";
+ struct stat stat;
+ int r, fd, fd_copy;
+
+ fd = mkostemp_safe(fn);
+ assert_se(fd >= 0);
+
+ fd_copy = mkostemp_safe(fn_copy);
+ assert_se(fd >= 0);
+
+ r = RET_NERRNO(fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, 1));
+ if (ERRNO_IS_NOT_SUPPORTED(r))
+ 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(lseek(fd, 1024 * 1024, SEEK_CUR) >= 0);
+ assert_se(write(fd, "abc", strlen("abc")) >= 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);
+
+ /* 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"));
+
+ close(fd);
+ close(fd_copy);
+
+ unlink(fn);
+ unlink(fn_copy);
+
+ return 0;
+}
+
DEFINE_TEST_MAIN(LOG_DEBUG);