git: 81b7ebe32edd - main - stress2: Added a few regression tests

From: Peter Holm <pho_at_FreeBSD.org>
Date: Tue, 27 Feb 2024 10:05:53 UTC
The branch main has been updated by pho:

URL: https://cgit.FreeBSD.org/src/commit/?id=81b7ebe32edd4be705fe2d50c8d061191ba41573

commit 81b7ebe32edd4be705fe2d50c8d061191ba41573
Author:     Peter Holm <pho@FreeBSD.org>
AuthorDate: 2024-02-27 10:05:26 +0000
Commit:     Peter Holm <pho@FreeBSD.org>
CommitDate: 2024-02-27 10:05:26 +0000

    stress2: Added a few regression tests
---
 tools/test/stress2/misc/mapwrite.sh | 189 ++++++++++++++++++++++++++++++++++++
 tools/test/stress2/misc/mmap41.sh   | 160 ++++++++++++++++++++++++++++++
 tools/test/stress2/misc/mmap42.sh   | 101 +++++++++++++++++++
 tools/test/stress2/misc/mmap43.sh   | 182 ++++++++++++++++++++++++++++++++++
 4 files changed, 632 insertions(+)

diff --git a/tools/test/stress2/misc/mapwrite.sh b/tools/test/stress2/misc/mapwrite.sh
new file mode 100755
index 000000000000..1fef81942b64
--- /dev/null
+++ b/tools/test/stress2/misc/mapwrite.sh
@@ -0,0 +1,189 @@
+#!/bin/sh
+
+# File corruption scenario
+
+# Test program by Rob Norris <rob norris klarasystems com>
+# Test program obtained from: https://gist.github.com/robn/9804c60cd0275086d26893d73e7af35c
+# https://github.com/openzfs/zfs/issues/15654
+
+[ `id -u ` -ne 0 ] && echo "Must be root!" && exit 1
+
+. ../default.cfg
+
+set -u
+prog=$(basename "$0" .sh)
+cat > /tmp/$prog.c <<EOF
+/*
+ * Some kind of clone-related crasher. Not sure if legit or just outdated
+ * assertion.
+ *
+ * Creates clone, maps it, writes from map back into itself.
+ *
+ * Compile a recent (2.2+) ZFS with --enable-debug.
+ *
+ * cc -o mapwrite mapwrite.c
+ *
+ * echo 1 > /sys/modules/zfs/parameters/zfs_bclone_enabled
+ * zpool create tank ...
+ * cd /tank
+ * mapwrite
+ *
+ * [    7.666305] VERIFY(arc_released(db->db_buf)) failed
+ * [    7.666443] PANIC at dbuf.c:2150:dbuf_redirty()
+ * [    7.666489] Showing stack for process 608
+ * [    7.666534] CPU: 1 PID: 608 Comm: mapwrite Tainted: P           O      5.10.170 #3
+ * [    7.666610] Call Trace:
+ * [    7.666646]  dump_stack+0x57/0x6e
+ * [    7.666717]  spl_panic+0xd3/0xfb [spl]
+ * [    7.667113]  ? zfs_btree_find+0x16a/0x300 [zfs]
+ * [    7.667278]  ? range_tree_find_impl+0x55/0xa0 [zfs]
+ * [    7.667333]  ? _cond_resched+0x1a/0x50
+ * [    7.667371]  ? __kmalloc_node+0x14a/0x2b0
+ * [    7.667415]  ? spl_kmem_alloc_impl+0xb0/0xd0 [spl]
+ * [    7.667555]  ? __list_add+0x12/0x30 [zfs]
+ * [    7.667681]  spl_assert+0x17/0x20 [zfs]
+ * [    7.667807]  dbuf_redirty+0xad/0xb0 [zfs]
+ * [    7.667963]  dbuf_dirty+0xe76/0x1310 [zfs]
+ * [    7.668011]  ? mutex_lock+0xe/0x30
+ * [    7.668133]  ? dbuf_noread+0x112/0x240 [zfs]
+ * [    7.668271]  dmu_write_uio_dnode+0x101/0x1b0 [zfs]
+ * [    7.668411]  dmu_write_uio_dbuf+0x4a/0x70 [zfs]
+ * [    7.668555]  zfs_write+0x500/0xc80 [zfs]
+ * [    7.668610]  ? page_add_file_rmap+0xe/0xb0
+ * [    7.668740]  zpl_iter_write+0xe4/0x130 [zfs]
+ * [    7.668803]  new_sync_write+0x119/0x1b0
+ * [    7.668843]  vfs_write+0x1ce/0x260
+ * [    7.668880]  __x64_sys_pwrite64+0x91/0xc0
+ * [    7.668918]  do_syscall_64+0x30/0x40
+ * [    7.668957]  entry_SYSCALL_64_after_hwframe+0x61/0xc6
+ */
+
+#define	_GNU_SOURCE
+
+#include <fcntl.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#define	DATASIZE	(1024*1024)
+char data[DATASIZE];
+
+#define NDATA		(512)
+
+#define FILE_NAME	"file"
+#define CLONE_NAME	"clone"
+
+static int
+_create_file(void)
+{
+	memset(data, 0x5a, DATASIZE);
+
+	int fd;
+	if ((fd = open(FILE_NAME, O_RDWR | O_CREAT | O_APPEND,
+	    S_IRUSR | S_IWUSR)) < 0) {
+		perror("open '" FILE_NAME "'");
+		abort();
+	}
+
+	for (int i = 0; i < NDATA; i++) {
+		int nwr = write(fd, data, DATASIZE);
+		if (nwr < 0) {
+			perror("write");
+			abort();
+		}
+		if (nwr < DATASIZE) {
+			fprintf(stderr, "short write\n");
+			abort();
+		}
+	}
+
+	if (lseek(fd, 0, SEEK_SET) < 0) {
+		perror("lseek");
+		abort();
+	}
+
+	sync();
+
+	return (fd);
+}
+
+static int
+_clone_file(int sfd)
+{
+	int dfd;
+	if ((dfd = open(CLONE_NAME, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) < 0) {
+		perror("open '" CLONE_NAME "'");
+		abort();
+	}
+
+	if (copy_file_range(sfd, 0, dfd, 0, DATASIZE * NDATA, 0) < 0) {
+		perror("copy_file_range");
+		abort();
+	}
+
+	return (dfd);
+}
+
+static void *
+_map_file(int fd)
+{
+	void *p = mmap(NULL, DATASIZE*NDATA, PROT_READ, MAP_SHARED, fd, 0);
+	if (p == MAP_FAILED) {
+		perror("mmap");
+		abort();
+	}
+
+	return (p);
+}
+
+static void
+_map_write(void *p, int fd)
+{
+	if (pwrite(fd, p, DATASIZE, 0) < 0) {
+		perror("pwrite");
+		abort();
+	}
+}
+
+int
+main(void)
+{
+	int sfd = _create_file();
+	int dfd = _clone_file(sfd);
+	void *p = _map_file(dfd);
+	_map_write(p, dfd);
+	return (0);
+}
+EOF
+mycc -o /tmp/$prog -Wall -Wextra -O2 /tmp/$prog.c || exit 1
+
+mount | grep -q "on $mntpoint " && umount -f $mntpoint
+mdconfig -l | grep -q md$mdstart && mdconfig -d -u $mdstart
+mdconfig -s 5g -u $mdstart
+
+newfs -n $newfs_flags /dev/md$mdstart > /dev/null
+mount /dev/md$mdstart $mntpoint
+
+mycc -o /tmp/swap -Wall -Wextra -O0 ../tools/swap.c || exit 1
+timeout -k 90 60 /tmp/swap -d 100 &
+for i in `jot 10`; do
+	capacity=`swapinfo | tail -1 | sed 's/.* //; s/%//'`
+	[ $capacity -gt 1 ] && break
+	sleep 2	# Wait for swapping
+done
+
+cd $mntpoint
+/tmp/$prog; s=$?
+pkill swap
+wait
+cmp $mntpoint/file $mntpoint/clone || { echo Fail; s=1; }
+cd -
+
+umount $mntpoint
+mdconfig -d -u $mdstart
+rm /tmp/$prog /tmp/$prog.c
+exit $s
diff --git a/tools/test/stress2/misc/mmap41.sh b/tools/test/stress2/misc/mmap41.sh
new file mode 100755
index 000000000000..5051681aaf31
--- /dev/null
+++ b/tools/test/stress2/misc/mmap41.sh
@@ -0,0 +1,160 @@
+#!/bin/sh
+
+#
+# Copyright (c) 2024 Peter Holm <pho@FreeBSD.org>
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+# Based on code from https://syzkaller.appspot.com/text?tag=ReproC&x=15d9baada80000
+# No problems seen
+
+. ../default.cfg
+
+prog=$(basename "$0" .sh)
+odir=`pwd`
+cd /tmp
+sed '1,/^EOF/d' < $odir/$0 > $prog.c
+mycc -o $prog -Wall -Wextra -O0 $prog.c -lpthread || exit 1
+rm -f $prog.c
+
+set -e
+mount | grep "on $mntpoint " | grep -q /dev/md && umount -f $mntpoint
+[ -c /dev/md$mdstart ] &&  mdconfig -d -u $mdstart
+mdconfig -a -t swap -s 2g -u $mdstart
+newfs $newfs_flags md$mdstart > /dev/null
+mount /dev/md$mdstart $mntpoint
+set +e
+
+$odir/../testcases/swap/swap -t 2m -i 10 > /dev/null &
+cd $mntpoint
+/tmp/$prog
+cd $odir
+while pkill swap; do :; done
+wait
+
+for i in `jot 6`; do
+	mount | grep -q "on $mntpoint " || break
+	umount $mntpoint && break || sleep 10
+	[ $i -eq 6 ] &&
+	    { echo FATAL; fstat -mf $mntpoint; exit 1; }
+done
+mdconfig -d -u $mdstart
+rm -f /tmp/$prog
+exit 0
+
+EOF
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define DEBUG 0	/* 1 to enable */
+#define THREADS 2
+
+static volatile int go;
+static int fd;
+static char *p, path[128];
+
+#define ADDR (void *) 0x20000000ul
+#define LEN  0x1000000ul
+
+void *
+thr(void *arg)
+{
+	struct iovec iov;
+	long n, w;
+	char *p1;
+
+	if (*(int *)arg == 0) {
+		while (go == 0)
+			usleep(100);
+		while (go == 1) {
+			if ((p1 = mmap(ADDR, LEN, PROT_WRITE, MAP_ANON|MAP_FIXED|MAP_PRIVATE, -1, 0)) == MAP_FAILED)
+				err(1, "mmap() in %s", __func__);
+			usleep(arc4random() % 50);
+			if ((p1 = mmap(ADDR, LEN, PROT_READ|PROT_WRITE, MAP_ANON|MAP_FIXED|MAP_PRIVATE, -1, 0)) == MAP_FAILED)
+				err(1, "mmap() in %s", __func__);
+			usleep(arc4random() % 10000);
+		}
+	} else {
+		while (go == 0)
+			usleep(100);
+		n = w = 0;
+		while (go == 1) {
+			iov.iov_base = p;
+			iov.iov_len = 0x100000;
+			if (pwritev(fd, &iov, 1, 0) != -1)
+				w++;
+			n++;
+		}
+		if (DEBUG == 1)
+			fprintf(stderr, "%ld out of %ld  writes (%ld%%)\n", w, n, w * 100 / n);
+	}
+
+
+	return (0);
+}
+
+void
+test(void)
+{
+	pthread_t threads[THREADS];
+	int nr[THREADS];
+	int i, r;
+
+	sprintf(path, "mmap.%06d", getpid());
+	if ((fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0622)) == -1)
+		err(1,"open()");
+
+
+	if ((p = mmap(ADDR, LEN, PROT_READ|PROT_WRITE, MAP_ANON|MAP_FIXED|MAP_PRIVATE, -1, 0)) == MAP_FAILED)
+		err(1, "mmap() in %s", __func__);
+
+	go = 0;
+	for (i = 0; i < THREADS; i++) {
+		nr[i] = i;
+		if ((r = pthread_create(&threads[i], NULL, thr,
+		    (void *)&nr[i])) != 0)
+			errc(1, r, "pthread_create()");
+	}
+
+	go = 1;
+	sleep(60);
+	go = 2;
+
+	for (i = 0; i < THREADS; i++) {
+		if ((r = pthread_join(threads[i], NULL)) != 0)
+			errc(1, r, "pthread_join(%d)", i);
+	}
+	close(fd);
+	if (DEBUG == 0) {
+		if (unlink(path) == -1)
+			err(1, "unlink(%s)", path);
+	}
+
+	_exit(0);
+}
+
+int
+main(void)
+{
+	pid_t pid;
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		if ((pid = fork()) == 0)
+			test();
+		if (waitpid(pid, NULL, 0) != pid)
+			err(1, "waitpid()");
+	}
+}
diff --git a/tools/test/stress2/misc/mmap42.sh b/tools/test/stress2/misc/mmap42.sh
new file mode 100755
index 000000000000..11235e581e73
--- /dev/null
+++ b/tools/test/stress2/misc/mmap42.sh
@@ -0,0 +1,101 @@
+#!/bin/sh
+
+# Test scenario by: kib@
+# Test program obtained from Kyle Evans <kevans@FreeBSD.org>
+
+# Demonstrate UFS SU file corruption:
+# ffs: on write into a buffer without content
+
+[ `id -u ` -ne 0 ] && echo "Must be root!" && exit 1
+
+. ../default.cfg
+
+set -u
+prog=$(basename "$0" .sh)
+s=0
+cat > /tmp/$prog.c <<EOF
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define	FILE	"file"
+
+int
+main(void)
+{
+	struct stat sb;
+	ssize_t wsz;
+	size_t bufsz;
+	void *buf, *obuf;
+	int mfd, fd;
+	int done = 0;
+
+	mfd = open(FILE, O_RDONLY);
+	assert(mfd >= 0);
+
+	assert(fstat(mfd, &sb) == 0);
+	bufsz = sb.st_size;
+	buf = obuf = mmap(NULL, bufsz, PROT_READ, MAP_SHARED, mfd, 0);
+	assert(buf != MAP_FAILED);
+
+	/* O_RDWR */
+	fd = open(FILE, O_RDWR);
+	if (fd < 0)
+		err(1, "open");
+	assert(fd >= 0);
+
+again:
+	while (bufsz > 0) {
+		wsz = write(fd, buf, bufsz);
+		if (wsz < 0)
+			err(1, "write");
+		else if (wsz == 0)
+			fprintf(stderr, "Huh?\n");
+		bufsz -= wsz;
+		buf += wsz;
+	}
+
+	bufsz = sb.st_size;
+	buf = obuf;
+
+	if (++done < 2)
+		goto again;
+
+	close(fd);
+	munmap(obuf, sb.st_size);
+	close(mfd);
+	return (0);
+}
+EOF
+mycc -o /tmp/$prog -Wall -Wextra -O0 /tmp/$prog.c || exit 1
+
+mount | grep -q "on $mntpoint " && umount -f $mntpoint
+mdconfig -l | grep -q md$mdstart && mdconfig -d -u $mdstart
+mdconfig -s 32m -u $mdstart
+
+pagesize=$(sysctl -n hw.pagesize)
+newfs -Un -b $pagesize  /dev/md$mdstart > /dev/null
+mount /dev/md$mdstart $mntpoint
+dd if=/dev/random of=/mnt/file.orig bs=${pagesize} count=1 status=none
+cp $mntpoint/file.orig $mntpoint/file
+cat $mntpoint/file $mntpoint/file > $mntpoint/file.post
+umount $mntpoint
+
+mount /dev/md$mdstart $mntpoint
+(cd $mntpoint; /tmp/$prog)
+
+if ! cmp $mntpoint/file $mntpoint/file.post; then
+	echo "Files differ"
+	ls -l $mntpoint/file $mntpoint/file.post
+	s=1
+fi
+
+umount $mntpoint
+mdconfig -d -u $mdstart
+rm /tmp/$prog /tmp/$prog.c
+exit $s
diff --git a/tools/test/stress2/misc/mmap43.sh b/tools/test/stress2/misc/mmap43.sh
new file mode 100755
index 000000000000..98f1de174d54
--- /dev/null
+++ b/tools/test/stress2/misc/mmap43.sh
@@ -0,0 +1,182 @@
+#!/bin/sh
+
+# Test program obtained from Kyle Evans <kevans@FreeBSD.org>
+
+# Demonstrate UFS SU file corruption
+
+[ `id -u ` -ne 0 ] && echo "Must be root!" && exit 1
+
+. ../default.cfg
+
+set -u
+prog=$(basename "$0" .sh)
+log=/tmp/$prog.log
+rm -f $log
+cat > /tmp/$prog.c <<EOF
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define	FILE	"file"
+
+int
+main(void)
+{
+	struct stat sb;
+	ssize_t wsz;
+	size_t bufsz;
+	void *buf, *obuf;
+	int mfd, fd;
+	int done = 0;
+
+	mfd = open(FILE, O_RDONLY);
+	assert(mfd >= 0);
+
+	assert(fstat(mfd, &sb) == 0);
+	bufsz = sb.st_size;
+	buf = obuf = mmap(NULL, bufsz, PROT_READ, MAP_SHARED, mfd, 0);
+	assert(buf != MAP_FAILED);
+
+	/* O_RDWR */
+	fd = open(FILE, O_RDWR);
+	if (fd < 0)
+		err(1, "open");
+	assert(fd >= 0);
+
+again:
+	while (bufsz > 0) {
+		wsz = write(fd, buf, bufsz);
+		if (wsz < 0)
+			err(1, "write");
+		else if (wsz == 0)
+			fprintf(stderr, "Huh?\n");
+		bufsz -= wsz;
+		buf += wsz;
+	}
+
+	bufsz = sb.st_size;
+	buf = obuf;
+
+	if (++done < 2)
+		goto again;
+
+	close(fd);
+	munmap(obuf, sb.st_size);
+	close(mfd);
+	return (0);
+}
+EOF
+mycc -o /tmp/$prog -Wall -Wextra -O0 /tmp/$prog.c || exit 1
+
+cat > /tmp/$prog.serial.c <<EOF
+/* Fill a file with sequential numbers */
+#include <sys/param.h>
+#include <sys/mman.h>
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+int
+main(int argc, char *argv[])
+{
+	size_t i, size;
+	long ix, *lp;
+	int fd;
+	char *file;
+
+	if (argc != 3) {
+		fprintf(stderr, "Usage: %s <file> <file length in bytes>\n", argv[0]);
+		exit(1);
+	}
+	file = argv[1];
+	size = atol(argv[2]);
+
+	if ((fd = open(file, O_RDWR | O_CREAT | O_TRUNC, 0600)) < 0)
+		err(1, "%s", file);
+
+	if (lseek(fd, size - 1, SEEK_SET) == -1)
+		err(1, "lseek error");
+
+	/* write a dummy byte at the last location */
+	if (write(fd, "\0", 1) != 1)
+		err(1, "write error");
+
+	if ((lp = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED)
+		err(1, "mmap()");
+
+	for (i = 0, ix = 0; i < size; i += sizeof(long), ix++)
+		lp[ix] = ix;
+
+	if (munmap(lp, size) == -1)
+		err(1, "munmap");
+	close(fd);
+}
+EOF
+mycc -o /tmp/$prog.serial -Wall -Wextra -O0 /tmp/$prog.serial.c || exit 1
+
+mount | grep -q "on $mntpoint " && umount -f $mntpoint
+mdconfig -l | grep -q md$mdstart && mdconfig -d -u $mdstart
+mdconfig -s 5g -u $mdstart
+
+newfs -n $newfs_flags /dev/md$mdstart > /dev/null
+mount /dev/md$mdstart $mntpoint
+
+here=`pwd`
+cd $mntpoint
+
+size=875998990
+pagesize=`sysctl -n hw.pagesize`
+tail=$((size % pagesize))
+/tmp/$prog.serial file $size
+
+cat file file > file.post
+mv file file.orig
+md5=`md5 < file.post`
+
+cp /usr/bin/sort /tmp/$prog.sort
+counter=1
+n=$((`sysctl -n hw.ncpu`))
+[ $n -gt 10 ] && n=10
+s=0
+start=`date +%s`
+while [ $((`date +%s` - start)) -lt 300 ]; do
+	st=`date +%s`
+	cp file.orig file
+	for i in `jot $n`; do
+		timeout -k 70s 1m /tmp/$prog.sort /dev/zero &
+	done
+	sleep $n
+	/tmp/$prog
+	while pkill $prog.sort; do sleep .2; done
+	wait
+	m=`md5 < file`
+	if [ $md5 != $m ]; then
+		echo "Failed @ iteration $counter"
+		ls -l
+		od -t x8 file      > /var/tmp/$prog.file1
+		od -t x8 file.post > /var/tmp/$prog.file2
+		diff /var/tmp/$prog.file1 /var/tmp/$prog.file2 > $log
+		head -10 $log
+		rm /var/tmp/$prog.file1 /var/tmp/$prog.file2
+		s=1
+		break
+	fi
+	echo "`date +%T` Loop #$counter, elapsed $((`date +%s` - st)) seconds."
+	counter=$((counter + 1))
+done
+cd $here
+
+umount $mntpoint
+mdconfig -d -u $mdstart
+rm /tmp/$prog /tmp/$prog.c /tmp/$prog.sort
+[ $s -eq 0 ] &&
+	printf "OK   File size is %9d, tail is %4d bytes. (%3d loops)\n" $size $tail $counter ||
+	printf "FAIL File size is %9d, tail is %4d bytes. (%3d loops)\n" $size $tail $counter
+exit $s