xfs: regression test for allocsp handing out stale disk contents
authorDarrick J. Wong <djwong@kernel.org>
Tue, 11 Jan 2022 21:50:35 +0000 (13:50 -0800)
committerEryu Guan <guaneryu@gmail.com>
Sun, 16 Jan 2022 05:47:29 +0000 (13:47 +0800)
Add a regression test to check that XFS_IOC_ALLOCSP isn't handing out
stale disk blocks for preallocation.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Eryu Guan <guaneryu@gmail.com>
Signed-off-by: Eryu Guan <guaneryu@gmail.com>
.gitignore
src/Makefile
src/allocstale.c [new file with mode: 0644]
tests/xfs/107 [new file with mode: 0755]
tests/xfs/107.out [new file with mode: 0644]

index 65b93307b7c7259379e9fb34836a23772286ccf4..ba0c572b248fcfecc89aeb0bd313054d22de3e9f 100644 (file)
@@ -56,6 +56,7 @@ tags
 # src/ binaries
 /src/af_unix
 /src/alloc
 # src/ binaries
 /src/af_unix
 /src/alloc
+/src/allocstale
 /src/append_reader
 /src/append_writer
 /src/attr_replace_test
 /src/append_reader
 /src/append_writer
 /src/attr_replace_test
index 1737ed0e131a3c2eec0d7da0061188ffe7f080c8..111ce1d90fe6f658431a55fde64e2287732eecc1 100644 (file)
@@ -18,7 +18,7 @@ TARGETS = dirstress fill fill2 getpagesize holes lstat64 \
        t_ext4_dax_journal_corruption t_ext4_dax_inline_corruption \
        t_ofd_locks t_mmap_collision mmap-write-concurrent \
        t_get_file_time t_create_short_dirs t_create_long_dirs t_enospc \
        t_ext4_dax_journal_corruption t_ext4_dax_inline_corruption \
        t_ofd_locks t_mmap_collision mmap-write-concurrent \
        t_get_file_time t_create_short_dirs t_create_long_dirs t_enospc \
-       t_mmap_writev_overlap checkpoint_journal mmap-rw-fault
+       t_mmap_writev_overlap checkpoint_journal mmap-rw-fault allocstale
 
 LINUX_TARGETS = xfsctl bstat t_mtab getdevicesize preallo_rw_pattern_reader \
        preallo_rw_pattern_writer ftrunc trunc fs_perms testx looptest \
 
 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/allocstale.c b/src/allocstale.c
