git: 9c6ed9a88d3a - main - stress2: Added new test scenarios

From: Peter Holm <pho_at_FreeBSD.org>
Date: Tue, 14 Jul 2026 07:29:01 UTC
The branch main has been updated by pho:

URL: https://cgit.FreeBSD.org/src/commit/?id=9c6ed9a88d3afb8bc9aba7cf852cc36e03bcd34c

commit 9c6ed9a88d3afb8bc9aba7cf852cc36e03bcd34c
Author:     Peter Holm <pho@FreeBSD.org>
AuthorDate: 2026-07-14 07:27:42 +0000
Commit:     Peter Holm <pho@FreeBSD.org>
CommitDate: 2026-07-14 07:27:42 +0000

    stress2: Added new test scenarios
---
 tools/test/stress2/misc/nullfs33.sh         | 129 ++++++++++++++++++++++
 tools/test/stress2/misc/nullfs34.sh         |  28 +++++
 tools/test/stress2/misc/pddupfd.sh          | 132 +++++++++++++++++++++++
 tools/test/stress2/misc/rename17.sh         |  57 ++++++++++
 tools/test/stress2/misc/rename18.sh         |  21 ++++
 tools/test/stress2/misc/rename_exchange.sh  | 162 ++++++++++++++++++++++++++++
 tools/test/stress2/misc/rename_exchange2.sh |  84 +++++++++++++++
 7 files changed, 613 insertions(+)

