git: 8db679af66b0 - main - UFS: make mkdir() and link() reliable when using SU and reaching nlink limit

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Wed, 22 Jun 2022 12:36:02 UTC
The branch main has been updated by kib:

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

commit 8db679af66b023802139d41e275e41a77da1c515
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2022-06-18 10:59:31 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2022-06-22 12:35:47 +0000

    UFS: make mkdir() and link() reliable when using SU and reaching nlink limit
    
    i_nlink overflow might be transient, i_effnlink indicates the final
    value of the link count after all dependencies would be resolved. So if
    i_nlink reached the maximum but i_efflink did not, we should be able to
    make the link by syncing.
    
    We must sync the whole filesystem to resolve dependencies,
    which requires unlocking vnodes locked for VOPs.  Use existing
    ERELOOKUP/VOP_UNLOCK_PAIR() mechanism to restart the VOP if sync with
    unlock was done.
    
    PR:     165392
    Reported by:    Vsevolod Volkov <vvv@colocall.net>
    Reviewed by:    mckusick
    Tested by:      pho
    Sponsored by:   The FreeBSD Foundation
    MFC after:      1 week
    Differential revision:  https://reviews.freebsd.org/D35514
---
 sys/ufs/ufs/ufs_vnops.c | 42 +++++++++++++++++++++++++++++++++++-------
 1 file changed, 35 insertions(+), 7 deletions(-)

diff --git a/sys/ufs/ufs/ufs_vnops.c b/sys/ufs/ufs/ufs_vnops.c
index 13116e8224f3..2def837c157a 100644
--- a/sys/ufs/ufs/ufs_vnops.c
+++ b/sys/ufs/ufs/ufs_vnops.c
@@ -196,6 +196,35 @@ ufs_itimes(struct vnode *vp)
 	VI_UNLOCK(vp);
 }
 
+static int
+ufs_sync_nlink(struct vnode *vp, struct vnode *vp1)
+{
+	struct inode *ip;
+	struct mount *mp;
+	int error;
+
+	ip = VTOI(vp);
+	if (ip->i_nlink < UFS_LINK_MAX)
+		return (0);
+	if (!DOINGSOFTDEP(vp) || ip->i_effnlink >= UFS_LINK_MAX)
+		return (EMLINK);
+
+	mp = vp->v_mount;
+	vfs_ref(mp);
+	VOP_UNLOCK(vp);
+	if (vp1 != NULL)
+		VOP_UNLOCK(vp1);
+	error = vfs_busy(mp, 0);
+	if (error == 0) {
+		VFS_SYNC(mp, MNT_WAIT);
+		vfs_unbusy(mp);
+		error = ERELOOKUP;
+	}
+	vfs_rel(mp);
+	vn_lock_pair(vp, false, vp1, false);
+	return (error);
+}
+
 /*
  * Create a regular file
  */
@@ -1086,11 +1115,11 @@ ufs_link(ap)
 		error = EINVAL;
 		goto out;
 	}
-	ip = VTOI(vp);
-	if (ip->i_nlink >= UFS_LINK_MAX) {
-		error = EMLINK;
+	error = ufs_sync_nlink(vp, tdvp);
+	if (error != 0)
 		goto out;
-	}
+	ip = VTOI(vp);
+
 	/*
 	 * The file may have been removed after namei dropped the original
 	 * lock.
@@ -1950,10 +1979,9 @@ ufs_mkdir(ap)
 		panic("ufs_mkdir: no name");
 #endif
 	dp = VTOI(dvp);
-	if (dp->i_nlink >= UFS_LINK_MAX) {
-		error = EMLINK;
+	error = ufs_sync_nlink(dvp, NULL);
+	if (error != 0)
 		goto out;
-	}
 	dmode = vap->va_mode & 0777;
 	dmode |= IFDIR;