From nobody Tue Oct 13 04:01:39 2026 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4j3gd00cHHz6vcQk for ; Tue, 13 Oct 2026 04:01:40 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR2" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4j3gcz6xhGz4lrx for ; Tue, 13 Oct 2026 04:01:39 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1791864100; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=rkTL/hd54ZBnFPRv9JPK89pxXSoQQ5AVHrOFCdab/EA=; b=XEBZW+S7FWxe4YiXji+Xag6ifgMcGQGHdHul7rWhhFhIoNijtOA3SorZc7AhdytbeVzzLL ZK2nniKNUlh9bPGpxO+77JB4BpRl+FVVvwd/YaHHm+E/jvqOcqX2PGstv6LMZLy9yAZrve dzC5AiunVnLIT69quCXeXRA13bwNT/25W4tqWcgzDhNDbuXCD4fZSUTNLhe/NA/5+Wz6pS YDWw5RKkqK9J9Naio0gn6VX8ND4re0R1IOBd0z/QV9wSL05mzjVPrwUmF5m4Iezwy0WJ52 cfpZNz3W9/ptTDNLU2yjbwJyUg5jb+7OyXSpZBQiyvsn084tLFJwaXEWw/ryRw== ARC-Seal: i=1; a=rsa-sha256; d=freebsd.org; s=dkim; cv=none; t=1791864100; b=Xm5EkONdeWYN4etHGOqoYi5PoP3ivDWnDjWOFT7rxDDD51cO/brJ506JNFdzzGP3ufJIRG l0R+BJdSSf1TYcMJ6UREBkSPOozqz7JcakBw73g1umFDpK0cWCZo/gnRvhWU1qOmw+Q/Sk D4/FvdFj6MGqwIla5BwQLbI+kJvj7Cm9nqdChmNAzsH+5rNDQtA9SZEwKEl5gh1lrSa135 yKQjJw0ZZpG9M91lbgyrmtJAEjNm1GnQmOSPX25B+C+FlZ3hM8izrhxKYsHa6NkdBr9D5n HG5pHSw7XMOhbFMSDi0eW+uqwUIDi+VDv89rwaOYgEuYKhnjQeEhbhqynyEhaw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1791864100; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=rkTL/hd54ZBnFPRv9JPK89pxXSoQQ5AVHrOFCdab/EA=; b=j5oRdmhQQxTIIBiAZOMCsxwKjhB/W/N/bkZ6x+sLGbdwZkAY3/+fCAWvMNRzmvpGdczKT1 MxYvUi4LLvzTMOGA/di7gjO06J10CUzFh2P9fa6iPwhXO+NWfpQcZ3E8ZLLi4l7fGY1QH4 zS7waNbJpCqfNgJKHFJvbS0vXlR/fttIAupSKyJ3a8jCZ+VKjVGZ5h8wB2qW+bAeko9/wS gUpGmK+tplHd9GIQsIHHeTBcOzTME2Di3JLI4Gk9Cq8lZ4pFjeS4Yrxf1XvUFtIG+3lokC p5+NNrvD7zXP5cQlf9JQjNkVItNKLvoLl7DrcoH5eNEU5UTP/Xhk/jH8tiy7fA== ARC-Authentication-Results: i=1; mx1.freebsd.org; none Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4j3gcz60ygzy4M for ; Tue, 13 Oct 2026 04:01:39 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 26323 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 13 Oct 2026 04:01:39 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Maxim Sobolev Subject: git: 2aca5deff7a0 - main - stress2: add a test for forced unmount racing mkdir at UFS_LINK_MAX List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org List-Id: Commit messages for the main branch of the src repository List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: sobomax X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 2aca5deff7a0fb9f6f082d562f1ca85e707f674e Auto-Submitted: auto-generated Date: Tue, 13 Oct 2026 04:01:39 +0000 Message-Id: <6acdad23.26323.7ea0b120@gitrepo.freebsd.org> The branch main has been updated by sobomax: URL: https://cgit.FreeBSD.org/src/commit/?id=2aca5deff7a0fb9f6f082d562f1ca85e707f674e commit 2aca5deff7a0fb9f6f082d562f1ca85e707f674e Author: Maxim Sobolev AuthorDate: 2026-10-13 03:59:13 +0000 Commit: Maxim Sobolev CommitDate: 2026-10-13 04:00:36 +0000 stress2: add a test for forced unmount racing mkdir at UFS_LINK_MAX On a soft updates file system, keep a directory at UFS_LINK_MAX with workers removing and recreating subdirectories in it, so that mkdir(2) keeps going through ufs_sync_nlink(), and force-unmount, check and remount the file system in a loop. The workers work relative to the directory: a lookup through the mount point would wait for the unmount on the covered vnode lock and never race with it. Before the fix, the first forced unmount deadlocked with mkdir(2) sleeping in vfs_busy(). With dtrace=1, count the vfs_busy() calls made during an unmount by caller, and the mkdir(2) calls that synced and restarted with ERELOOKUP during an unmount, which shows that the race was hit. Sponsored by: Sippy Software, Inc. Reviewed by: pho Tested by: pho Differential Revision: https://reviews.freebsd.org/D60204 MFC after: 1 month --- tools/test/stress2/misc/nlink6.sh | 191 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/tools/test/stress2/misc/nlink6.sh b/tools/test/stress2/misc/nlink6.sh new file mode 100755 index 000000000000..6f9d2f4ca3a9 --- /dev/null +++ b/tools/test/stress2/misc/nlink6.sh @@ -0,0 +1,191 @@ +#!/bin/sh + +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2026 Maksym Sobolyev +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# + +# Forced unmount racing mkdir(2) in a directory at UFS_LINK_MAX on SU. +# +# With soft updates, rmdir(2) lowers the parent's i_effnlink at once and +# its i_nlink only when the removal is on disk. A mkdir(2) in between +# finds i_nlink at UFS_LINK_MAX and goes through ufs_sync_nlink(), which +# syncs the file system and restarts. The sync used to busy the mount +# and sleep in vfs_busy() while an unmount was in progress, with the +# mkdir(2) still counted as a write by vn_start_write(), while the unmount +# waited in vfs_write_suspend() for that write to end. The unmount and +# every later vfs_busy() of the mount point hung. +# +# Set dtrace=1 to count, while an unmount is in progress, the vfs_busy() +# calls by caller, and the mkdir(2) calls that synced and restarted with +# ERELOOKUP. Without the fix, a vfs_busy() from ufs_sync_nlink() here is +# the deadlock; with it, the mkdir(2) calls show the race was hit. + +[ `id -u` -ne 0 ] && echo "Must be root!" && exit 1 + +. ../default.cfg + +UFS_LINK_MAX=`grep UFS_LINK_MAX /usr/include/ufs/ufs/dinode.h 2>/dev/null` +[ -z "$UFS_LINK_MAX" ] && exit 0 +UFS_LINK_MAX=`echo $UFS_LINK_MAX | awk '{print $3}'` +nworkers=8 +runtime=${runtime:-300} +log=/tmp/nlink6.log + +cat > /tmp/nlink6.c < +#include +#include +#include +#include +#include +#include +#include + +/* + * "setup": fill directory d with subdirectories up to UFS_LINK_MAX, the + * first nworkers of them named w. + * "work ": remove and recreate /w in a loop. + * Work relative to : a lookup through the mount point would wait for + * the unmount on the covered vnode lock and never race with it. + */ +int +main(int argc, char *argv[]) +{ + char path[1024]; + time_t start; + int i, nworkers, secs; + + if (argc == 3 && strcmp(argv[1], "setup") == 0) { + nworkers = atoi(argv[2]); + if (mkdir("d", 0755) == -1) + err(1, "mkdir(d)"); + for (i = 0; i < UFS_LINK_MAX - 2; i++) { + if (i < nworkers) + snprintf(path, sizeof(path), "d/w%d", i); + else + snprintf(path, sizeof(path), "d/%d", i); + if (mkdir(path, 0755) == -1) + err(1, "mkdir(%s)", path); + } + return (0); + } + if (argc != 5 || strcmp(argv[1], "work") != 0) + errx(1, "usage: nlink6 setup | work "); + if (chdir(argv[2]) == -1) + err(1, "chdir(%s)", argv[2]); + snprintf(path, sizeof(path), "w%s", argv[3]); + secs = atoi(argv[4]); + start = time(NULL); + while (time(NULL) - start < secs) { + /* Errors are expected once the file system is gone. */ + if (rmdir(path) == -1 || mkdir(path, 0755) == -1) + usleep(1000); + } + return (0); +} +EOF +mycc -o /tmp/nlink6 -Wall -Wextra -O2 /tmp/nlink6.c || exit 1 +rm /tmp/nlink6.c + +set -e +mount | grep -q "on $mntpoint " && umount -f $mntpoint +mdconfig -l | grep -q "md$mdstart" && mdconfig -d -u $mdstart +mdconfig -a -t swap -s 1g -u $mdstart +newfs -Un /dev/md$mdstart > /dev/null +mount /dev/md$mdstart $mntpoint +(cd $mntpoint; /tmp/nlink6 setup $nworkers) +umount $mntpoint +set +e + +if [ "$dtrace" = 1 ]; then + # The unmount-in-progress flag (MNTK_UNMOUNT) is 0x01000000. + dtrace -q -n 'fbt::vfs_busy:entry + /(args[0]->mnt_kern_flag & 0x01000000) != 0/ + { @b[stack(2)] = count(); } + fbt::ufs_mkdir:entry { self->mp = args[0]->a_dvp->v_mount; } + fbt::ufs_mkdir:return + /self->mp != NULL && (int)arg1 == -5 && + (self->mp->mnt_kern_flag & 0x01000000) != 0/ + { @m = count(); } + fbt::ufs_mkdir:return { self->mp = NULL; } + END { + printf("vfs_busy() during unmount, by caller:"); + printa(@b); + printa("mkdir(2) synced and restarted during unmount: %@d\n", + @m); + }' > $log.dtrace & + dpid=$! + sleep 5 +fi + +s=0 +start=`date +%s` +n=0 +while [ $((`date +%s` - start)) -lt $runtime ]; do + mount /dev/md$mdstart $mntpoint || { s=1; break; } + pids= + for i in `jot $nworkers 0`; do + /tmp/nlink6 work $mntpoint/d $i 10 & + pids="$pids $!" + done + sleep `jot -r 1 1 5` + umount -f $mntpoint & + upid=$! + t=0 + while kill -0 $upid 2>/dev/null; do + sleep 1 + t=$((t + 1)) + if [ $t -eq 120 ]; then + echo "FAIL: umount -f $mntpoint is stuck" + procstat -kk $upid `pgrep -x nlink6` + if [ -n "$dpid" ]; then + kill -TERM $dpid + wait $dpid + cat $log.dtrace + fi + # The mount point stays busy, leave everything as is. + exit 1 + fi + done + wait $upid $pids + n=$((n + 1)) + if ! fsck_ffs -fy /dev/md$mdstart > $log 2>&1; then + echo "FAIL: fsck_ffs exit $? after cycle $n" + tail -20 $log + s=1 + break + fi +done +echo "$n forced unmounts" + +if [ -n "$dpid" ]; then + kill -TERM $dpid + wait $dpid + cat $log.dtrace +fi +mdconfig -d -u $mdstart +rm -f /tmp/nlink6 $log $log.dtrace +exit $s