git: 935cf3284f52 - main - vfs_mount.c: Don't call VFS_MOUNT() if only exports are being updated

From: Rick Macklem <rmacklem_at_FreeBSD.org>
Date: Tue, 17 Feb 2026 23:01:27 UTC
The branch main has been updated by rmacklem:

URL: https://cgit.FreeBSD.org/src/commit/?id=935cf3284f520c90a63baaadb762caaa30084f5c

commit 935cf3284f520c90a63baaadb762caaa30084f5c
Author:     Rick Macklem <rmacklem@FreeBSD.org>
AuthorDate: 2026-02-17 22:57:42 +0000
Commit:     Rick Macklem <rmacklem@FreeBSD.org>
CommitDate: 2026-02-17 22:59:57 +0000

    vfs_mount.c: Don't call VFS_MOUNT() if only exports are being updated
    
    PR#293198 reports a hang within ZFS when exports
    are being updated concurrently with a VOP_SETEXTATTR().
    The hang appears to be caused by mishandling of the
    z_teardown_lock, but fixing handling of this lock appears
    to be a major effort. Since the hang occurs when
    VFS_MOUNT() acquires a write/exclusive z_teardown_lock,
    which rarely occurs, except when exports are being updated,
    this patch avoids the VFS_MOUNT() call for this case.
    
    Avoiding a VFS_MOUNT() call fixes the hang for the case
    reported by PR#293198 and is also an optimization.
    As such, this patch avoids the VFS_MOUNT() call when only exports
    are being updated similar to what was already being done
    within vnet prisons.
    
    PR:     293198
    Reviewed by:    kib, markj
    MFC after:      1 week
    Differential Revision:  https://reviews.freebsd.org/D55318
---
 sys/kern/vfs_mount.c | 52 +++++++++++++++++++++++++++++++---------------------
 1 file changed, 31 insertions(+), 21 deletions(-)

diff --git a/sys/kern/vfs_mount.c b/sys/kern/vfs_mount.c
index 2237fcc6b423..383ccf98c10e 100644
--- a/sys/kern/vfs_mount.c
+++ b/sys/kern/vfs_mount.c
@@ -74,7 +74,7 @@
 #define	VFS_MOUNTARG_SIZE_MAX	(1024 * 64)
 
 static int	vfs_domount(struct thread *td, const char *fstype, char *fspath,
-		    uint64_t fsflags, bool jail_export,
+		    uint64_t fsflags, bool only_export, bool jail_export,
 		    struct vfsoptlist **optlist);
 static void	free_mntarg(struct mntarg *ma);
 
@@ -806,7 +806,7 @@ vfs_donmount(struct thread *td, uint64_t fsflags, struct uio *fsoptions)
 	struct vfsopt *opt, *tmp_opt;
 	char *fstype, *fspath, *errmsg;
 	int error, fstypelen, fspathlen, errmsg_len, errmsg_pos;
-	bool autoro, has_nonexport, jail_export;
+	bool autoro, has_nonexport, only_export, jail_export;
 
 	errmsg = fspath = NULL;
 	errmsg_len = fspathlen = 0;
@@ -861,12 +861,11 @@ vfs_donmount(struct thread *td, uint64_t fsflags, struct uio *fsoptions)
 	 * when we want to update the root filesystem.
 	 */
 	has_nonexport = false;