new file mode 100644 (file)
index 0000000..6253fe4
--- /dev/null
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 Oracle.  All Rights Reserved.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ *
+ * Test program to try to trip over XFS_IOC_ALLOCSP mapping stale disk blocks
+ * into a file.
+ */
+#include <xfs/xfs.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+
+#ifndef XFS_IOC_ALLOCSP
+# define XFS_IOC_ALLOCSP       _IOW ('X', 10, struct xfs_flock64)
+#endif
+
+int
+main(
+       int             argc,
+       char            *argv[])
+{
+       struct stat     sb;
+       char            *buf, *zeroes;
+       unsigned long   i;
+       unsigned long   iterations;
+       int             fd, ret;
+
+       if (argc != 3) {
+               fprintf(stderr, "Usage: %s filename iterations\n", argv[0]);
+               return 1;
+       }
+
+       errno = 0;
+       iterations = strtoul(argv[2], NULL, 0);
+       if (errno) {
+               perror(argv[2]);
+               return 1;
+       }
+
+       fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0600);
+       if (fd < 0) {
+               perror(argv[1]);
+               return 1;
+       }
+
+       ret = fstat(fd, &sb);
+       if (ret) {
+               perror(argv[1]);
+               return 1;
+       }
+
+       buf = malloc(sb.st_blksize);
+       if (!buf) {
+               perror("pread buffer");
+               return 1;
+       }
+
+       zeroes = calloc(1, sb.st_blksize);
+       if (!zeroes) {
+               perror("zeroes buffer");
+               return 1;
+       }
+
+       for (i = 1; i <= iterations; i++) {
+               struct xfs_flock64      arg = { };
+               ssize_t                 read_bytes;
+               off_t                   offset = sb.st_blksize * i;
+
+               /* Ensure the last block of the file is a hole... */
+               ret = ftruncate(fd, offset - 1);
+               if (ret) {
+                       perror("truncate");
+                       return 1;
+               }
+
+               /*
+                * ...then use ALLOCSP to allocate the last block in the file.
+                * An unpatched kernel neglects to mark the new mapping
+                * unwritten or to zero the ondisk block, so...
+                */
+               arg.l_whence = SEEK_SET;
+               arg.l_start = offset;
+               ret = ioctl(fd, XFS_IOC_ALLOCSP, &arg);
+               if (ret < 0) {
+                       perror("ioctl");
+                       return 1;
+               }
+
+               /* ... we can read old disk contents here. */
+               read_bytes = pread(fd, buf, sb.st_blksize,
+                                               offset - sb.st_blksize);
+               if (read_bytes < 0) {
+                       perror(argv[1]);
+                       return 1;
+               }
+               if (read_bytes != sb.st_blksize) {
+                       fprintf(stderr, "%s: short read of %zd bytes\n",
+                                       argv[1], read_bytes);
+                       return 1;
+               }
+
+               if (memcmp(zeroes, buf, sb.st_blksize) != 0) {
+                       fprintf(stderr, "%s: found junk near offset %zd!\n",
+                                       argv[1], offset - sb.st_blksize);
+                       return 2;
+               }
+       }
+
+       return 0;
+}
diff --git a/tests/xfs/107 b/tests/xfs/107
new file mode 100755 (executable)
index 0000000..6034dbc
--- /dev/null
@@ -0,0 +1,57 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2022 Oracle.  All Rights Reserved.
+#
+# FS QA Test No. 107
+#
+# Regression test for commit:
+#
+# 983d8e60f508 ("xfs: map unwritten blocks in XFS_IOC_{ALLOC,FREE}SP just like fallocate")
+#
+. ./common/preamble
+_begin_fstest auto quick prealloc
+
+# Import common functions.
+. ./common/filter
+
+# real QA test starts here
+
+# Modify as appropriate.
+_supported_fs xfs
+_require_test
+_require_scratch
+_require_test_program allocstale
+
+size_mb=32
+# Write a known pattern to the disk so that we can detect stale disk blocks
+# being mapped into the file.  In the test author's experience, the bug will
+# reproduce within the first 500KB's worth of ALLOCSP calls, so running up
+# to 16MB should suffice.
+$XFS_IO_PROG -d -c "pwrite -S 0x58 -b 8m 0 ${size_mb}m" $SCRATCH_DEV > $seqres.full
+MKFS_OPTIONS="-K $MKFS_OPTIONS" _scratch_mkfs_sized $((size_mb * 1048576)) >> $seqres.full
+
+_scratch_mount
+
+# Force the file to be created on the data device, which we pre-initialized
+# with a known pattern.  The bug exists in the generic bmap code, so the choice
+# of backing device does not matter, and ignoring the rt device gets us out of
+# needing to detect things like rt extent size.
+_xfs_force_bdev data $SCRATCH_MNT
+testfile=$SCRATCH_MNT/a
+
+# Allow the test program to expand the file to consume half the free space.
+blksz=$(_get_file_block_size $SCRATCH_MNT)
+iterations=$(( (size_mb / 2) * 1048576 / blksz))
+echo "Setting up $iterations runs for block size $blksz" >> $seqres.full
+
+# Run reproducer program and dump file contents if we see stale data.  Full
+# details are in the source for the C program, but in a nutshell we run ALLOCSP
+# one block at a time to see if it'll give us blocks full of 'X'es.
+$here/src/allocstale $testfile $iterations
+res=$?
+test $res -eq 2 && od -tx1 -Ad -c $testfile
+
+# success, all done
+echo Silence is golden
+status=0
+exit
diff --git a/tests/xfs/107.out b/tests/xfs/107.out
new file mode 100644 (file)
index 0000000..862bdfb
--- /dev/null
@@ -0,0 +1,2 @@
+QA output created by 107
+Silence is golden