From: Daan De Meyer Date: Fri, 14 Jan 2022 16:49:06 +0000 (+0000) Subject: journal: Copy holes when archiving BTRFS journal files X-Git-Tag: v251-rc1~515^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F22125%2Fhead;p=thirdparty%2Fsystemd.git journal: Copy holes when archiving BTRFS journal files Previously, the holes we punched earlier would get removed when copying the file. Let's enable the new COPY_HOLES flag to make sure this doesn't happen. In my test, this drops a 800MB btrfs journal (without compression) to 720 MB. Fixes #22087 --- diff --git a/src/journal/journald-file.c b/src/journal/journald-file.c index 35ca305384e..0e698e329b0 100644 --- a/src/journal/journald-file.c +++ b/src/journal/journald-file.c @@ -179,7 +179,7 @@ static void journald_file_set_offline_internal(JournaldFile *f) { 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; diff --git a/src/shared/copy.c b/src/shared/copy.c index 6877b040a3a..457488efa2b 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -211,29 +211,28 @@ int copy_bytes_full( /* 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) diff --git a/src/test/test-copy.c b/src/test/test-copy.c index c7ed054207e..8572f25d879 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -323,4 +323,48 @@ TEST(copy_proc) { 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);