-	jail_export = false;
+	only_export = false;
 	TAILQ_FOREACH_SAFE(opt, optlist, link, tmp_opt) {
 		int do_freeopt = 0;
 
-		if (jailed(td->td_ucred) &&
-		    strcmp(opt->name, "export") != 0 &&
+		if (strcmp(opt->name, "export") != 0 &&
 		    strcmp(opt->name, "update") != 0 &&
 		    strcmp(opt->name, "fstype") != 0 &&
 		    strcmp(opt->name, "fspath") != 0 &&
@@ -957,7 +956,7 @@ vfs_donmount(struct thread *td, uint64_t fsflags, struct uio *fsoptions)
 			fsflags |= MNT_UNION;
 		else if (strcmp(opt->name, "export") == 0) {
 			fsflags |= MNT_EXPORTED;
-			jail_export = true;
+			only_export = true;
 		} else if (strcmp(opt->name, "automounted") == 0) {
 			fsflags |= MNT_AUTOMOUNTED;
 			do_freeopt = 1;
@@ -989,14 +988,22 @@ vfs_donmount(struct thread *td, uint64_t fsflags, struct uio *fsoptions)
 	}
 
 	/*
-	 * If has_nonexport is true or the caller is not running within a
-	 * vnet prison that can run mountd(8), set jail_export false.
+	 * only_export is set to true only if exports are being
+	 * updated and nothing else is being updated.
+	 */
+	if (has_nonexport)
+		only_export = false;
+	/*
+	 * If only_export is true and the caller is running within a
+	 * vnet prison that can run mountd(8), set jail_export true.
 	 */
-	if (has_nonexport || !jailed(td->td_ucred) ||
-	    !prison_check_nfsd(td->td_ucred))
-		jail_export = false;
+	jail_export = false;
+	if (only_export && jailed(td->td_ucred) &&
+	    prison_check_nfsd(td->td_ucred))
+		jail_export = true;
 
-	error = vfs_domount(td, fstype, fspath, fsflags, jail_export, &optlist);
+	error = vfs_domount(td, fstype, fspath, fsflags, only_export,
+	    jail_export, &optlist);
 	if (error == ENODEV) {
 		error = EINVAL;
 		if (errmsg != NULL)
@@ -1014,8 +1021,8 @@ vfs_donmount(struct thread *td, uint64_t fsflags, struct uio *fsoptions)
 		printf("%s: R/W mount failed, possibly R/O media,"
 		    " trying R/O mount\n", __func__);
 		fsflags |= MNT_RDONLY;
-		error = vfs_domount(td, fstype, fspath, fsflags, jail_export,
-		    &optlist);
+		error = vfs_domount(td, fstype, fspath, fsflags, only_export,
+		    jail_export, &optlist);
 	}
 bail:
 	/* copyout the errmsg */
@@ -1307,6 +1314,7 @@ vfs_domount_update(
 	struct thread *td,		/* Calling thread. */
 	struct vnode *vp,		/* Mount point vnode. */
 	uint64_t fsflags,		/* Flags common to all filesystems. */
+	bool only_export,		/* Got export option. */
 	bool jail_export,		/* Got export option in vnet prison. */
 	struct vfsoptlist **optlist	/* Options local to the filesystem. */
 	)
@@ -1442,15 +1450,16 @@ vfs_domount_update(
 	 * get.  No freeing of cn_pnbuf.
 	 */
 	/*
+	 * When only updating mount exports, VFS_MOUNT() does not need to
+	 * be called, as indicated by only_export being set true.
 	 * For the case of mountd(8) doing exports from within a vnet jail,
 	 * "from" is typically not set correctly such that VFS_MOUNT() will
-	 * return ENOENT. It is not obvious that VFS_MOUNT() ever needs to be
-	 * called when mountd is doing exports, but this check only applies to
-	 * the specific case where it is running inside a vnet jail, to
-	 * avoid any POLA violation.
+	 * return ENOENT. For ZFS, there is a locking bug which can result in
+	 * deadlock if VFS_MOUNT() is called when extended attributes are
+	 * being updated.
 	 */
 	error = 0;
-	if (!jail_export)
+	if (!only_export)
 		error = VFS_MOUNT(mp);
 
 	export_error = 0;
@@ -1590,6 +1599,7 @@ vfs_domount(
 	const char *fstype,		/* Filesystem type. */
 	char *fspath,			/* Mount path. */
 	uint64_t fsflags,		/* Flags common to all filesystems. */
+	bool only_export,		/* Got export option. */
 	bool jail_export,		/* Got export option in vnet prison. */
 	struct vfsoptlist **optlist	/* Options local to the filesystem. */
 	)
@@ -1693,8 +1703,8 @@ vfs_domount(
 		}
 		free(pathbuf, M_TEMP);
 	} else
-		error = vfs_domount_update(td, vp, fsflags, jail_export,
-		    optlist);
+		error = vfs_domount_update(td, vp, fsflags, only_export,
+		    jail_export, optlist);
 
 out:
 	NDFREE_PNBUF(&nd);