]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
journal: Copy holes when archiving BTRFS journal files 22125/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 14 Jan 2022 16:49:06 +0000 (16:49 +0000)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 17 Jan 2022 16:10:18 +0000 (16:10 +0000)
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

src/journal/journald-file.c
src/shared/copy.c
src/test/test-copy.c

index 35ca305384ecbf99f99ef95f2ec36b7f76cbf36e..0e698e329b05db24065a44a5861ac6295722bde1 100644 (file)
@@ -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;
index 6877b040a3a4245dcf7ef6b2ec318dbd38c3bdb3..457488efa2b6675b603e74539ad57ad60edd2ceb 100644 (file)
@@ -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)
index c7ed054207efc0efe043e7e08cd79dd9685fa343..8572f25d879c39b85d6c6666d13c8bdb3066de61 100644 (file)
@@ -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);