The branch main has been updated by pho: URL: https://cgit.FreeBSD.org/src/commit/?id=9c6ed9a88d3afb8bc9aba7cf852cc36e03bcd34c
commit 9c6ed9a88d3afb8bc9aba7cf852cc36e03bcd34c Author: Peter Holm <[email protected]> AuthorDate: 2026-07-14 07:27:42 +0000 Commit: Peter Holm <[email protected]> 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 <[email protected]> +# +# 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 <[email protected]> +# +# 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
