diff mbox series

generic: check reflink multiple mmap write

Message ID 20191010041440.GH13097@magnolia (mailing list archive)
State New, archived
Headers show
Series generic: check reflink multiple mmap write | expand

Commit Message

Darrick J. Wong Oct. 10, 2019, 4:14 a.m. UTC
From: Darrick J. Wong <darrick.wong@oracle.com>

Add a test to make sure that we can handle multiple memory mappings to a
physical storage extent shared by multiple files, and that we can handle
the copy on write operation without error.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
---
 src/Makefile                |    2 -
 src/mmap-write-concurrent.c |  157 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/945           |   89 ++++++++++++++++++++++++
 tests/generic/945.out       |   25 +++++++
 tests/generic/group         |    1 
 5 files changed, 273 insertions(+), 1 deletion(-)
 create mode 100644 src/mmap-write-concurrent.c
 create mode 100755 tests/generic/945
 create mode 100644 tests/generic/945.out

Comments

Christoph Hellwig Oct. 10, 2019, 6:49 a.m. UTC | #1
On Wed, Oct 09, 2019 at 09:14:40PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <darrick.wong@oracle.com>
> 
> Add a test to make sure that we can handle multiple memory mappings to a
> physical storage extent shared by multiple files, and that we can handle
> the copy on write operation without error.
> 
> Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>

Thanks, this looks good:

Reviewed-by: Christoph Hellwig <hch@lst.de>

To further try fund DAX corner cases we should also try different
offsets for the different files, or different offsets in the same
file referring to the same blocks.  I can take a stab at that once
I find some time.
diff mbox series

Patch

diff --git a/src/Makefile b/src/Makefile
index ef7cfa63..5bc33e77 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -16,7 +16,7 @@  TARGETS = dirstress fill fill2 getpagesize holes lstat64 \
 	holetest t_truncate_self t_mmap_dio af_unix t_mmap_stale_pmd \
 	t_mmap_cow_race t_mmap_fallocate fsync-err t_mmap_write_ro \
 	t_ext4_dax_journal_corruption t_ext4_dax_inline_corruption \
-	t_ofd_locks t_locks_execve t_mmap_collision
+	t_ofd_locks t_locks_execve t_mmap_collision mmap-write-concurrent
 
 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/mmap-write-concurrent.c b/src/mmap-write-concurrent.c
