]> git.apps.os.sepia.ceph.com Git - xfstests-dev.git/commitdiff
generic: test page fault during direct IO write with O_APPEND
authorFilipe Manana <fdmanana@suse.com>
Sat, 27 Jul 2024 11:28:16 +0000 (12:28 +0100)
committerZorro Lang <zlang@kernel.org>
Tue, 6 Aug 2024 12:41:07 +0000 (20:41 +0800)
Test that doing a direct IO append write to a file when the input buffer
was not yet faulted in, does not result in an incorrect file size.

This exercises a bug on btrfs reported by users and which is fixed by
the following kernel patch:

   "btrfs: fix corruption after buffer fault in during direct IO append write"

Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: Zorro Lang <zlang@redhat.com>
Signed-off-by: Zorro Lang <zlang@kernel.org>
.gitignore
src/Makefile
src/dio-append-buf-fault.c [new file with mode: 0644]
tests/generic/362 [new file with mode: 0755]
tests/generic/362.out [new file with mode: 0644]

index b5f1516260c838cb589ef0d8b7a72821dd713046..97c7e00143831e57982034b66f9c58158719b0ae 100644 (file)
@@ -72,6 +72,7 @@ tags
 /src/deduperace
 /src/detached_mounts_propagation
 /src/devzero
+/src/dio-append-buf-fault
 /src/dio-buf-fault
 /src/dio-interleaved
 /src/dio-invalidate-cache
index 9979613711c91257b34b655e2db69cec79dc4621..559209be9f4bc0fbf8639c3e68745faba4df2477 100644 (file)
@@ -20,7 +20,7 @@ TARGETS = dirstress fill fill2 getpagesize holes lstat64 \
        t_get_file_time t_create_short_dirs t_create_long_dirs t_enospc \
        t_mmap_writev_overlap checkpoint_journal mmap-rw-fault allocstale \
        t_mmap_cow_memory_failure fake-dump-rootino dio-buf-fault rewinddir-test \
-       readdir-while-renames
+       readdir-while-renames dio-append-buf-fault
 
 LINUX_TARGETS = xfsctl bstat t_mtab getdevicesize preallo_rw_pattern_reader \
        preallo_rw_pattern_writer ftrunc trunc fs_perms testx looptest \
diff --git a/src/dio-append-buf-fault.c b/src/dio-append-buf-fault.c
new file mode 100644 (file)
index 0000000..72c2326
--- /dev/null
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2024 SUSE Linux Products GmbH.  All Rights Reserved.
+ */
+
+/*
+ * Test a direct IO write in append mode with a buffer that was not faulted in
+ * (or just partially) before the write.
+ */
+
+/* Get the O_DIRECT definition. */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+static ssize_t do_write(int fd, const void *buf, size_t count)
+{
+        while (count > 0) {
+               ssize_t ret;
+
+               ret = write(fd, buf, count);
+               if (ret < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       return ret;
+               }
+               count -= ret;
+               buf += ret;
+       }
+       return 0;
+}
+
+int main(int argc, char *argv[])
+{
+       struct stat stbuf;
+       int fd;
+       long pagesize;
+       void *buf;
+       ssize_t ret;
+
+       if (argc != 2) {
+               fprintf(stderr, "Use: %s <file path>\n", argv[0]);
+               return 1;
+       }
+
+       /*
+        * First try an append write against an empty file of a buffer with a
+        * size matching the page size. The buffer is not faulted in before
+        * attempting the write.
+        */
+
+       fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC | O_DIRECT | O_APPEND, 0666);
+       if (fd == -1) {
+               perror("Failed to open/create file");
+               return 2;
+       }
+
+       pagesize = sysconf(_SC_PAGE_SIZE);
+       if (pagesize == -1) {
+               perror("Failed to get page size");
+               return 3;
+       }
+
+       buf = mmap(NULL, pagesize, PROT_READ | PROT_WRITE,
+                  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+       if (buf == MAP_FAILED) {
+               perror("Failed to allocate first buffer");
+               return 4;
+       }
+
+       ret = do_write(fd, buf, pagesize);
+       if (ret < 0) {
+               perror("First write failed");
+               return 5;
+       }
+
+       ret = fstat(fd, &stbuf);
+       if (ret < 0) {
+               perror("First stat failed");
+               return 6;
+       }
+
+       /* Don't exit on failure so that we run the second test below too. */
+       if (stbuf.st_size != pagesize)
+               fprintf(stderr,
+                       "Wrong file size after first write, got %jd expected %ld\n",
+                       (intmax_t)stbuf.st_size, pagesize);
+
+       munmap(buf, pagesize);
+       close(fd);
+
+       /*
+        * Now try an append write against an empty file of a buffer with a
+        * size matching twice the page size. Only the first page of the buffer
+        * is faulted in before attempting the write, so that the second page
+        * should be faulted in during the write.
+        */
+       fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC | O_DIRECT | O_APPEND, 0666);
+       if (fd == -1) {
+               perror("Failed to open/create file");
+               return 7;
+       }
+
+       buf = mmap(NULL, pagesize * 2, PROT_READ | PROT_WRITE,
+                  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+       if (buf == MAP_FAILED) {
+               perror("Failed to allocate second buffer");
+               return 8;
+       }
+
+       /* Fault in first page of the buffer before the write. */
+       memset(buf, 0, 1);
+
+       ret = do_write(fd, buf, pagesize * 2);
+       if (ret < 0) {
+               perror("Second write failed");
+               return 9;
+       }
+
+       ret = fstat(fd, &stbuf);
+       if (ret < 0) {
+               perror("Second stat failed");
+               return 10;
+       }
+
+       if (stbuf.st_size != pagesize * 2)
+               fprintf(stderr,
+                       "Wrong file size after second write, got %jd expected %ld\n",
+                       (intmax_t)stbuf.st_size, pagesize * 2);
+
+       munmap(buf, pagesize * 2);
+       close(fd);
+
+       return 0;
+}
diff --git a/tests/generic/362 b/tests/generic/362
new file mode 100755 (executable)
index 0000000..f5b4ed0
--- /dev/null
@@ -0,0 +1,28 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2024 SUSE Linux Products GmbH. All Rights Reserved.
+#
+# FS QA Test 362
+#
+# Test that doing a direct IO append write to a file when the input buffer was
+# not yet faulted in, does not result in an incorrect file size.
+#
+. ./common/preamble
+_begin_fstest auto quick
+
+_require_test
+_require_odirect
+_require_test_program dio-append-buf-fault
+
+[ $FSTYP == "btrfs" ] && \
+       _fixed_by_kernel_commit 939b656bc8ab \
+       "btrfs: fix corruption after buffer fault in during direct IO append write"
+
+# On error the test program writes messages to stderr, causing a golden output
+# mismatch and making the test fail.
+$here/src/dio-append-buf-fault $TEST_DIR/dio-append-buf-fault
+
+# success, all done
+echo "Silence is golden"
+status=0
+exit
diff --git a/tests/generic/362.out b/tests/generic/362.out
new file mode 100644 (file)
index 0000000..0ff4090
--- /dev/null
@@ -0,0 +1,2 @@
+QA output created by 362
+Silence is golden