git: be9295deedd1 - main - VOP_VPUT_PAIR(): handle the case when dvp == vp

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Fri, 26 Jun 2026 21:45:48 UTC
The branch main has been updated by kib:

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

commit be9295deedd1e837ee3645583d07e1dffbe1ead0
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2026-06-24 18:21:49 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-06-26 21:45:30 +0000

    VOP_VPUT_PAIR(): handle the case when dvp == vp
    
    Reviewed by:    jah, markj
    Sponsored by:   The FreeBSD Foundation
    MFC after:      1 week
    Differential revision:  https://reviews.freebsd.org/D57824
---
 sys/kern/vfs_default.c  | 28 +++++++++++++++++++++++-----
 sys/sys/vnode.h         |  1 +
 sys/ufs/ffs/ffs_vnops.c | 18 +++++++++++-------
 3 files changed, 35 insertions(+), 12 deletions(-)

diff --git a/sys/kern/vfs_default.c b/sys/kern/vfs_default.c
index 3151c69d1912..5d6203570a79 100644
--- a/sys/kern/vfs_default.c
+++ b/sys/kern/vfs_default.c
@@ -81,7 +81,6 @@ static int vop_stdfdatasync(struct vop_fdatasync_args *ap);
 static int vop_stdgetpages_async(struct vop_getpages_async_args *ap);
 static int vop_stdread_pgcache(struct vop_read_pgcache_args *ap);
 static int vop_stdstat(struct vop_stat_args *ap);
-static int vop_stdvput_pair(struct vop_vput_pair_args *ap);
 static int vop_stdgetlowvnode(struct vop_getlowvnode_args *ap);
 
 /*
@@ -1623,16 +1622,35 @@ vop_stdread_pgcache(struct vop_read_pgcache_args *ap __unused)
 	return (EJUSTRETURN);
 }
 
-static int
+/*
+ * vput(dvp).  If unlock_vp is true, vput(vp).  Both dvp and vp must
+ * be locked.  This is VOP because some filesystems might need to do
+ * additional actions on dvp when unlocked, and need to be aware of
+ * any other locked vnodes.  Also as the consequence, vp might become
+ * doomed even if unlock_vp is false.
+ *
+ * Special case: dvp == vp.  If unlock_vp is true, vunref(vp);
+ * vput(vp), else vunref(vp).  In other words, the VOP assumes that
+ * there are two references on the vnode on entry, and releases only
+ * one of them in case of !unlock_vp.
+ */
+int
 vop_stdvput_pair(struct vop_vput_pair_args *ap)
 {
 	struct vnode *dvp, *vp, **vpp;
 
 	dvp = ap->a_dvp;
 	vpp = ap->a_vpp;
-	vput(dvp);
-	if (vpp != NULL && ap->a_unlock_vp && (vp = *vpp) != NULL)
-		vput(vp);
+	vp = vpp != NULL ? *vpp : NULL;
+	if (dvp != vp) {
+		vput(dvp);
+		if (vp != NULL && ap->a_unlock_vp)
+			vput(vp);
+	} else {
+		vunref(vp);
+		if (ap->a_unlock_vp)
+			vput(vp);
+	}
 	return (0);
 }
 
diff --git a/sys/sys/vnode.h b/sys/sys/vnode.h
index 8077e7741ec3..9d17dde1e73d 100644
--- a/sys/sys/vnode.h
+++ b/sys/sys/vnode.h
@@ -907,6 +907,7 @@ int	vop_stdpathconf(struct vop_pathconf_args *);
 int	vop_stdpoll(struct vop_poll_args *);
 int	vop_stdvptocnp(struct vop_vptocnp_args *ap);
 int	vop_stdvptofh(struct vop_vptofh_args *ap);
+int	vop_stdvput_pair(struct vop_vput_pair_args *ap);
 int	vop_stdunp_bind(struct vop_unp_bind_args *ap);
 int	vop_stdunp_connect(struct vop_unp_connect_args *ap);
 int	vop_stdunp_detach(struct vop_unp_detach_args *ap);
diff --git a/sys/ufs/ffs/ffs_vnops.c b/sys/ufs/ffs/ffs_vnops.c
index c7e2b3f4b8e6..ee495fe3888c 100644
--- a/sys/ufs/ffs/ffs_vnops.c
+++ b/sys/ufs/ffs/ffs_vnops.c
@@ -2006,16 +2006,14 @@ ffs_vput_pair(struct vop_vput_pair_args *ap)
 	vpp = ap->a_vpp;
 	vp = vpp != NULL ? *vpp : NULL;
 
-	if ((dp->i_flag & (IN_NEEDSYNC | IN_ENDOFF)) == 0) {
-		vput(dvp);
-		if (vp != NULL && ap->a_unlock_vp)
-			vput(vp);
-		return (0);
-	}
+	if ((dp->i_flag & (IN_NEEDSYNC | IN_ENDOFF)) == 0)
+		return (vop_stdvput_pair(ap));
 
 	mp = dvp->v_mount;
 	if (vp != NULL) {
-		if (ap->a_unlock_vp) {
+		if (dvp == vp) {
+			vunref(vp);
+		} else if (ap->a_unlock_vp) {
 			vput(vp);
 		} else {
 			MPASS(vp->v_type != VNON);
@@ -2055,6 +2053,12 @@ ffs_vput_pair(struct vop_vput_pair_args *ap)
 		} while (error == ERELOOKUP);
 	}
 
+	if (vp == dvp) {
+		if (ap->a_unlock_vp)
+			vput(dvp);
+		return (0);
+	}
+
 	vput(dvp);
 
 	if (vp == NULL || ap->a_unlock_vp)