new file mode 100644
index 00000000..0eccea0a
--- /dev/null
+++ b/src/mmap-write-concurrent.c
@@ -0,0 +1,157 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-newer
+/*
+ * Copyright (c) 2019 Oracle.
+ * All Rights Reserved.
+ *
+ * Create writable mappings to multiple files and write them all to test
+ * concurrent mmap writes to the same shared blocks.
+ */
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+int main(int argc, char *argv[])
+{
+	struct stat *statbuf;
+	off_t offset;
+	size_t length;
+	char *endptr;
+	char **mappings;
+	int *fds;
+	char *buf;
+	int i;
+	int ret;
+
+	if (argc < 4) {
+		printf("Usage: %s offset len file [files...]\n", argv[0]);
+		return 1;
+	}
+
+	/* Parse mwrite offset. */
+	errno = 0;
+	offset = strtoul(argv[1], &endptr, 0);
+	if (errno) {
+		perror(argv[1]);
+		return 1;
+	}
+	if (*endptr != '\0') {
+		fprintf(stderr, "%s: not a proper file offset?\n", argv[1]);
+		return 1;
+	}
+
+	/* Parse mwrite length. */
+	errno = 0;
+	length = strtoul(argv[2], &endptr, 0);
+	if (errno) {
+		perror(argv[1]);
+		return 1;
+	}
+	if (*endptr != '\0') {
+		fprintf(stderr, "%s: not a proper file length?\n", argv[2]);
+		return 1;
+	}
+
+	mappings = calloc(argc - 3, sizeof(char *));
+	if (!mappings) {
+		perror("calloc maps");
+		return 1;
+	}
+
+	fds = calloc(argc - 3, sizeof(int));
+	if (!fds) {
+		perror("calloc fds");
+		return 1;
+	}
+
+	buf = malloc(length);
+	if (!buf) {
+		perror("malloc buf");
+		return 1;
+	}
+
+	statbuf = calloc(argc - 3, sizeof(struct stat));
+	if (!statbuf) {
+		perror("calloc statbuf");
+		return 1;
+	}
+
+	for (i = 0; i < argc - 3; i++) {
+		char *fname = argv[i + 3];
+
+		/* Open file, create mapping for the range we want. */
+		fds[i] = open(fname, O_RDWR);
+		if (fds[i] < 0) {
+			perror(fname);
+			return 1;
+		}
+
+		ret = fstat(fds[i], &statbuf[i]);
+		if (ret) {
+			perror(fname);
+			return 1;
+		}
+
+		if (offset + length > statbuf[i].st_size) {
+			fprintf(stderr, "%s: file must be %llu bytes\n",
+					fname,
+					(unsigned long long)offset + length);
+			return 1;
+		}
+
+		mappings[i] = mmap(NULL, statbuf[i].st_size,
+				PROT_READ | PROT_WRITE, MAP_SHARED,
+				fds[i], 0);
+		if (mappings[i] == MAP_FAILED) {
+			perror(fname);
+			return 1;
+		}
+
+		/*
+		 * Make sure the mapping for region we're going to write is
+		 * already populated in the page cache.
+		 */
+		memcpy(buf, mappings[i] + offset, length);
+	}
+
+	/* Dirty the same region in each file to test COW. */
+	for (i = 0; i < argc - 3; i++) {
+		memset(buf, 0x58 + i, length);
+		memcpy(mappings[i] + offset, buf, length);
+	}
+	for (i = 0; i < argc - 3; i++) {
+		ret = msync(mappings[i], offset + length, MS_SYNC);
+		if (ret) {
+			perror("msync");
+			return 1;
+		}
+	}
+
+	/* Close everything. */
+	for (i = 0; i < argc - 3; i++) {
+		ret = munmap(mappings[i], statbuf[i].st_size);
+		if (ret) {
+			perror("munmap");
+			return 1;
+		}
+
+		ret = close(fds[i]);
+		if (ret) {
+			perror("close");
+			return 1;
+		}
+	}
+
+	/* Free everything. */
+	free(statbuf);
+	free(buf);
+	free(fds);
+	free(mappings);
+
+	return 0;
+}
diff --git a/tests/generic/945 b/tests/generic/945
new file mode 100755
index 00000000..53e26966
--- /dev/null
+++ b/tests/generic/945
@@ -0,0 +1,89 @@ 
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-newer
+# Copyright (c) 2019, Oracle and/or its affiliates.  All Rights Reserved.
+#
+# FS QA Test No. 945
+#
+# Make sure that we can handle multiple mmap writers to the same file.
+
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1    # failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+	cd /
+	rm -rf $tmp.* $testdir
+}
+
+# get standard environment, filters and checks
+. ./common/rc
+. ./common/filter
+. ./common/reflink
+
+# real QA test starts here
+_supported_os Linux
+_supported_fs generic
+_require_command "$FILEFRAG_PROG" filefrag
+_require_test_reflink
+_require_cp_reflink
+
+rm -f $seqres.full
+
+compare() {
+	md5sum $testdir/file1 | _filter_test_dir
+	md5sum $testdir/file2 | _filter_test_dir
+	md5sum $testdir/file3 | _filter_test_dir
+	md5sum $testdir/file4 | _filter_test_dir
+
+	cmp -s $testdir/file1 $testdir/file2 || echo "Files 1-2 do not match"
+	cmp -s $testdir/file1 $testdir/file3 || echo "Files 1-3 do not match"
+	cmp -s $testdir/file1 $testdir/file4 || echo "Files 1-4 do not match"
+}
+
+testdir=$TEST_DIR/test-$seq
+rm -rf $testdir
+mkdir $testdir
+
+echo "Create the original files"
+filesz=$((65536 * 4))
+_pwrite_byte 0x61 0 $filesz $testdir/file1 >> $seqres.full
+_cp_reflink $testdir/file1 $testdir/file2 >> $seqres.full
+_cp_reflink $testdir/file1 $testdir/file3 >> $seqres.full
+_cp_reflink $testdir/file1 $testdir/file4 >> $seqres.full
+_test_cycle_mount
+
+echo "Compare files before cow"
+compare
+
+echo "mwrite all copies"
+off=$(( (filesz / 2) - 168 ))
+len=337
+./src/mmap-write-concurrent $off $len $testdir/file1 $testdir/file2 \
+		$testdir/file3 $testdir/file4
+
+echo "Compare files before remount"
+compare
+_test_cycle_mount
+
+echo "Compare files after remount"
+compare
+
+echo "Check for non-shared extents"
+$FILEFRAG_PROG -v $testdir/file1 $testdir/file2 $testdir/file3 \
+		$testdir/file4 | grep '^[[:space:]]*[0-9]*:' > $testdir/fiemap
+cat $testdir/fiemap >> $seqres.full
+grep -q 'shared' $testdir/fiemap || \
+		echo "Expected to find shared extents"
+
+grep -q -v 'shared' $testdir/fiemap || \
+		echo "Expected to find non-shared extents"
+
+# success, all done
+status=0
+exit
diff --git a/tests/generic/945.out b/tests/generic/945.out
new file mode 100644
index 00000000..ba79ddfc
--- /dev/null
+++ b/tests/generic/945.out
@@ -0,0 +1,25 @@ 
+QA output created by 945
+Create the original files
+Compare files before cow
+c946b71bb69c07daf25470742c967e7c  TEST_DIR/test-945/file1
+c946b71bb69c07daf25470742c967e7c  TEST_DIR/test-945/file2
+c946b71bb69c07daf25470742c967e7c  TEST_DIR/test-945/file3
+c946b71bb69c07daf25470742c967e7c  TEST_DIR/test-945/file4
+mwrite all copies
+Compare files before remount
+83f84225313027ef51fea4f86239d432  TEST_DIR/test-945/file1
+d9d86b794e7d626ccee90392518c9048  TEST_DIR/test-945/file2
+c0dae8b7541aad8ef4ed3b9ffd20e9a3  TEST_DIR/test-945/file3
+094457223ea2751265e617631f5f4aa9  TEST_DIR/test-945/file4
+Files 1-2 do not match
+Files 1-3 do not match
+Files 1-4 do not match
+Compare files after remount
+83f84225313027ef51fea4f86239d432  TEST_DIR/test-945/file1
+d9d86b794e7d626ccee90392518c9048  TEST_DIR/test-945/file2
+c0dae8b7541aad8ef4ed3b9ffd20e9a3  TEST_DIR/test-945/file3
+094457223ea2751265e617631f5f4aa9  TEST_DIR/test-945/file4
+Files 1-2 do not match
+Files 1-3 do not match
+Files 1-4 do not match
+Check for non-shared extents
diff --git a/tests/generic/group b/tests/generic/group
index 4584667f..f77c5b21 100644
--- a/tests/generic/group
+++ b/tests/generic/group
@@ -576,3 +576,4 @@ 
 715 dangerous_norepair
 716 dangerous_norepair
 720 dangerous_norepair
+945 auto quick rw clone