summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sys/fs/nullfs/null.h2
-rw-r--r--sys/fs/nullfs/null_subr.c80
-rw-r--r--sys/fs/nullfs/null_vnops.c171
3 files changed, 63 insertions, 190 deletions
diff --git a/sys/fs/nullfs/null.h b/sys/fs/nullfs/null.h
index b6cc7e3..c8fc5de 100644
--- a/sys/fs/nullfs/null.h
+++ b/sys/fs/nullfs/null.h
@@ -47,8 +47,6 @@ struct null_node {
LIST_ENTRY(null_node) null_hash; /* Hash list */
struct vnode *null_lowervp; /* VREFed once */
struct vnode *null_vnode; /* Back pointer */
- int null_pending_locks;
- int null_drain_wakeup;
};
#define MOUNTTONULLMOUNT(mp) ((struct null_mount *)((mp)->mnt_data))
diff --git a/sys/fs/nullfs/null_subr.c b/sys/fs/nullfs/null_subr.c
index f6543b2..e665675 100644
--- a/sys/fs/nullfs/null_subr.c
+++ b/sys/fs/nullfs/null_subr.c
@@ -120,27 +120,20 @@ loop:
LIST_FOREACH(a, hd, null_hash) {
if (a->null_lowervp == lowervp && NULLTOV(a)->v_mount == mp) {
vp = NULLTOV(a);
- mtx_lock(&vp->v_interlock);
+ VI_LOCK(vp);
/*
- * Don't block if nullfs vnode is being recycled.
- * We already hold a lock on the lower vnode, thus
- * waiting might deadlock against the thread
- * recycling the nullfs vnode or another thread
- * in vrele() waiting for the vnode lock.
+ * If the nullfs node is being recycled we have
+ * to wait until it finishes prior to scanning
+ * again.
*/
- if ((vp->v_iflag & VI_DOOMED) != 0) {
- VI_UNLOCK(vp);
- continue;
- }
mtx_unlock(&null_hashmtx);
- /*
- * We need vget for the VXLOCK
- * stuff, but we don't want to lock
- * the lower node.
- */
- if (vget(vp, LK_EXCLUSIVE | LK_THISLAYER | LK_INTERLOCK, td))
+ if ((vp->v_iflag & VI_DOOMED) != 0) {
+ /* Wait for recycling to finish. */
+ VOP_LOCK(vp, LK_EXCLUSIVE|LK_INTERLOCK, td);
+ VOP_UNLOCK(vp, 0, td);
goto loop;
-
+ }
+ vget(vp, LK_INTERLOCK, td);
return (vp);
}
}
@@ -169,22 +162,19 @@ loop:
if (oxp->null_lowervp == xp->null_lowervp &&
NULLTOV(oxp)->v_mount == mp) {
ovp = NULLTOV(oxp);
- mtx_lock(&ovp->v_interlock);
+ VI_LOCK(ovp);
/*
- * Don't block if nullfs vnode is being recycled.
- * We already hold a lock on the lower vnode, thus
- * waiting might deadlock against the thread
- * recycling the nullfs vnode or another thread
- * in vrele() waiting for the vnode lock.
+ * If the nullfs node is being recycled we have
+ * to wait until it finishes prior to scanning
+ * again.
*/
- if ((ovp->v_iflag & VI_DOOMED) != 0) {
- VI_UNLOCK(ovp);
- continue;
- }
mtx_unlock(&null_hashmtx);
- if (vget(ovp, LK_EXCLUSIVE | LK_THISLAYER | LK_INTERLOCK, td))
+ if ((ovp->v_iflag & VI_DOOMED) != 0) {
+ VOP_LOCK(ovp, LK_EXCLUSIVE|LK_INTERLOCK, td);
+ VOP_UNLOCK(ovp, 0, td);
goto loop;
-
+ }
+ vget(ovp, LK_INTERLOCK, td);
return (ovp);
}
}
@@ -207,7 +197,6 @@ null_nodeget(mp, lowervp, vpp)
struct vnode *lowervp;
struct vnode **vpp;
{
- struct thread *td = curthread; /* XXX */
struct null_node *xp;
struct vnode *vp;
int error;
@@ -243,26 +232,11 @@ null_nodeget(mp, lowervp, vpp)
xp->null_vnode = vp;
xp->null_lowervp = lowervp;
- xp->null_pending_locks = 0;
- xp->null_drain_wakeup = 0;
-
vp->v_type = lowervp->v_type;
vp->v_data = xp;
-
- /*
- * From NetBSD:
- * Now lock the new node. We rely on the fact that we were passed
- * a locked vnode. If the lower node is exporting a struct lock
- * (v_vnlock != NULL) then we just set the upper v_vnlock to the
- * lower one, and both are now locked. If the lower node is exporting
- * NULL, then we copy that up and manually lock the new vnode.
- */
-
vp->v_vnlock = lowervp->v_vnlock;
- error = VOP_LOCK(vp, LK_EXCLUSIVE | LK_THISLAYER, td);
- if (error)
- panic("null_nodeget: can't lock new vnode\n");
-
+ if (vp->v_vnlock == NULL)
+ panic("null_nodeget: Passed a NULL vnlock.\n");
/*
* Atomically insert our new node into the hash or vget existing
* if someone else has beaten us to it.
@@ -270,21 +244,11 @@ null_nodeget(mp, lowervp, vpp)
*vpp = null_hashins(mp, xp);
if (*vpp != NULL) {
vrele(lowervp);
- VOP_UNLOCK(vp, LK_THISLAYER, td);
- vp->v_vnlock = NULL;
+ vp->v_vnlock = &vp->v_lock;
xp->null_lowervp = NULL;
vrele(vp);
return (0);
}
-
- /*
- * XXX We take extra vref just to workaround UFS's XXX:
- * UFS can vrele() vnode in VOP_CLOSE() in some cases. Luckily, this
- * can only happen if v_usecount == 1. To workaround, we just don't
- * let v_usecount be 1, it will be 2 or more.
- */
- VREF(lowervp);
-
*vpp = vp;
return (0);
diff --git a/sys/fs/nullfs/null_vnops.c b/sys/fs/nullfs/null_vnops.c
index 19e88b5..3ab3a6f 100644
--- a/sys/fs/nullfs/null_vnops.c
+++ b/sys/fs/nullfs/null_vnops.c
@@ -296,7 +296,7 @@ null_bypass(struct vop_generic_args *ap)
*(vps_p[i]) = old_vps[i];
#if 0
if (reles & VDESC_VP0_WILLUNLOCK)
- VOP_UNLOCK(*(vps_p[i]), LK_THISLAYER, curthread);
+ VOP_UNLOCK(*(vps_p[i]), 0, curthread);
#endif
if (reles & VDESC_VP0_WILLRELE)
vrele(*(vps_p[i]));
@@ -339,7 +339,6 @@ null_lookup(struct vop_lookup_args *ap)
{
struct componentname *cnp = ap->a_cnp;
struct vnode *dvp = ap->a_dvp;
- struct thread *td = cnp->cn_thread;
int flags = cnp->cn_flags;
struct vnode *vp, *ldvp, *lvp;
int error;
@@ -359,12 +358,6 @@ null_lookup(struct vop_lookup_args *ap)
(cnp->cn_nameiop == CREATE || cnp->cn_nameiop == RENAME))
error = EROFS;
- /*
- * Rely only on the PDIRUNLOCK flag which should be carefully
- * tracked by underlying filesystem.
- */
- if ((cnp->cn_flags & PDIRUNLOCK) && dvp->v_vnlock != ldvp->v_vnlock)
- VOP_UNLOCK(dvp, LK_THISLAYER, td);
if ((error == 0 || error == EJUSTRETURN) && lvp != NULL) {
if (ldvp == lvp) {
*ap->a_vpp = dvp;
@@ -521,113 +514,42 @@ null_lock(struct vop_lock_args *ap)
struct vnode *vp = ap->a_vp;
int flags = ap->a_flags;
struct thread *td = ap->a_td;
+ struct null_node *nn;
struct vnode *lvp;
int error;
- struct null_node *nn;
- if (flags & LK_THISLAYER) {
- if (vp->v_vnlock != NULL) {
- /* lock is shared across layers */
- if (flags & LK_INTERLOCK)
- mtx_unlock(&vp->v_interlock);
- return 0;
- }
- error = lockmgr(&vp->v_lock, flags & ~LK_THISLAYER,
- &vp->v_interlock, td);
- return (error);
- }
if ((flags & LK_INTERLOCK) == 0) {
VI_LOCK(vp);
- flags |= LK_INTERLOCK;
+ ap->a_flags = flags |= LK_INTERLOCK;
}
- if (vp->v_vnlock != NULL) {
- /*
- * The lower level has exported a struct lock to us. Use
- * it so that all vnodes in the stack lock and unlock
- * simultaneously. Note: we don't DRAIN the lock as DRAIN
- * decommissions the lock - just because our vnode is
- * going away doesn't mean the struct lock below us is.
- * LK_EXCLUSIVE is fine.
- */
- nn = VTONULL(vp);
- if ((flags & LK_TYPE_MASK) == LK_DRAIN) {
- NULLFSDEBUG("null_lock: avoiding LK_DRAIN\n");
- /*
- * Emulate lock draining by waiting for all other
- * pending locks to complete. Afterwards the
- * lockmgr call might block, but no other threads
- * will attempt to use this nullfs vnode due to the
- * VI_DOOMED flag.
- */
- while (nn->null_pending_locks > 0) {
- nn->null_drain_wakeup = 1;
- msleep(&nn->null_pending_locks,
- VI_MTX(vp),
- PVFS,
- "nuldr", 0);
- }
- error = lockmgr(vp->v_vnlock,
- (flags & ~LK_TYPE_MASK) | LK_EXCLUSIVE,
- VI_MTX(vp), td);
- return error;
- }
- nn->null_pending_locks++;
- error = lockmgr(vp->v_vnlock, flags, &vp->v_interlock, td);
- VI_LOCK(vp);
- /*
- * If we're called from vrele then v_usecount can have been 0
- * and another process might have initiated a recycle
- * operation. When that happens, just back out.
- */
- if (error == 0 && (vp->v_iflag & VI_DOOMED) != 0 &&
- td != vp->v_vxthread) {
- lockmgr(vp->v_vnlock,
- (flags & ~LK_TYPE_MASK) | LK_RELEASE,
- VI_MTX(vp), td);
- VI_LOCK(vp);
- error = ENOENT;
- }
- nn->null_pending_locks--;
- /*
- * Wakeup the process draining the vnode after all
- * pending lock attempts has been failed.
- */
- if (nn->null_pending_locks == 0 &&
- nn->null_drain_wakeup != 0) {
- nn->null_drain_wakeup = 0;
- wakeup(&nn->null_pending_locks);
- }
+ nn = VTONULL(vp);
+ /*
+ * If we're still active we must ask the lower layer to
+ * lock as ffs has special lock considerations in it's
+ * vop lock.
+ */
+ if (nn != NULL && (lvp = NULLVPTOLOWERVP(vp)) != NULL) {
+ VI_LOCK(lvp);
VI_UNLOCK(vp);
- return error;
- } else {
/*
- * To prevent race conditions involving doing a lookup
- * on "..", we have to lock the lower node, then lock our
- * node. Most of the time it won't matter that we lock our
- * node (as any locking would need the lower one locked
- * first). But we can LK_DRAIN the upper lock as a step
- * towards decomissioning it.
+ * We have to hold the vnode here to solve a potential
+ * reclaim race. If we're forcibly vgone'd while we
+ * still have refs, a thread could be sleeping inside
+ * the lowervp's vop_lock routine. When we vgone we will
+ * drop our last ref to the lowervp, which would allow it
+ * to be reclaimed. The lowervp could then be recycled,
+ * in which case it is not legal to be sleeping in it's VOP.
+ * We prevent it from being recycled by holding the vnode
+ * here.
*/
- lvp = NULLVPTOLOWERVP(vp);
- if (lvp == NULL)
- return (lockmgr(&vp->v_lock, flags, &vp->v_interlock, td));
- if (flags & LK_INTERLOCK) {
- mtx_unlock(&vp->v_interlock);
- flags &= ~LK_INTERLOCK;
- }
- if ((flags & LK_TYPE_MASK) == LK_DRAIN) {
- error = VOP_LOCK(lvp,
- (flags & ~LK_TYPE_MASK) | LK_EXCLUSIVE, td);
- } else
- error = VOP_LOCK(lvp, flags, td);
- if (error)
- return (error);
- error = lockmgr(&vp->v_lock, flags, &vp->v_interlock, td);
- if (error)
- VOP_UNLOCK(lvp, 0, td);
- return (error);
- }
+ vholdl(lvp);
+ error = VOP_LOCK(lvp, flags, td);
+ vdrop(lvp);
+ } else
+ error = vop_stdlock(ap);
+
+ return (error);
}
/*
@@ -641,27 +563,21 @@ null_unlock(struct vop_unlock_args *ap)
struct vnode *vp = ap->a_vp;
int flags = ap->a_flags;
struct thread *td = ap->a_td;
+ struct null_node *nn;
struct vnode *lvp;
+ int error;
- if (vp->v_vnlock != NULL) {
- if (flags & LK_THISLAYER)
- return 0; /* the lock is shared across layers */
- flags &= ~LK_THISLAYER;
- return (lockmgr(vp->v_vnlock, flags | LK_RELEASE,
- &vp->v_interlock, td));
+ if ((flags & LK_INTERLOCK) != 0) {
+ VI_UNLOCK(vp);
+ ap->a_flags = flags &= ~LK_INTERLOCK;
}
- lvp = NULLVPTOLOWERVP(vp);
- if (lvp == NULL)
- return (lockmgr(&vp->v_lock, flags | LK_RELEASE, &vp->v_interlock, td));
- if ((flags & LK_THISLAYER) == 0) {
- if (flags & LK_INTERLOCK) {
- mtx_unlock(&vp->v_interlock);
- flags &= ~LK_INTERLOCK;
- }
- VOP_UNLOCK(lvp, flags & ~LK_INTERLOCK, td);
- } else
- flags &= ~LK_THISLAYER;
- return (lockmgr(&vp->v_lock, flags | LK_RELEASE, &vp->v_interlock, td));
+ nn = VTONULL(vp);
+ if (nn != NULL && (lvp = NULLVPTOLOWERVP(vp)) != NULL)
+ error = VOP_UNLOCK(lvp, flags, td);
+ else
+ error = vop_stdunlock(ap);
+
+ return (error);
}
static int
@@ -670,9 +586,7 @@ null_islocked(struct vop_islocked_args *ap)
struct vnode *vp = ap->a_vp;
struct thread *td = ap->a_td;
- if (vp->v_vnlock != NULL)
- return (lockstatus(vp->v_vnlock, td));
- return (lockstatus(&vp->v_lock, td));
+ return (lockstatus(vp->v_vnlock, td));
}
/*
@@ -715,17 +629,14 @@ null_reclaim(struct vop_reclaim_args *ap)
if (lowervp) {
null_hashrem(xp);
-
- vrele(lowervp);
vrele(lowervp);
}
vp->v_data = NULL;
vp->v_object = NULL;
vnlock = vp->v_vnlock;
- lockmgr(&vp->v_lock, LK_EXCLUSIVE, NULL, curthread);
vp->v_vnlock = &vp->v_lock;
- transferlockers(vnlock, vp->v_vnlock);
+ lockmgr(vp->v_vnlock, LK_EXCLUSIVE, NULL, curthread);
lockmgr(vnlock, LK_RELEASE, NULL, curthread);
FREE(xp, M_NULLFSNODE);
OpenPOWER on IntegriCloud