unionfs_mkdir(): handle dvp reclamation

The underlying VOP_MKDIR() implementation may temporarily drop the
parent directory vnode's lock.  If the vnode is reclaimed during that
window, the unionfs vnode will effectively become unlocked because
the its v_vnlock field will be reset.  To uphold the locking
requirements of VOP_MKDIR() and to avoid triggering various VFS
assertions, explicitly re-lock the unionfs vnode before returning
in this case.

Note that there are almost certainly other cases in which we'll
similarly need to handle vnode relocking by the underlying FS; this
is the only one that's caused problems in stress testing so far.
A more general solution, such as that employed for nullfs in
null_bypass(), will likely need to be implemented.

Tested by:	pho
Reviewed by:	kib, markj
Differential Revision: https://reviews.freebsd.org/D39272
This commit is contained in:
Jason A. Harmening 2023-01-16 15:50:59 -06:00
parent d711884e60
commit 93fe61afde

View File

@ -1389,6 +1389,7 @@ unionfs_mkdir(struct vop_mkdir_args *ap)
{
struct unionfs_node *dunp;
struct componentname *cnp;
struct vnode *dvp;
struct vnode *udvp;
struct vnode *uvp;
struct vattr va;
@ -1400,17 +1401,19 @@ unionfs_mkdir(struct vop_mkdir_args *ap)
KASSERT_UNIONFS_VNODE(ap->a_dvp);
error = EROFS;
dunp = VTOUNIONFS(ap->a_dvp);
dvp = ap->a_dvp;
dunp = VTOUNIONFS(dvp);
cnp = ap->a_cnp;
lkflags = cnp->cn_lkflags;
udvp = dunp->un_uppervp;
if (udvp != NULLVP) {
vref(udvp);
/* check opaque */
if (!(cnp->cn_flags & ISWHITEOUT)) {
error = VOP_GETATTR(udvp, &va, cnp->cn_cred);
if (error != 0)
return (error);
goto unionfs_mkdir_cleanup;
if ((va.va_flags & OPAQUE) != 0)
cnp->cn_flags |= ISWHITEOUT;
}
@ -1418,13 +1421,35 @@ unionfs_mkdir(struct vop_mkdir_args *ap)
if ((error = VOP_MKDIR(udvp, &uvp, cnp, ap->a_vap)) == 0) {
VOP_UNLOCK(uvp);
cnp->cn_lkflags = LK_EXCLUSIVE;
error = unionfs_nodeget(ap->a_dvp->v_mount, uvp, NULLVP,
ap->a_dvp, ap->a_vpp, cnp);
/*
* The underlying VOP_MKDIR() implementation may have
* temporarily dropped the parent directory vnode lock.
* Because the unionfs vnode ordinarily shares that
* lock, this may allow the unionfs vnode to be reclaimed
* and its lock field reset. In that case, the unionfs
* vnode is effectively no longer locked, and we must
* explicitly lock it before returning in order to meet
* the locking requirements of VOP_MKDIR().
*/
if (__predict_false(VTOUNIONFS(dvp) == NULL)) {
error = ENOENT;
goto unionfs_mkdir_cleanup;
}
error = unionfs_nodeget(dvp->v_mount, uvp, NULLVP,
dvp, ap->a_vpp, cnp);
cnp->cn_lkflags = lkflags;
vrele(uvp);
}
}
unionfs_mkdir_cleanup:
if (__predict_false(VTOUNIONFS(dvp) == NULL)) {
vput(udvp);
vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY);
} else if (udvp != NULLVP)
vrele(udvp);
UNIONFS_INTERNAL_DEBUG("unionfs_mkdir: leave (%d)\n", error);
return (error);