Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F174331631
D60204.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
6 KB
Referenced Files
None
Subscribers
None
D60204.diff
View Options
diff --git a/sys/ufs/ufs/ufs_vnops.c b/sys/ufs/ufs/ufs_vnops.c
--- a/sys/ufs/ufs/ufs_vnops.c
+++ b/sys/ufs/ufs/ufs_vnops.c
@@ -205,16 +205,17 @@
static int
ufs_sync_nlink1(struct mount *mp)
{
- int error;
- error = vfs_busy(mp, 0);
- if (error == 0) {
- VFS_SYNC(mp, MNT_WAIT);
- vfs_unbusy(mp);
- error = ERELOOKUP;
- }
+ /*
+ * Do not busy the mount point. The caller is between
+ * vn_start_write() and vn_finished_write(), which already keeps
+ * an unmount from getting past the write suspension in
+ * VFS_UNMOUNT(), while vfs_busy() would wait for the unmount to
+ * finish and deadlock with it.
+ */
+ VFS_SYNC(mp, MNT_WAIT);
vfs_rel(mp);
- return (error);
+ return (ERELOOKUP);
}
static int
diff --git a/tools/test/stress2/misc/nlink6.sh b/tools/test/stress2/misc/nlink6.sh
new file mode 100755
--- /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 <sobomax@FreeBSD.org>
+#
+# 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_nlink1(), which
+# syncs the file system and restarts. ufs_sync_nlink1() used to 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 <<EOF
+#include <sys/stat.h>
+#include <ufs/ufs/dinode.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+/*
+ * "setup": fill directory d with subdirectories up to UFS_LINK_MAX, the
+ * first nworkers of them named w<id>.
+ * "work <d> <id> <seconds>": remove and recreate <d>/w<id> in a loop.
+ * Work relative to <d>: 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 <n> | work <dir> <id> <seconds>");
+ 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
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Oct 3, 11:24 AM (9 h, 19 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40107952
Default Alt Text
D60204.diff (6 KB)
Attached To
Mode
D60204: ufs: do not busy the mount point in ufs_sync_nlink1()
Attached
Detach File
Event Timeline
Log In to Comment