Index: sys/fs/unionfs/union.h =================================================================== --- sys/fs/unionfs/union.h +++ sys/fs/unionfs/union.h @@ -130,9 +130,8 @@ int unionfs_init(struct vfsconf *); int unionfs_uninit(struct vfsconf *); int unionfs_nodeget(struct mount *, struct vnode *, struct vnode *, - struct vnode *, struct vnode **, struct componentname *, - struct thread *); -void unionfs_noderem(struct vnode *, struct thread *); + struct vnode *, struct vnode **, struct componentname *); +void unionfs_noderem(struct vnode *); void unionfs_get_node_status(struct unionfs_node *, struct thread *, struct unionfs_node_status **); void unionfs_tryrem_node_status(struct unionfs_node *, Index: sys/fs/unionfs/union_subr.c =================================================================== --- sys/fs/unionfs/union_subr.c +++ sys/fs/unionfs/union_subr.c @@ -299,7 +299,7 @@ int unionfs_nodeget(struct mount *mp, struct vnode *uppervp, struct vnode *lowervp, struct vnode *dvp, struct vnode **vpp, - struct componentname *cnp, struct thread *td) + struct componentname *cnp) { char *path; struct unionfs_mount *ump; @@ -426,7 +426,7 @@ * Clean up the unionfs node. */ void -unionfs_noderem(struct vnode *vp, struct thread *td) +unionfs_noderem(struct vnode *vp) { struct unionfs_node *unp, *unp_t1, *unp_t2; struct unionfs_node_hashhead *hd; Index: sys/fs/unionfs/union_vfsops.c =================================================================== --- sys/fs/unionfs/union_vfsops.c +++ sys/fs/unionfs/union_vfsops.c @@ -78,7 +78,6 @@ struct vnode *lowerrootvp; struct vnode *upperrootvp; struct unionfs_mount *ump; - struct thread *td; char *target; char *tmp; char *ep; @@ -105,7 +104,6 @@ copymode = UNIONFS_TRANSPARENT; /* default */ whitemode = UNIONFS_WHITE_ALWAYS; ndp = &nd; - td = curthread; if (mp->mnt_flag & MNT_ROOTFS) { vfs_mount_error(mp, "Cannot union mount root filesystem"); @@ -284,7 +282,7 @@ * Get the unionfs root vnode. */ error = unionfs_nodeget(mp, ump->um_uppervp, ump->um_lowervp, - NULLVP, &(ump->um_rootvp), NULL, td); + NULLVP, &(ump->um_rootvp), NULL); vrele(upperrootvp); if (error != 0) { free(ump, M_UNIONFSMNT); Index: sys/fs/unionfs/union_vnops.c =================================================================== --- sys/fs/unionfs/union_vnops.c +++ sys/fs/unionfs/union_vnops.c @@ -263,7 +263,7 @@ (1 < cnp->cn_namelen || '.' != *(cnp->cn_nameptr))) { /* get unionfs vnode in order to create a new shadow dir. */ error = unionfs_nodeget(dvp->v_mount, NULLVP, lvp, dvp, &vp, - cnp, td); + cnp); if (error != 0) goto unionfs_lookup_cleanup; @@ -319,7 +319,7 @@ */ else error = unionfs_nodeget(dvp->v_mount, uvp, lvp, - dvp, &vp, cnp, td); + dvp, &vp, cnp); if (error != 0) { UNIONFSDEBUG( "unionfs_lookup: Unable to create unionfs vnode."); @@ -383,7 +383,7 @@ else { VOP_UNLOCK(vp); error = unionfs_nodeget(ap->a_dvp->v_mount, vp, NULLVP, - ap->a_dvp, ap->a_vpp, cnp, curthread); + ap->a_dvp, ap->a_vpp, cnp); vrele(vp); } } @@ -457,7 +457,7 @@ else { VOP_UNLOCK(vp); error = unionfs_nodeget(ap->a_dvp->v_mount, vp, NULLVP, - ap->a_dvp, ap->a_vpp, cnp, curthread); + ap->a_dvp, ap->a_vpp, cnp); vrele(vp); } } @@ -631,7 +631,6 @@ unionfs_check_corrected_access(accmode_t accmode, struct vattr *va, struct ucred *cred) { - int count; uid_t uid; /* upper side vnode's uid */ gid_t gid; /* upper side vnode's gid */ u_short vmode; /* upper side vnode's mode */ @@ -654,7 +653,6 @@ } /* check group */ - count = 0; if (groupmember(gid, cred)) { if (accmode & VEXEC) mask |= S_IXGRP; @@ -1349,7 +1347,6 @@ { struct unionfs_node *dunp; struct componentname *cnp; - struct thread *td; struct vnode *udvp; struct vnode *uvp; struct vattr va; @@ -1364,7 +1361,6 @@ dunp = VTOUNIONFS(ap->a_dvp); cnp = ap->a_cnp; lkflags = cnp->cn_lkflags; - td = curthread; udvp = dunp->un_uppervp; if (udvp != NULLVP) { @@ -1381,7 +1377,7 @@ 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, td); + ap->a_dvp, ap->a_vpp, cnp); cnp->cn_lkflags = lkflags; vrele(uvp); } @@ -1469,7 +1465,6 @@ { struct unionfs_node *dunp; struct componentname *cnp; - struct thread *td; struct vnode *udvp; struct vnode *uvp; int error; @@ -1483,7 +1478,6 @@ dunp = VTOUNIONFS(ap->a_dvp); cnp = ap->a_cnp; lkflags = cnp->cn_lkflags; - td = curthread; udvp = dunp->un_uppervp; if (udvp != NULLVP) { @@ -1492,7 +1486,7 @@ 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, td); + ap->a_dvp, ap->a_vpp, cnp); cnp->cn_lkflags = lkflags; vrele(uvp); } @@ -1763,7 +1757,7 @@ { /* UNIONFS_INTERNAL_DEBUG("unionfs_reclaim: enter\n"); */ - unionfs_noderem(ap->a_vp, curthread); + unionfs_noderem(ap->a_vp); /* UNIONFS_INTERNAL_DEBUG("unionfs_reclaim: leave\n"); */ @@ -1874,6 +1868,16 @@ KASSERT_UNIONFS_VNODE(ap->a_vp); + /* + * TODO: rework the unionfs locking scheme. + * It's not guaranteed to be safe to blindly lock two vnodes on + * different mounts as is done here. Further, the entanglement + * of locking both vnodes with the various options that can be + * passed to VOP_LOCK() makes this code hard to reason about. + * Instead, consider locking only the upper vnode, or the lower + * vnode is the upper is not present, and taking separate measures + * to lock both vnodes in the few cases when that is needed. + */ error = 0; interlock = 1; uhold = 0; @@ -2541,6 +2545,128 @@ return (error); } +static int +unionfs_vput_pair(struct vop_vput_pair_args *ap) +{ + struct mount *mp; + struct vnode *dvp, *vp, **vpp, *lvp, *ldvp, *uvp, *udvp, *tempvp; + struct unionfs_node *dunp, *unp; + int error, res; + + dvp = ap->a_dvp; + vpp = ap->a_vpp; + vp = NULLVP; + lvp = NULLVP; + uvp = NULLVP; + unp = NULL; + + dunp = VTOUNIONFS(dvp); + udvp = dunp->un_uppervp; + ldvp = dunp->un_lowervp; + + /* + * Underlying vnodes should be locked because the encompassing unionfs + * node is locked, but will not be referenced, as the reference will + * only be on the unionfs node. Reference them now so that the vput()s + * performed by VOP_VPUT_PAIR() will have a reference to drop. + */ + if (udvp != NULLVP) + vref(udvp); + if (ldvp != NULLVP) + vref(ldvp); + + if (vpp != NULL) + vp = *vpp; + + if (vp != NULLVP) { + unp = VTOUNIONFS(vp); + uvp = unp->un_uppervp; + lvp = unp->un_lowervp; + if (uvp != NULLVP) + vref(uvp); + if (lvp != NULLVP) + vref(lvp); + + /* + * If we're being asked to return a locked child vnode, then + * we may need to create a replacement vnode in case the + * original is reclaimed while the lock is dropped. In that + * case we'll need to ensure the mount and the underlying + * vnodes aren't also recycled during that window. + */ + if (!ap->a_unlock_vp) { + vhold(vp); + if (uvp != NULLVP) + vhold(uvp); + if (lvp != NULLVP) + vhold(lvp); + mp = vp->v_mount; + vfs_ref(mp); + } + } + + /* + * TODO: Because unionfs_lock() locks both the lower and upper vnodes + * (if available), we must also call VOP_VPUT_PAIR() on both the lower + * and upper parent/child pairs. If unionfs_lock() is reworked to lock + * only a single vnode, this code will need to change to also only + * operate on one vnode pair. + */ + ASSERT_VOP_LOCKED(ldvp, __func__); + ASSERT_VOP_LOCKED(udvp, __func__); + ASSERT_VOP_LOCKED(lvp, __func__); + ASSERT_VOP_LOCKED(uvp, __func__); + + KASSERT(lvp == NULLVP || ldvp != NULLVP, + ("%s: NULL ldvp with non-NULL lvp", __func__)); + if (ldvp != NULLVP) + res = VOP_VPUT_PAIR(ldvp, lvp != NULLVP ? &lvp : NULL, true); + KASSERT(uvp == NULLVP || udvp != NULLVP, + ("%s: NULL udvp with non-NULL uvp", __func__)); + if (udvp != NULLVP) + res = VOP_VPUT_PAIR(udvp, uvp != NULLVP ? &uvp : NULL, true); + + ASSERT_VOP_UNLOCKED(ldvp, __func__); + ASSERT_VOP_UNLOCKED(udvp, __func__); + ASSERT_VOP_UNLOCKED(lvp, __func__); + ASSERT_VOP_UNLOCKED(uvp, __func__); + + /* + * VOP_VPUT_PAIR() dropped the references we added to the underlying + * vnodes, now drop the caller's reference to the unionfs vnodes. + */ + if (vp != NULLVP && ap->a_unlock_vp) + vrele(vp); + vrele(dvp); + + if (vp == NULLVP || ap->a_unlock_vp) + return (res); + + /* + * We're being asked to return a locked vnode. At this point, the + * underlying vnodes have been unlocked, so vp may have been reclaimed. + */ + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + if (vp->v_data == NULL && vfs_busy(mp, MBF_NOWAIT) == 0) { + vput(vp); + error = unionfs_nodeget(mp, uvp, lvp, dvp, &tempvp, NULL); + if (error == 0) { + vn_lock(tempvp, LK_EXCLUSIVE | LK_RETRY); + *vpp = tempvp; + } else + vget(vp, LK_EXCLUSIVE | LK_RETRY); + vfs_unbusy(mp); + } + if (lvp != NULLVP) + vdrop(lvp); + if (uvp != NULLVP) + vdrop(uvp); + vdrop(vp); + vfs_rel(mp); + + return (res); +} + struct vop_vector unionfs_vnodeops = { .vop_default = &default_vnodeops, @@ -2591,5 +2717,6 @@ .vop_write = unionfs_write, .vop_vptofh = unionfs_vptofh, .vop_add_writecount = unionfs_add_writecount, + .vop_vput_pair = unionfs_vput_pair, }; VFS_VOP_VECTOR_REGISTER(unionfs_vnodeops);