diff --git a/tools/test/stress2/misc/nullfs33.sh b/tools/test/stress2/misc/nullfs33.sh
new file mode 100755
index 000000000000..8b445405e213
--- /dev/null
+++ b/tools/test/stress2/misc/nullfs33.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+
+# Bug 275570 - self-referential nullfs mount over tmpfs in combination with MNT_UPDATE results in a hang
+
+# Test scenario mounts:
+# tmpfs on /mnt (tmpfs, local, noexec, read-only)
+# .setup-done on /mnt/.setup-done (nullfs, local)
+
+# Hang seen on a GENERIC kernel only
+# UID  PID PPID C PRI NI   VSZ  RSS MWCHAN STAT TT     TIME COMMAND
+#   0 4429 4417 4  59  0 14192 2832 vlp2   D+    0  0:00.06 ./nullfs33
+# https://people.freebsd.org/~pho/stress/log/log0663.txt
+
+
+. ../default.cfg
+set -u
+prog=$(basename "$0" .sh)
+
+cat > /tmp/$prog.c <<EOF
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/_iovec.h>
+#include <sys/file.h>
+#include <sys/mount.h>
+
+static void xmount(const char* fstype, const char* from, const char* to, int flags) {
+
+  char errmsg[255];
+  errmsg[0] = '\0';
+
+  int capacity = 8;
+
+  struct iovec* iov = malloc(sizeof(struct iovec) * capacity);
+
+  iov[0].iov_base = "fstype";
+  iov[0].iov_len  = sizeof("fstype");
+  iov[1].iov_base = __DECONST(char*, fstype);
+  iov[1].iov_len  = strlen(fstype) + 1;
+
+  iov[2].iov_base = "fspath";
+  iov[2].iov_len  = sizeof("fspath");
+  iov[3].iov_base = __DECONST(char*, to);
+  iov[3].iov_len  = strlen(to) + 1;
+
+  iov[4].iov_base = "from";
+  iov[4].iov_len  = sizeof("from");
+  iov[5].iov_base = __DECONST(char*, from);
+  iov[5].iov_len  = strlen(from) + 1;
+
+  iov[6].iov_base = "errmsg";
+  iov[6].iov_len  = sizeof("errmsg");
+  iov[7].iov_base = errmsg;
+  iov[7].iov_len  = sizeof(errmsg);
+
+  int len = 8;
+
+  if (flags & MNT_NOCOVER) {
+
+    assert(len + 2 <= capacity);
+
+    iov[len].iov_base = "nocover";
+    iov[len].iov_len  = sizeof("nocover");
+    len++;
+
+    iov[len].iov_base = NULL;
+    iov[len].iov_len  = 0;
+    len++;
+  }
+
+  assert(len == capacity);
+
+  if (nmount(iov, len, (int)flags) == -1) {
+    err(EXIT_FAILURE, "nmount %s -> %s: %s", from, to, errmsg);
+  }
+
+  free(iov);
+}
+
+static void xchdir(const char* path) {
+  if (chdir(path) == -1) {
+    err(EXIT_FAILURE, "chdir(\"%s\")", path);
+  }
+}
+
+static void xcreat(char* path, int mode) {
+  int fd = open(path, O_CREAT | O_WRONLY, mode);
+  if (fd == -1) {
+    err(EXIT_FAILURE, "can't create %s", path);
+  }
+  close(fd);
+}
+
+//#define ROOT_DIR "/mnt"
+#define ROOT_DIR "$mntpoint"
+
+int main(int argc __unused, char* argv[] __unused) {
+
+  xchdir(ROOT_DIR);
+
+  xmount("tmpfs", "tmpfs", ".", MNT_NOEXEC);
+
+  xcreat(".setup-done", 0444);
+  xmount("nullfs", ".setup-done", ".setup-done", 0);
+
+  xmount("tmpfs", "tmpfs", ".", MNT_RDONLY | MNT_NOEXEC | MNT_UPDATE);
+
+  unmount(ROOT_DIR "/.setup-done", MNT_FORCE);
+  unmount(ROOT_DIR, MNT_FORCE);
+
+  return 0;
+}
+EOF
+cc -o /tmp/$prog -Wall -Wextra -O0 /tmp/$prog.c || exit 1
+
+cd /tmp
+start=`date +%s`
+while [ $((`date +%s` - start)) -lt 60 ]; do
+	./$prog || break
+done
+cd -
+
+mount | grep -q "on $mntpoint " && umount $mntpoint
+rm -f /tmp/$prog /tmp/$prog.c
+exit 0
diff --git a/tools/test/stress2/misc/nullfs34.sh b/tools/test/stress2/misc/nullfs34.sh
new file mode 100755
index 000000000000..9e70049e23ec
--- /dev/null
+++ b/tools/test/stress2/misc/nullfs34.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+#
+# Copyright (c) 2026 Peter Holm <pho@FreeBSD.org>
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+# "nullfs mount over a file" scenario
+
+set -u
+prog=$(basename "$0" .sh)
+
+file=/tmp/$prog.file
+mp=/tmp/$prog.mp
+touch $file $mp
+
+s=0
+mount_nullfs $file $mp || exit 1
+echo a > $mp
+cmp -s $file $mp ||
+    { echo "Files differ"; ls -l $file $mp; s=1; }
+umount $mp
+cmp -s $file $mp &&
+    { echo "Files are equal"; ls -l $file $mp; s=2; }
+
+rm -f $file $mp
+exit $s
diff --git a/tools/test/stress2/misc/pddupfd.sh b/tools/test/stress2/misc/pddupfd.sh
new file mode 100755
index 000000000000..cc71cf883e41
--- /dev/null
+++ b/tools/test/stress2/misc/pddupfd.sh
@@ -0,0 +1,132 @@
+#!/bin/sh
+
+set -u
+prog=$(basename "$0" .sh)
+cat > /tmp/$prog.c <<EOF
+/* \$Id: pddupfd.c,v 1.6 2026/07/04 17:48:47 kostik Exp kostik $ */
+
+#include <sys/types.h>
+#include <sys/fcntl.h>
+#include <sys/mman.h>
+#include <sys/procdesc.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+#include <machine/atomic.h>
+#include <err.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#define MY_SYS_pdopenpid	603
+#define MY_SYS_pddupfd		604
+
+static int
+my_pdopenpid(pid_t pid, int flags)
+{
+	return (syscall(MY_SYS_pdopenpid, pid, flags));
+}
+
+static int
+my_pddupfd(pid_t pid, int remote_fd, int flags)
+{
+	return (syscall(MY_SYS_pddupfd, pid, remote_fd, flags));
+}
+
+static void
+check(int fd, int child, int *comm)
+{
+	struct __wrusage wu;
+	struct __siginfo si;
+	int error, f, pp, rfd, status, workfd;
+	char cmd[128];
+
+	error = pdgetpid(fd, &pp);
+	if (error == -1)
+		err(1, "pgdetpid");
+	printf("child pid %d %d\n", child, pp);
+
+	while ((rfd = (int)atomic_load_int(comm)) == -1)
+		;
+	if (rfd == -2)
+		errx(1, "open in child failed");
+	workfd = my_pddupfd(fd, rfd, 0);
+	if (workfd == -1)
+		err(1, "pddupfd");
+
+	f = fcntl(workfd, F_GETFD);
+	if (f == -1)
+		err(1, "F_GETFD %d", workfd);
+	f &= ~FD_CLOEXEC;
+	error = fcntl(workfd, F_SETFD, f);
+	if (error == -1)
+		err(1, "F_SETFD %d", workfd);
+
+	snprintf(cmd, sizeof(cmd), "cat 0<&%d", workfd);
+	system(cmd);
+	close(workfd);
+
+	atomic_store_int(comm, -1);
+	
+	error = pdwait(fd, &status, WEXITED, &wu, &si);
+	if (error == -1)
+		err(1, "pdwait");
+	printf("exited pid %d status %#x si.si_status %#x si.si_code %#x\n",
+	    pp, status, si.si_status, si.si_code);
+}
+
+int
+main(void)
+{
+	int *comm;
+	pid_t child;
+	int fd, workfd;
+
+	comm = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED |
+	    MAP_ANONYMOUS, -1, 0);
+	if (comm == MAP_FAILED)
+		err(1, "mmap");
+	atomic_store_int(comm, -1);
+
+	child = fork();
+	if (child == -1)
+		err(1, "fork");
+	if (child == 0) {
+		workfd = open("/etc/passwd", O_RDONLY);
+		if (workfd == -1) {
+			atomic_store_int(comm, -2);
+			err(1, "open");
+		}
+		atomic_store_int(comm, workfd);
+		while ((int)atomic_load_int(comm) >= 0)
+			sleep(1);
+		_exit(0x123);
+	}
+
+	fd = my_pdopenpid(child, 0);
+	if (fd == -1)
+		err(1, "pdopenpid");
+
+	check(fd, child, comm);
+	close(fd);
+}
+EOF
+
+cc -o /tmp/$prog -Wall -Wextra -g -O0 /tmp/$prog.c || exit 1
+
+../testcases/swap/swap -t 2m -i 20 &
+sleep 2
+
+cd /tmp
+s=0
+start=`date +%s`
+while [ $((`date +%s`- start)) -lt 120 ]; do
+	./$prog > /dev/null ; s=$?
+done
+[ $s -eq 124 ] && s=0
+while pkill swap; do :; done
+wait
+cd -
+
+rm /tmp/$prog /tmp/$prog.c
+exit $s
diff --git a/tools/test/stress2/misc/rename17.sh b/tools/test/stress2/misc/rename17.sh
new file mode 100755
index 000000000000..682e1908a9e0
--- /dev/null
+++ b/tools/test/stress2/misc/rename17.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+#
+# Copyright (c) 2026 Peter Holm <pho@FreeBSD.org>
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+# Test rename(2) over nfs.
+# This needs to be in /etc/exports: /mnt	-maproot=root 127.0.0.1
+
+# "panic: Lock rename not exclusively locked @ ../../../kern/vfs_subr.c:5878" seen
+
+. ../default.cfg
+
+[ `id -u ` -ne 0 ] && echo "Must be root!" && exit 1
+grep -q $mntpoint /etc/exports ||
+	{ echo "$mntpoint missing from /etc/exports"; exit 0; }
+ps -x | grep -v grep | grep -q nfsd || { echo "nfsd is not running"; exit 0; }
+
+set -u
+mp1=$mntpoint
+mp2=${mntpoint}2
+
+mount | grep "$mp2" | grep nfs > /dev/null && umount -f $mp2
+mount | grep "$mp1" | grep -q /md && umount -f $mp1
+mdconfig -l | grep -q $mdstart  &&  mdconfig -d -u $mdstart
+
+set -e
+mdconfig -a -t swap -s 2g -u $mdstart
+newfs $newfs_flags /dev/md$mdstart > /dev/null
+mount /dev/md$mdstart $mp1
+set +e
+
+mkdir $mp1/stressX $mp1/stressX.control
+chmod 777 $mp1/stressX $mp1/stressX.control
+
+[ ! -d $mp2 ] &&  mkdir $mp2
+chmod 777 $mp2
+
+mount -t nfs -o tcp -o retrycnt=3 -o soft -o rw \
+    127.0.0.1:$mp1 $mp2
+
+sleep 1
+export CTRLDIR=$mp2/stressX.control
+export RUNDIR=$mp2/stressX
+export renameLOAD=100
+export runRUNTIME=5m
+export TESTPROGS="
+testcases/rename/rename
+testcases/swap/swap"
+su -m $testuser -c 'cd ..; ./testcases/run/run $TESTPROGS'
+
+umount -f $mp2 > /dev/null 2>&1
+umount -f $mp1 > /dev/null 2>&1
+mdconfig -d -u $mdstart
+exit 0
diff --git a/tools/test/stress2/misc/rename18.sh b/tools/test/stress2/misc/rename18.sh
new file mode 100755
index 000000000000..f71e29bb1bef
--- /dev/null
+++ b/tools/test/stress2/misc/rename18.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+# Test scenario from:
+# Bug 295826 - kern_renameat() infinite loop
+
+set -u
+root=/root
+cd $root
+mkdir -p $root/test_loop/a
+mkdir -p $root/test_loop/b
+echo '123' > $root/test_loop/b/file
+touch $root/test_loop/a/file
+mount_nullfs -o rw -o nocache -o noatime -o noexec -o nosuid $root/test_loop/b/file $root/test_loop/a/file
+echo '123' > /tmp/file
+
+# The "mv" would spin
+mv /tmp/file $root/test_loop/a/file
+
+umount $root/test_loop/a/file
+rm -rf /tmp/file $root/test_loop
+exit
diff --git a/tools/test/stress2/misc/rename_exchange.sh b/tools/test/stress2/misc/rename_exchange.sh
new file mode 100755
index 000000000000..23f837af84d6
--- /dev/null
+++ b/tools/test/stress2/misc/rename_exchange.sh
@@ -0,0 +1,162 @@
+#!/bin/sh
+
+# Test of RENAME_EXCHANGE for tmpfs
+
+. ../default.cfg
+set -u
+prog=$(basename "$0" .sh)
+cat > /tmp/$prog.c <<EOF
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#if ! defined(AT_RENAME_EXCHANGE)
+#define AT_RENAME_EXCHANGE 0x0002
+#endif
+
+static char *dir, *f1, *f2;
+
+void
+test1(void)
+{
+	struct stat s, s1, s2;
+	time_t start;
+	char file1[1024], file2[1024];
+
+	snprintf(file1, sizeof(file1), "%s/%s", dir, f1);
+	snprintf(file2, sizeof(file2), "%s/%s", dir, f2);
+	if (stat(file1, &s1) < 0)
+		err(1, "stat(%s)", file1);
+	if (stat(file2, &s2) < 0)
+		err(1, "stat(%s)", file2);
+	assert(s1.st_size != s2.st_size);
+	start = time(NULL);
+	while (time(NULL) - start < 30) {
+		if (renameat2(AT_FDCWD, file1, AT_FDCWD, file2, RENAME_EXCHANGE) == -1)
+			err(1, "%s: rename2(%s, %s)", __func__, file1, file2);
+
+		if (stat(file1, &s) < 0)
+			err(1, "statfs(%s)", file1);
+		if (s.st_size != s2.st_size)
+			errx(1, "Got size %jd, expected %jd", (uintmax_t)s.st_size, (uintmax_t)s2.st_size);
+
+		if (renameat2(AT_FDCWD, file2, AT_FDCWD, file1, RENAME_EXCHANGE) == -1)
+			err(1, "%s: rename2(%s, %s)", __func__, file2, file1);
+	}
+}
+
+void
+test2(void)
+{
+	time_t start;
+	char cwd[1024];
+
+	if (getcwd(cwd, sizeof(cwd)) == NULL)
+		err(1, "getcwd()");
+	chdir(dir);
+	start = time(NULL);
+	while (time(NULL) - start < 30) {
+		if (renameat2(AT_FDCWD, f1, AT_FDCWD, f2, RENAME_EXCHANGE) == -1)
+			err(1, "%s: rename2(%s, %s)", __func__, f1, f2);
+
+		if (renameat2(AT_FDCWD, f2, AT_FDCWD, f1, RENAME_EXCHANGE) == -1)
+			err(1, "%s: rename2(%s, %s)", __func__, f2, f1);
+	}
+	chdir(cwd);
+}
+
+/* rename(<file>, <non existing file>) */
+void
+test3(void)
+{
+	int e;
+	char file1[1024], file2[1024];
+
+	snprintf(file1, sizeof(file1), "%s/%s", dir, f1);
+	snprintf(file2, sizeof(file2), "%s/NotThere", dir);
+	if (renameat2(AT_FDCWD, file1, AT_FDCWD, file2, RENAME_EXCHANGE) != -1)
+		err(1, "%s: rename2(%s, %s)", __func__, file1, file2);
+	e = errno;
+	if (e != ENOENT)
+		errx(1, "%s: Expected errno %i, got %d\n", __func__, ENOENT, e);
+
+	if (renameat2(AT_FDCWD, file2, AT_FDCWD, file1, RENAME_EXCHANGE) != -1)
+		err(1, "%s: rename2(%s, %s)", __func__, file2, file1);
+	e = errno;
+	if (e != ENOENT)
+		errx(1, "%s: Expected errno %i, got %d\n", __func__, ENOENT, e);
+}
+
+/* rename(<file>, <directory>) */
+void
+test4(void)
+{
+	int e;
+	char file1[1024], file2[1024];
+
+	snprintf(file1, sizeof(file1), "%s/%s", dir, f1);
+	snprintf(file2, sizeof(file2), "%s", dir);
+	if (renameat2(AT_FDCWD, file1, AT_FDCWD, file2, RENAME_EXCHANGE) != -1)
+		err(1, "%s: rename2(%s, %s)", __func__, file1, file2);
+	e = errno;
+	if (e != EINVAL)
+		errx(1, "%s: Expected errno %i, got %d\n", __func__, EINVAL, e);
+
+	if (renameat2(AT_FDCWD, file2, AT_FDCWD, file1, RENAME_EXCHANGE) != -1)
+		err(1, "%s: rename2(%s, %s)", __func__, file2, file1);
+	e = errno;
+	if (e != EINVAL)
+		errx(1, "%s: Expected errno %i, got %d\n", __func__, EINVAL, e);
+}
+
+int
+main(int argc, char *argv[])
+{
+
+	if (argc != 4) {
+		fprintf(stderr, "Usage: %s <dir> <file1> <file2>\n", argv[0]);
+		exit(1);
+	}
+	dir = argv[1];
+	f1 = argv[2];
+	f2 = argv[3];
+
+	test1();
+	test2();
+	test3();
+	test4();
+}
+EOF
+cc -o /tmp/$prog -Wall -Wextra -g -O0 /tmp/$prog.c || exit 1
+
+mount | grep -q "on $mntpoint " && umount $mntpoint
+size=2g
+mount -o size=${size}m -t tmpfs tmpfs $mntpoint
+
+../testcases/swap/swap -t 5m -i 20 > /dev/null &
+sleep 1
+
+for d in d1 d2 d3 d4 d5; do
+	mkdir $mntpoint/$d
+	echo 1  > $mntpoint/$d/f1
+	echo 22 > $mntpoint/$d/f2
+done
+cd $mntpoint
+for d in d1 d2 d3 d4 d5; do
+	/tmp/$prog $mntpoint/$d f1 f2 &
+done
+while pgrep -q $prog; do sleep .2; done
+cd -
+
+while pkill swap; do sleep .5; done
+wait
+
+umount $mntpoint
+exit 0
diff --git a/tools/test/stress2/misc/rename_exchange2.sh b/tools/test/stress2/misc/rename_exchange2.sh
new file mode 100755
index 000000000000..c8ecd7713cd4
--- /dev/null
+++ b/tools/test/stress2/misc/rename_exchange2.sh
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+# Test of RENAME_EXCHANGE for tmpfs, cross directory version
+
+. ../default.cfg
+set -u
+prog=$(basename "$0" .sh)
+cat > /tmp/$prog.c <<EOF
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#if ! defined(AT_RENAME_EXCHANGE)
+#define AT_RENAME_EXCHANGE 0x0002
+#endif
+
+static char *f1, *f2;
+
+void
+test1(void)
+{
+	struct stat s, s1, s2;
+	time_t start;
+
+	if (stat(f1, &s1) < 0)
+		err(1, "stat(%s)", f1);
+	if (stat(f2, &s2) < 0)
+		err(1, "stat(%s)", f2);
+	assert(s1.st_size != s2.st_size);
+	start = time(NULL);
+	while (time(NULL) - start < 30) {
+		if (renameat2(AT_FDCWD, f1, AT_FDCWD, f2, RENAME_EXCHANGE) == -1)
+			err(1, "%s: rename2(%s, %s)", __func__, f1, f2);
+
+		if (stat(f1, &s) < 0)
+			err(1, "statfs(%s)", f1);
+		if (s.st_size != s2.st_size)
+			errx(1, "Got size %jd, expected %jd", (uintmax_t)s.st_size, (uintmax_t)s2.st_size);
+
+		if (renameat2(AT_FDCWD, f2, AT_FDCWD, f1, RENAME_EXCHANGE) == -1)
+			err(1, "%s: rename2(%s, %s)", __func__, f2, f1);
+	}
+}
+
+int
+main(int argc, char *argv[])
+{
+
+	if (argc != 3) {
+		fprintf(stderr, "Usage: %s <file1> <file2>\n", argv[0]);
+		exit(1);
+	}
+	f1 = argv[1];
+	f2 = argv[2];
+
+	test1();
+}
+EOF
+cc -o /tmp/$prog -Wall -Wextra -g -O0 /tmp/$prog.c || exit 1
+
+mount | grep -q "on $mntpoint " && umount $mntpoint
+size=2g
+mount -o size=${size}m -t tmpfs tmpfs $mntpoint
+
+../testcases/swap/swap -t 5m -i 20 > /dev/null &
+sleep 1
+
+mkdir $mntpoint/d1 $mntpoint/d2
+echo 1  > $mntpoint/d1/f1
+echo 22 > $mntpoint/d2/f2
+/tmp/$prog $mntpoint/d1/f1 $mntpoint/d2/f2
+
+while pkill swap; do sleep .5; done
+wait
+
+umount $mntpoint
+exit 0