diff --git a/sys/vfs/ufs/ufs_quota.c b/sys/vfs/ufs/ufs_quota.c index 0000000..1111111 100644 --- a/sys/vfs/ufs/ufs_quota.c +++ b/sys/vfs/ufs/ufs_quota.c @@ -44,6 +44,7 @@ #include #include #include +#include #include "quota.h" #include "inode.h" @@ -732,6 +733,15 @@ static long ufs_numdquot, ufs_desireddquot = DQUOTINC; /* + * Serializes all mutation of the global dquot hash table and free list. + * The token is held only across list/hash manipulation (lookup, recycle, + * insert, remove) and is NOT held across sleeping operations (kmalloc + * M_WAITOK, vn_lock, VOP_READ/VOP_WRITE). See ufs_dqget/ufs_dqrele/ + * ufs_dqflush. + */ +static struct lwkt_token ufs_dq_token = LWKT_TOKEN_INITIALIZER(ufs_dq_token); + +/* * Initialize the quota system. */ void @@ -741,6 +751,7 @@ ufs_dqhashtbl = hashinit(hsize, M_DQUOT, &ufs_dqhash); TAILQ_INIT(&ufs_dqfreelist); + lwkt_token_init(&ufs_dq_token, "ufsdqlist"); } /* @@ -765,8 +776,14 @@ } /* * Check the cache first. + * + * The dquot token serializes all mutation of the global hash table and + * free list. It is held across the cache lookup and the miss-path + * alloc/recycle + hash insert, but released around sleeping operations + * (kmalloc M_WAITOK below, vn_lock + VOP_READ further down). */ dqh = DQHASH(dqvp, id); + lwkt_gettoken(&ufs_dq_token); LIST_FOREACH(dq, dqh, dq_hash) { if (dq->dq_id != id || dq->dq_ump->um_quotas[dq->dq_type] != dqvp) @@ -779,6 +796,7 @@ TAILQ_REMOVE(&ufs_dqfreelist, dq, dq_freelist); DQREF(dq); *dqp = dq; + lwkt_reltoken(&ufs_dq_token); return (0); } @@ -790,13 +808,20 @@ ufs_desireddquot += DQUOTINC; } if (ufs_numdquot < ufs_desireddquot) { + /* + * kmalloc(M_WAITOK) may sleep -> drop the token around it. + * ufs_numdquot is bumped under the token after the allocation. + */ + lwkt_reltoken(&ufs_dq_token); dq = (struct ufs_dquot *) kmalloc(sizeof *dq, M_DQUOT, M_WAITOK | M_ZERO); + lwkt_gettoken(&ufs_dq_token); ufs_numdquot++; } else { if ((dq = TAILQ_FIRST(&ufs_dqfreelist)) == NULL) { tablefull("dquot"); *dqp = NODQUOT; + lwkt_reltoken(&ufs_dq_token); return (EUSERS); } if (dq->dq_cnt || (dq->dq_flags & DQ_MOD)) @@ -806,16 +831,22 @@ LIST_REMOVE(dq, dq_hash); } /* - * Initialize the contents of the dquot structure. + * Initialize the contents of the dquot structure and publish it on + * the hash chain while still holding the token. */ - if (vp != dqvp) - vn_lock(dqvp, LK_EXCLUSIVE | LK_RETRY); LIST_INSERT_HEAD(dqh, dq, dq_hash); DQREF(dq); dq->dq_flags = DQ_LOCK; dq->dq_id = id; dq->dq_ump = ump; dq->dq_type = type; + lwkt_reltoken(&ufs_dq_token); + + /* + * Read the quota record. Performed outside the token (may sleep). + */ + if (vp != dqvp) + vn_lock(dqvp, LK_EXCLUSIVE | LK_RETRY); auio.uio_iov = &aiov; auio.uio_iovcnt = 1; aiov.iov_base = (caddr_t)&dq->dq_dqb; @@ -838,7 +869,9 @@ * quota structure and reflect problem to caller. */ if (error) { + lwkt_gettoken(&ufs_dq_token); LIST_REMOVE(dq, dq_hash); + lwkt_reltoken(&ufs_dq_token); ufs_dqrele(vp, dq); *dqp = NODQUOT; return (error); @@ -887,7 +920,9 @@ (void)ufs_dqsync(vp, dq); if (--dq->dq_cnt > 0) return; + lwkt_gettoken(&ufs_dq_token); TAILQ_INSERT_TAIL(&ufs_dqfreelist, dq, dq_freelist); + lwkt_reltoken(&ufs_dq_token); } /* @@ -953,6 +988,7 @@ * file off their hash chains (they will eventually * fall off the head of the free list and be re-used). */ + lwkt_gettoken(&ufs_dq_token); for (dqh = &ufs_dqhashtbl[ufs_dqhash]; dqh >= ufs_dqhashtbl; dqh--) { for (dq = dqh->lh_first; dq; dq = nextdq) { nextdq = dq->dq_hash.le_next; @@ -964,4 +1000,5 @@ dq->dq_ump = NULL; } } + lwkt_reltoken(&ufs_dq_token); }