Always take the hash list lock before removing a node from the hash chain.

Release the hash list lock before calling getnewvnode() and check the
hash list again like other file systems do.

Take v_interlock before calling vget().
This commit is contained in:
hannken 2010-07-16 08:23:28 +00:00
parent a055a1e00a
commit 438de34ae8
1 changed files with 39 additions and 16 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: union_subr.c,v 1.37 2010/06/24 13:03:11 hannken Exp $ */
/* $NetBSD: union_subr.c,v 1.38 2010/07/16 08:23:28 hannken Exp $ */
/*
* Copyright (c) 1994
@ -72,7 +72,7 @@
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: union_subr.c,v 1.37 2010/06/24 13:03:11 hannken Exp $");
__KERNEL_RCSID(0, "$NetBSD: union_subr.c,v 1.38 2010/07/16 08:23:28 hannken Exp $");
#include <sys/param.h>
#include <sys/systm.h>
@ -330,8 +330,8 @@ union_allocvp(
{
int error;
struct vattr va;
struct union_node *un = NULL;
struct vnode *xlowervp = NULLVP;
struct union_node *un = NULL, *un1;
struct vnode *vp, *xlowervp = NULLVP;
struct union_mount *um = MOUNTTOUNIONMOUNT(mp);
voff_t uppersz, lowersz;
int hash = 0;
@ -394,7 +394,9 @@ loop:
(un->un_uppervp == uppervp ||
un->un_uppervp == NULLVP) &&
(UNIONTOV(un)->v_mount == mp)) {
if (vget(UNIONTOV(un), 0)) {
vp = UNIONTOV(un);
mutex_enter(&vp->v_interlock);
if (vget(vp, LK_INTERLOCK)) {
union_list_unlock(hash);
goto loop;
}
@ -502,18 +504,8 @@ loop:
if (lowervp != NULLVP)
if (VOP_GETATTR(lowervp, &va, FSCRED) == 0)
lowersz = va.va_size;
if (docache) {
/*
* otherwise lock the vp list while we call getnewvnode
* since that can block.
*/
hash = UNION_HASH(uppervp, lowervp);
if (union_list_lock(hash))
goto loop;
}
error = getnewvnode(VT_UNION, mp, union_vnodeop_p, vpp);
if (error) {
if (uppervp) {
@ -528,6 +520,24 @@ loop:
goto out;
}
if (docache) {
while (union_list_lock(hash))
continue;
LIST_FOREACH(un1, &unhead[hash], un_cache) {
if (un1->un_lowervp == lowervp &&
un1->un_uppervp == uppervp &&
UNIONTOV(un1)->v_mount == mp) {
/*
* Another thread beat us, push back freshly
* allocated vnode and retry.
*/
union_list_unlock(hash);
ungetnewvnode(*vpp);
goto loop;
}
}
}
(*vpp)->v_data = malloc(sizeof(struct union_node), M_TEMP, M_WAITOK);
(*vpp)->v_vflag |= vflag;
@ -590,12 +600,18 @@ out:
int
union_freevp(struct vnode *vp)
{
int hash;
struct union_node *un = VTOUNION(vp);
hash = UNION_HASH(un->un_uppervp, un->un_lowervp);
while (union_list_lock(hash))
continue;
if (un->un_flags & UN_CACHED) {
un->un_flags &= ~UN_CACHED;
LIST_REMOVE(un, un_cache);
}
union_list_unlock(hash);
if (un->un_pvp != NULLVP)
vrele(un->un_pvp);
@ -998,6 +1014,8 @@ union_vn_close(struct vnode *vp, int fmode, kauth_cred_t cred, struct lwp *l)
void
union_removed_upper(struct union_node *un)
{
int hash;
#if 1
/*
* We do not set the uppervp to NULLVP here, because lowervp
@ -1013,10 +1031,15 @@ union_removed_upper(struct union_node *un)
union_newupper(un, NULLVP);
#endif
hash = UNION_HASH(un->un_uppervp, un->un_lowervp);
while (union_list_lock(hash))
continue;
if (un->un_flags & UN_CACHED) {
un->un_flags &= ~UN_CACHED;
LIST_REMOVE(un, un_cache);
}
union_list_unlock(hash);
if (un->un_flags & UN_ULOCK) {
un->un_flags &= ~UN_ULOCK;