1090 lines
26 KiB
C
1090 lines
26 KiB
C
/*
|
|
* Copyright (c) 2002-2006 Apple Computer, Inc. All rights reserved.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
|
|
*
|
|
* This file contains Original Code and/or Modifications of Original Code
|
|
* as defined in and that are subject to the Apple Public Source License
|
|
* Version 2.0 (the 'License'). You may not use this file except in
|
|
* compliance with the License. The rights granted to you under the License
|
|
* may not be used to create, or enable the creation or redistribution of,
|
|
* unlawful or unlicensed copies of an Apple operating system, or to
|
|
* circumvent, violate, or enable the circumvention or violation of, any
|
|
* terms of an Apple operating system software license agreement.
|
|
*
|
|
* Please obtain a copy of the License at
|
|
* http://www.opensource.apple.com/apsl/ and read it before using this file.
|
|
*
|
|
* The Original Code and all software distributed under the License are
|
|
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
|
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
|
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
|
* Please see the License for the specific language governing rights and
|
|
* limitations under the License.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
|
|
*/
|
|
/*
|
|
* Copyright (c) 1982, 1986, 1990, 1993, 1995
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to Berkeley by
|
|
* Robert Elz at The University of Melbourne.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the University of
|
|
* California, Berkeley and its contributors.
|
|
* 4. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* @(#)vfs_quota.c
|
|
* derived from @(#)ufs_quota.c 8.5 (Berkeley) 5/20/95
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/systm.h>
|
|
#include <kern/zalloc.h>
|
|
#include <sys/file_internal.h>
|
|
#include <sys/proc_internal.h>
|
|
#include <sys/vnode_internal.h>
|
|
#include <sys/mount_internal.h>
|
|
#include <sys/quota.h>
|
|
#include <sys/uio_internal.h>
|
|
|
|
#include <libkern/OSByteOrder.h>
|
|
|
|
|
|
/* vars for quota file lock */
|
|
static LCK_GRP_DECLARE(qf_lck_grp, "quota file");
|
|
|
|
/* vars for quota list lock */
|
|
static LCK_GRP_DECLARE(quota_list_lck_grp, "quuota list");
|
|
static LCK_MTX_DECLARE(quota_list_mtx_lock, "a_list_lck_grp);
|
|
|
|
/* Routines to lock and unlock the quota global data */
|
|
static int dq_list_lock(void);
|
|
static void dq_list_unlock(void);
|
|
|
|
static void dq_lock_internal(struct dquot *dq);
|
|
static void dq_unlock_internal(struct dquot *dq);
|
|
|
|
static u_int32_t quotamagic[MAXQUOTAS] = INITQMAGICS;
|
|
|
|
|
|
/*
|
|
* Code pertaining to management of the in-core dquot data structures.
|
|
*/
|
|
#define DQHASH(dqvp, id) \
|
|
(&dqhashtbl[((((intptr_t)(dqvp)) >> 8) + id) & dqhash])
|
|
LIST_HEAD(dqhash, dquot) * dqhashtbl;
|
|
u_long dqhash;
|
|
|
|
#define DQUOTINC 5 /* minimum free dquots desired */
|
|
long numdquot, desireddquot = DQUOTINC;
|
|
|
|
/*
|
|
* Dquot free list.
|
|
*/
|
|
TAILQ_HEAD(dqfreelist, dquot) dqfreelist;
|
|
/*
|
|
* Dquot dirty orphans list
|
|
*/
|
|
TAILQ_HEAD(dqdirtylist, dquot) dqdirtylist;
|
|
|
|
KALLOC_TYPE_DEFINE(KT_DQUOT, struct dquot, KT_PRIV_ACCT);
|
|
|
|
static int dqlookup(struct quotafile *, u_int32_t, struct dqblk *, u_int32_t *);
|
|
static int dqsync_locked(struct dquot *dq);
|
|
|
|
static void qf_lock(struct quotafile *);
|
|
static void qf_unlock(struct quotafile *);
|
|
static int qf_ref(struct quotafile *);
|
|
static void qf_rele(struct quotafile *);
|
|
|
|
|
|
/*
|
|
* Report whether dqhashinit has been run.
|
|
*/
|
|
int
|
|
dqisinitialized(void)
|
|
{
|
|
return dqhashtbl != NULL;
|
|
}
|
|
|
|
/*
|
|
* Initialize hash table for dquot structures.
|
|
*/
|
|
void
|
|
dqhashinit(void)
|
|
{
|
|
dq_list_lock();
|
|
if (dqisinitialized()) {
|
|
goto out;
|
|
}
|
|
|
|
TAILQ_INIT(&dqfreelist);
|
|
TAILQ_INIT(&dqdirtylist);
|
|
dqhashtbl = hashinit(desiredvnodes, M_DQUOT, &dqhash);
|
|
out:
|
|
dq_list_unlock();
|
|
}
|
|
|
|
|
|
static volatile int dq_list_lock_cnt = 0;
|
|
|
|
static int
|
|
dq_list_lock(void)
|
|
{
|
|
lck_mtx_lock("a_list_mtx_lock);
|
|
return ++dq_list_lock_cnt;
|
|
}
|
|
|
|
static int
|
|
dq_list_lock_changed(int oldval)
|
|
{
|
|
return dq_list_lock_cnt != oldval;
|
|
}
|
|
|
|
static int
|
|
dq_list_lock_val(void)
|
|
{
|
|
return dq_list_lock_cnt;
|
|
}
|
|
|
|
void
|
|
dq_list_unlock(void)
|
|
{
|
|
lck_mtx_unlock("a_list_mtx_lock);
|
|
}
|
|
|
|
|
|
/*
|
|
* must be called with the quota_list_lock held
|
|
*/
|
|
void
|
|
dq_lock_internal(struct dquot *dq)
|
|
{
|
|
while (dq->dq_lflags & DQ_LLOCK) {
|
|
dq->dq_lflags |= DQ_LWANT;
|
|
msleep(&dq->dq_lflags, "a_list_mtx_lock, PVFS, "dq_lock_internal", NULL);
|
|
}
|
|
dq->dq_lflags |= DQ_LLOCK;
|
|
}
|
|
|
|
/*
|
|
* must be called with the quota_list_lock held
|
|
*/
|
|
void
|
|
dq_unlock_internal(struct dquot *dq)
|
|
{
|
|
int wanted = dq->dq_lflags & DQ_LWANT;
|
|
|
|
dq->dq_lflags &= ~(DQ_LLOCK | DQ_LWANT);
|
|
|
|
if (wanted) {
|
|
wakeup(&dq->dq_lflags);
|
|
}
|
|
}
|
|
|
|
void
|
|
dqlock(struct dquot *dq)
|
|
{
|
|
lck_mtx_lock("a_list_mtx_lock);
|
|
|
|
dq_lock_internal(dq);
|
|
|
|
lck_mtx_unlock("a_list_mtx_lock);
|
|
}
|
|
|
|
void
|
|
dqunlock(struct dquot *dq)
|
|
{
|
|
lck_mtx_lock("a_list_mtx_lock);
|
|
|
|
dq_unlock_internal(dq);
|
|
|
|
lck_mtx_unlock("a_list_mtx_lock);
|
|
}
|
|
|
|
|
|
|
|
int
|
|
qf_get(struct quotafile *qfp, int type)
|
|
{
|
|
int error = 0;
|
|
|
|
dq_list_lock();
|
|
|
|
switch (type) {
|
|
case QTF_OPENING:
|
|
while ((qfp->qf_qflags & (QTF_OPENING | QTF_CLOSING))) {
|
|
if ((qfp->qf_qflags & QTF_OPENING)) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
if ((qfp->qf_qflags & QTF_CLOSING)) {
|
|
qfp->qf_qflags |= QTF_WANTED;
|
|
msleep(&qfp->qf_qflags, "a_list_mtx_lock, PVFS, "qf_get", NULL);
|
|
}
|
|
}
|
|
if (qfp->qf_vp != NULLVP) {
|
|
error = EBUSY;
|
|
}
|
|
if (error == 0) {
|
|
qfp->qf_qflags |= QTF_OPENING;
|
|
}
|
|
break;
|
|
|
|
case QTF_CLOSING:
|
|
if ((qfp->qf_qflags & QTF_CLOSING)) {
|
|
error = EBUSY;
|
|
break;
|
|
}
|
|
qfp->qf_qflags |= QTF_CLOSING;
|
|
|
|
while ((qfp->qf_qflags & QTF_OPENING) || qfp->qf_refcnt) {
|
|
qfp->qf_qflags |= QTF_WANTED;
|
|
msleep(&qfp->qf_qflags, "a_list_mtx_lock, PVFS, "qf_get", NULL);
|
|
}
|
|
if (qfp->qf_vp == NULLVP) {
|
|
qfp->qf_qflags &= ~QTF_CLOSING;
|
|
error = EBUSY;
|
|
}
|
|
break;
|
|
}
|
|
dq_list_unlock();
|
|
|
|
return error;
|
|
}
|
|
|
|
void
|
|
qf_put(struct quotafile *qfp, int type)
|
|
{
|
|
dq_list_lock();
|
|
|
|
switch (type) {
|
|
case QTF_OPENING:
|
|
case QTF_CLOSING:
|
|
qfp->qf_qflags &= ~type;
|
|
break;
|
|
}
|
|
if ((qfp->qf_qflags & QTF_WANTED)) {
|
|
qfp->qf_qflags &= ~QTF_WANTED;
|
|
wakeup(&qfp->qf_qflags);
|
|
}
|
|
dq_list_unlock();
|
|
}
|
|
|
|
|
|
static void
|
|
qf_lock(struct quotafile *qfp)
|
|
{
|
|
lck_mtx_lock(&qfp->qf_lock);
|
|
}
|
|
|
|
static void
|
|
qf_unlock(struct quotafile *qfp)
|
|
{
|
|
lck_mtx_unlock(&qfp->qf_lock);
|
|
}
|
|
|
|
|
|
/*
|
|
* take a reference on the quota file while we're
|
|
* in dqget... this will prevent a quota_off from
|
|
* occurring while we're potentially playing with
|
|
* the quota file... the quota_off will stall until
|
|
* all the current references 'die'... once we start
|
|
* into quoto_off, all new references will be rejected
|
|
* we also don't want any dqgets being processed while
|
|
* we're in the middle of the quota_on... once we've
|
|
* actually got the quota file open and the associated
|
|
* struct quotafile inited, we can let them come through
|
|
*
|
|
* quota list lock must be held on entry
|
|
*/
|
|
static int
|
|
qf_ref(struct quotafile *qfp)
|
|
{
|
|
int error = 0;
|
|
|
|
if ((qfp->qf_qflags & (QTF_OPENING | QTF_CLOSING)) || (qfp->qf_vp == NULLVP)) {
|
|
error = EINVAL;
|
|
} else {
|
|
qfp->qf_refcnt++;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* drop our reference and wakeup any waiters if
|
|
* we were the last one holding a ref
|
|
*
|
|
* quota list lock must be held on entry
|
|
*/
|
|
static void
|
|
qf_rele(struct quotafile *qfp)
|
|
{
|
|
qfp->qf_refcnt--;
|
|
|
|
if ((qfp->qf_qflags & QTF_WANTED) && qfp->qf_refcnt == 0) {
|
|
qfp->qf_qflags &= ~QTF_WANTED;
|
|
wakeup(&qfp->qf_qflags);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
dqfileinit(struct quotafile *qfp)
|
|
{
|
|
qfp->qf_vp = NULLVP;
|
|
qfp->qf_qflags = 0;
|
|
|
|
lck_mtx_init(&qfp->qf_lock, &qf_lck_grp, LCK_ATTR_NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
* Initialize a quota file
|
|
*
|
|
* must be called with the quota file lock held
|
|
*/
|
|
int
|
|
dqfileopen(struct quotafile *qfp, int type)
|
|
{
|
|
struct dqfilehdr header;
|
|
struct vfs_context context;
|
|
off_t file_size;
|
|
uio_t auio;
|
|
int error = 0;
|
|
UIO_STACKBUF(uio_buf, 1);
|
|
|
|
context.vc_thread = current_thread();
|
|
context.vc_ucred = qfp->qf_cred;
|
|
|
|
/* Obtain the file size */
|
|
if ((error = vnode_size(qfp->qf_vp, &file_size, &context)) != 0) {
|
|
goto out;
|
|
}
|
|
|
|
/* Read the file header */
|
|
auio = uio_createwithbuffer(1, 0, UIO_SYSSPACE, UIO_READ,
|
|
&uio_buf[0], sizeof(uio_buf));
|
|
uio_addiov(auio, CAST_USER_ADDR_T(&header), sizeof(header));
|
|
error = VNOP_READ(qfp->qf_vp, auio, 0, &context);
|
|
if (error) {
|
|
goto out;
|
|
} else if (uio_resid(auio)) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
/* Sanity check the quota file header. */
|
|
if ((OSSwapBigToHostInt32(header.dqh_magic) != quotamagic[type]) ||
|
|
(OSSwapBigToHostInt32(header.dqh_version) > QF_VERSION) ||
|
|
(!powerof2(OSSwapBigToHostInt32(header.dqh_maxentries))) ||
|
|
(OSSwapBigToHostInt32(header.dqh_maxentries) > (file_size / sizeof(struct dqblk)))) {
|
|
error = EINVAL;
|
|
goto out;
|
|
}
|
|
/* Set up the time limits for this quota. */
|
|
if (header.dqh_btime != 0) {
|
|
qfp->qf_btime = OSSwapBigToHostInt32(header.dqh_btime);
|
|
} else {
|
|
qfp->qf_btime = MAX_DQ_TIME;
|
|
}
|
|
if (header.dqh_itime != 0) {
|
|
qfp->qf_itime = OSSwapBigToHostInt32(header.dqh_itime);
|
|
} else {
|
|
qfp->qf_itime = MAX_IQ_TIME;
|
|
}
|
|
|
|
/* Calculate the hash table constants. */
|
|
qfp->qf_maxentries = OSSwapBigToHostInt32(header.dqh_maxentries);
|
|
qfp->qf_entrycnt = OSSwapBigToHostInt32(header.dqh_entrycnt);
|
|
qfp->qf_shift = dqhashshift(qfp->qf_maxentries);
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Close down a quota file
|
|
*/
|
|
void
|
|
dqfileclose(struct quotafile *qfp, __unused int type)
|
|
{
|
|
struct dqfilehdr header;
|
|
struct vfs_context context;
|
|
uio_t auio;
|
|
UIO_STACKBUF(uio_buf, 1);
|
|
|
|
auio = uio_createwithbuffer(1, 0, UIO_SYSSPACE, UIO_READ,
|
|
&uio_buf[0], sizeof(uio_buf));
|
|
uio_addiov(auio, CAST_USER_ADDR_T(&header), sizeof(header));
|
|
|
|
context.vc_thread = current_thread();
|
|
context.vc_ucred = qfp->qf_cred;
|
|
|
|
if (VNOP_READ(qfp->qf_vp, auio, 0, &context) == 0) {
|
|
header.dqh_entrycnt = OSSwapHostToBigInt32(qfp->qf_entrycnt);
|
|
uio_reset(auio, 0, UIO_SYSSPACE, UIO_WRITE);
|
|
uio_addiov(auio, CAST_USER_ADDR_T(&header), sizeof(header));
|
|
(void) VNOP_WRITE(qfp->qf_vp, auio, 0, &context);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Obtain a dquot structure for the specified identifier and quota file
|
|
* reading the information from the file if necessary.
|
|
*/
|
|
int
|
|
dqget(u_int32_t id, struct quotafile *qfp, int type, struct dquot **dqp)
|
|
{
|
|
struct dquot *dq;
|
|
struct dquot *ndq = NULL;
|
|
struct dquot *fdq = NULL;
|
|
struct dqhash *dqh;
|
|
struct vnode *dqvp;
|
|
int error = 0;
|
|
int listlockval = 0;
|
|
|
|
if (!dqisinitialized()) {
|
|
*dqp = NODQUOT;
|
|
return EINVAL;
|
|
}
|
|
|
|
if (id == 0 || qfp->qf_vp == NULLVP) {
|
|
*dqp = NODQUOT;
|
|
return EINVAL;
|
|
}
|
|
dq_list_lock();
|
|
|
|
if ((qf_ref(qfp))) {
|
|
dq_list_unlock();
|
|
|
|
*dqp = NODQUOT;
|
|
return EINVAL;
|
|
}
|
|
if ((dqvp = qfp->qf_vp) == NULLVP) {
|
|
qf_rele(qfp);
|
|
dq_list_unlock();
|
|
|
|
*dqp = NODQUOT;
|
|
return EINVAL;
|
|
}
|
|
dqh = DQHASH(dqvp, id);
|
|
|
|
relookup:
|
|
listlockval = dq_list_lock_val();
|
|
|
|
/*
|
|
* Check the cache first.
|
|
*/
|
|
for (dq = dqh->lh_first; dq; dq = dq->dq_hash.le_next) {
|
|
if (dq->dq_id != id ||
|
|
dq->dq_qfile->qf_vp != dqvp) {
|
|
continue;
|
|
}
|
|
|
|
dq_lock_internal(dq);
|
|
if (dq_list_lock_changed(listlockval)) {
|
|
dq_unlock_internal(dq);
|
|
goto relookup;
|
|
}
|
|
|
|
/*
|
|
* dq_lock_internal may drop the quota_list_lock to msleep, so
|
|
* we need to re-evaluate the identity of this dq
|
|
*/
|
|
if (dq->dq_id != id || dq->dq_qfile == NULL ||
|
|
dq->dq_qfile->qf_vp != dqvp) {
|
|
dq_unlock_internal(dq);
|
|
goto relookup;
|
|
}
|
|
/*
|
|
* Cache hit with no references. Take
|
|
* the structure off the free list.
|
|
*/
|
|
if (dq->dq_cnt++ == 0) {
|
|
if (dq->dq_flags & DQ_MOD) {
|
|
TAILQ_REMOVE(&dqdirtylist, dq, dq_freelist);
|
|
} else {
|
|
TAILQ_REMOVE(&dqfreelist, dq, dq_freelist);
|
|
}
|
|
}
|
|
dq_unlock_internal(dq);
|
|
|
|
if (fdq != NULL) {
|
|
/*
|
|
* we grabbed this from the free list in the first pass
|
|
* but we found the dq we were looking for in
|
|
* the cache the 2nd time through
|
|
* so stick it back on the free list and return the cached entry
|
|
*/
|
|
TAILQ_INSERT_HEAD(&dqfreelist, fdq, dq_freelist);
|
|
}
|
|
qf_rele(qfp);
|
|
dq_list_unlock();
|
|
|
|
if (ndq != NULL) {
|
|
/*
|
|
* we allocated this in the first pass
|
|
* but we found the dq we were looking for in
|
|
* the cache the 2nd time through so free it
|
|
*/
|
|
zfree(KT_DQUOT, ndq);
|
|
}
|
|
*dqp = dq;
|
|
|
|
return 0;
|
|
}
|
|
/*
|
|
* Not in cache, allocate a new one.
|
|
*/
|
|
if (TAILQ_EMPTY(&dqfreelist) &&
|
|
numdquot < MAXQUOTAS * desiredvnodes) {
|
|
desireddquot += DQUOTINC;
|
|
}
|
|
|
|
if (fdq != NULL) {
|
|
/*
|
|
* we captured this from the free list
|
|
* in the first pass through, so go
|
|
* ahead and use it
|
|
*/
|
|
dq = fdq;
|
|
fdq = NULL;
|
|
} else if (numdquot < desireddquot) {
|
|
if (ndq == NULL) {
|
|
/*
|
|
* drop the quota list lock since zalloc may block
|
|
*/
|
|
dq_list_unlock();
|
|
|
|
ndq = (struct dquot *)zalloc_flags(KT_DQUOT,
|
|
Z_WAITOK | Z_ZERO);
|
|
|
|
listlockval = dq_list_lock();
|
|
/*
|
|
* need to look for the entry again in the cache
|
|
* since we dropped the quota list lock and
|
|
* someone else may have beaten us to creating it
|
|
*/
|
|
goto relookup;
|
|
} else {
|
|
/*
|
|
* we allocated this in the first pass through
|
|
* and we're still under out target, so go
|
|
* ahead and use it
|
|
*/
|
|
dq = ndq;
|
|
ndq = NULL;
|
|
numdquot++;
|
|
}
|
|
} else {
|
|
if (TAILQ_EMPTY(&dqfreelist)) {
|
|
qf_rele(qfp);
|
|
dq_list_unlock();
|
|
|
|
if (ndq) {
|
|
/*
|
|
* we allocated this in the first pass through
|
|
* but we're now at the limit of our cache size
|
|
* so free it
|
|
*/
|
|
zfree(KT_DQUOT, ndq);
|
|
}
|
|
tablefull("dquot");
|
|
*dqp = NODQUOT;
|
|
return EUSERS;
|
|
}
|
|
dq = TAILQ_FIRST(&dqfreelist);
|
|
|
|
dq_lock_internal(dq);
|
|
|
|
if (dq_list_lock_changed(listlockval) || dq->dq_cnt || (dq->dq_flags & DQ_MOD)) {
|
|
/*
|
|
* we lost the race while we weren't holding
|
|
* the quota list lock... dq_lock_internal
|
|
* will drop it to msleep... this dq has been
|
|
* reclaimed... go find another
|
|
*/
|
|
dq_unlock_internal(dq);
|
|
|
|
/*
|
|
* need to look for the entry again in the cache
|
|
* since we dropped the quota list lock and
|
|
* someone else may have beaten us to creating it
|
|
*/
|
|
goto relookup;
|
|
}
|
|
TAILQ_REMOVE(&dqfreelist, dq, dq_freelist);
|
|
|
|
if (dq->dq_qfile != NULL) {
|
|
LIST_REMOVE(dq, dq_hash);
|
|
dq->dq_qfile = NULL;
|
|
dq->dq_id = 0;
|
|
}
|
|
dq_unlock_internal(dq);
|
|
|
|
/*
|
|
* because we may have dropped the quota list lock
|
|
* in the call to dq_lock_internal, we need to
|
|
* relookup in the hash in case someone else
|
|
* caused a dq with this identity to be created...
|
|
* if we don't find it, we'll use this one
|
|
*/
|
|
fdq = dq;
|
|
goto relookup;
|
|
}
|
|
/*
|
|
* we've either freshly allocated a dq
|
|
* or we've atomically pulled it out of
|
|
* the hash and freelists... no one else
|
|
* can have a reference, which means no
|
|
* one else can be trying to use this dq
|
|
*/
|
|
dq_lock_internal(dq);
|
|
if (dq_list_lock_changed(listlockval)) {
|
|
dq_unlock_internal(dq);
|
|
goto relookup;
|
|
}
|
|
|
|
/*
|
|
* Initialize the contents of the dquot structure.
|
|
*/
|
|
dq->dq_cnt = 1;
|
|
dq->dq_flags = 0;
|
|
dq->dq_id = id;
|
|
dq->dq_qfile = qfp;
|
|
dq->dq_type = type;
|
|
/*
|
|
* once we insert it in the hash and
|
|
* drop the quota_list_lock, it can be
|
|
* 'found'... however, we're still holding
|
|
* the dq_lock which will keep us from doing
|
|
* anything with it until we've finished
|
|
* initializing it...
|
|
*/
|
|
LIST_INSERT_HEAD(dqh, dq, dq_hash);
|
|
dq_list_unlock();
|
|
|
|
if (ndq) {
|
|
/*
|
|
* we allocated this in the first pass through
|
|
* but we didn't need it, so free it after
|
|
* we've droped the quota list lock
|
|
*/
|
|
zfree(KT_DQUOT, ndq);
|
|
}
|
|
|
|
error = dqlookup(qfp, id, &dq->dq_dqb, &dq->dq_index);
|
|
|
|
/*
|
|
* I/O error in reading quota file, release
|
|
* quota structure and reflect problem to caller.
|
|
*/
|
|
if (error) {
|
|
dq_list_lock();
|
|
|
|
dq->dq_id = 0;
|
|
dq->dq_qfile = NULL;
|
|
LIST_REMOVE(dq, dq_hash);
|
|
|
|
dq_unlock_internal(dq);
|
|
qf_rele(qfp);
|
|
dq_list_unlock();
|
|
|
|
dqrele(dq);
|
|
|
|
*dqp = NODQUOT;
|
|
return error;
|
|
}
|
|
/*
|
|
* Check for no limit to enforce.
|
|
* Initialize time values if necessary.
|
|
*/
|
|
if (dq->dq_isoftlimit == 0 && dq->dq_bsoftlimit == 0 &&
|
|
dq->dq_ihardlimit == 0 && dq->dq_bhardlimit == 0) {
|
|
dq->dq_flags |= DQ_FAKE;
|
|
}
|
|
if (dq->dq_id != 0) {
|
|
struct timeval tv;
|
|
|
|
microtime(&tv);
|
|
if (dq->dq_btime == 0) {
|
|
dq->dq_btime = tv.tv_sec + qfp->qf_btime;
|
|
}
|
|
if (dq->dq_itime == 0) {
|
|
dq->dq_itime = tv.tv_sec + qfp->qf_itime;
|
|
}
|
|
}
|
|
dq_list_lock();
|
|
dq_unlock_internal(dq);
|
|
qf_rele(qfp);
|
|
dq_list_unlock();
|
|
|
|
*dqp = dq;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Lookup a dqblk structure for the specified identifier and
|
|
* quota file. If there is no entry for this identifier then
|
|
* one is inserted. The actual hash table index is returned.
|
|
*/
|
|
static int
|
|
dqlookup(struct quotafile *qfp, u_int32_t id, struct dqblk *dqb, uint32_t *index)
|
|
{
|
|
struct vnode *dqvp;
|
|
struct vfs_context context;
|
|
uio_t auio;
|
|
int i, skip, last;
|
|
u_int32_t mask;
|
|
int error = 0;
|
|
UIO_STACKBUF(uio_buf, 1);
|
|
|
|
|
|
qf_lock(qfp);
|
|
|
|
dqvp = qfp->qf_vp;
|
|
|
|
context.vc_thread = current_thread();
|
|
context.vc_ucred = qfp->qf_cred;
|
|
|
|
mask = qfp->qf_maxentries - 1;
|
|
i = dqhash1(id, qfp->qf_shift, mask);
|
|
skip = dqhash2(id, mask);
|
|
|
|
for (last = (i + (qfp->qf_maxentries - 1) * skip) & mask;
|
|
i != last;
|
|
i = (i + skip) & mask) {
|
|
auio = uio_createwithbuffer(1, dqoffset(i), UIO_SYSSPACE, UIO_READ,
|
|
&uio_buf[0], sizeof(uio_buf));
|
|
uio_addiov(auio, CAST_USER_ADDR_T(dqb), sizeof(struct dqblk));
|
|
error = VNOP_READ(dqvp, auio, 0, &context);
|
|
if (error) {
|
|
printf("dqlookup: error %d looking up id %u at index %d\n", error, id, i);
|
|
break;
|
|
} else if (uio_resid(auio)) {
|
|
error = EIO;
|
|
printf("dqlookup: error looking up id %u at index %d\n", id, i);
|
|
break;
|
|
}
|
|
/*
|
|
* An empty entry means there is no entry
|
|
* with that id. In this case a new dqb
|
|
* record will be inserted.
|
|
*/
|
|
if (dqb->dqb_id == 0) {
|
|
bzero(dqb, sizeof(struct dqblk));
|
|
dqb->dqb_id = OSSwapHostToBigInt32(id);
|
|
/*
|
|
* Write back to reserve entry for this id
|
|
*/
|
|
uio_reset(auio, dqoffset(i), UIO_SYSSPACE, UIO_WRITE);
|
|
uio_addiov(auio, CAST_USER_ADDR_T(dqb), sizeof(struct dqblk));
|
|
error = VNOP_WRITE(dqvp, auio, 0, &context);
|
|
if (uio_resid(auio) && error == 0) {
|
|
error = EIO;
|
|
}
|
|
if (error == 0) {
|
|
++qfp->qf_entrycnt;
|
|
}
|
|
dqb->dqb_id = id;
|
|
break;
|
|
}
|
|
/* An id match means an entry was found. */
|
|
if (OSSwapBigToHostInt32(dqb->dqb_id) == id) {
|
|
dqb->dqb_bhardlimit = OSSwapBigToHostInt64(dqb->dqb_bhardlimit);
|
|
dqb->dqb_bsoftlimit = OSSwapBigToHostInt64(dqb->dqb_bsoftlimit);
|
|
dqb->dqb_curbytes = OSSwapBigToHostInt64(dqb->dqb_curbytes);
|
|
dqb->dqb_ihardlimit = OSSwapBigToHostInt32(dqb->dqb_ihardlimit);
|
|
dqb->dqb_isoftlimit = OSSwapBigToHostInt32(dqb->dqb_isoftlimit);
|
|
dqb->dqb_curinodes = OSSwapBigToHostInt32(dqb->dqb_curinodes);
|
|
dqb->dqb_btime = OSSwapBigToHostInt32(dqb->dqb_btime);
|
|
dqb->dqb_itime = OSSwapBigToHostInt32(dqb->dqb_itime);
|
|
dqb->dqb_id = OSSwapBigToHostInt32(dqb->dqb_id);
|
|
break;
|
|
}
|
|
}
|
|
qf_unlock(qfp);
|
|
|
|
*index = i; /* remember index so we don't have to recompute it later */
|
|
|
|
return error;
|
|
}
|
|
|
|
|
|
/*
|
|
* Release a reference to a dquot.
|
|
*/
|
|
void
|
|
dqrele(struct dquot *dq)
|
|
{
|
|
if (dq == NODQUOT) {
|
|
return;
|
|
}
|
|
dqlock(dq);
|
|
|
|
if (dq->dq_cnt > 1) {
|
|
dq->dq_cnt--;
|
|
|
|
dqunlock(dq);
|
|
return;
|
|
}
|
|
if (dq->dq_flags & DQ_MOD) {
|
|
(void) dqsync_locked(dq);
|
|
}
|
|
dq->dq_cnt--;
|
|
|
|
dq_list_lock();
|
|
TAILQ_INSERT_TAIL(&dqfreelist, dq, dq_freelist);
|
|
dq_unlock_internal(dq);
|
|
dq_list_unlock();
|
|
}
|
|
|
|
/*
|
|
* Release a reference to a dquot but don't do any I/O.
|
|
*/
|
|
void
|
|
dqreclaim(struct dquot *dq)
|
|
{
|
|
if (dq == NODQUOT) {
|
|
return;
|
|
}
|
|
|
|
dq_list_lock();
|
|
dq_lock_internal(dq);
|
|
|
|
if (--dq->dq_cnt > 0) {
|
|
dq_unlock_internal(dq);
|
|
dq_list_unlock();
|
|
return;
|
|
}
|
|
if (dq->dq_flags & DQ_MOD) {
|
|
TAILQ_INSERT_TAIL(&dqdirtylist, dq, dq_freelist);
|
|
} else {
|
|
TAILQ_INSERT_TAIL(&dqfreelist, dq, dq_freelist);
|
|
}
|
|
|
|
dq_unlock_internal(dq);
|
|
dq_list_unlock();
|
|
}
|
|
|
|
/*
|
|
* Update a quota file's orphaned disk quotas.
|
|
*/
|
|
void
|
|
dqsync_orphans(struct quotafile *qfp)
|
|
{
|
|
struct dquot *dq;
|
|
|
|
dq_list_lock();
|
|
loop:
|
|
TAILQ_FOREACH(dq, &dqdirtylist, dq_freelist) {
|
|
if (dq->dq_qfile != qfp) {
|
|
continue;
|
|
}
|
|
|
|
dq_lock_internal(dq);
|
|
|
|
if (dq->dq_qfile != qfp) {
|
|
/*
|
|
* the identity of this dq changed while
|
|
* the quota_list_lock was dropped
|
|
* dq_lock_internal can drop it to msleep
|
|
*/
|
|
dq_unlock_internal(dq);
|
|
goto loop;
|
|
}
|
|
if ((dq->dq_flags & DQ_MOD) == 0) {
|
|
/*
|
|
* someone cleaned and removed this from
|
|
* the dq from the dirty list while the
|
|
* quota_list_lock was dropped
|
|
*/
|
|
dq_unlock_internal(dq);
|
|
goto loop;
|
|
}
|
|
if (dq->dq_cnt != 0) {
|
|
panic("dqsync_orphans: dquot in use");
|
|
}
|
|
|
|
TAILQ_REMOVE(&dqdirtylist, dq, dq_freelist);
|
|
|
|
dq_list_unlock();
|
|
/*
|
|
* we're still holding the dqlock at this point
|
|
* with the reference count == 0
|
|
* we shouldn't be able
|
|
* to pick up another one since we hold dqlock
|
|
*/
|
|
(void) dqsync_locked(dq);
|
|
|
|
dq_list_lock();
|
|
|
|
TAILQ_INSERT_TAIL(&dqfreelist, dq, dq_freelist);
|
|
|
|
dq_unlock_internal(dq);
|
|
goto loop;
|
|
}
|
|
dq_list_unlock();
|
|
}
|
|
|
|
int
|
|
dqsync(struct dquot *dq)
|
|
{
|
|
int error = 0;
|
|
|
|
if (dq != NODQUOT) {
|
|
dqlock(dq);
|
|
|
|
if ((dq->dq_flags & DQ_MOD)) {
|
|
error = dqsync_locked(dq);
|
|
}
|
|
|
|
dqunlock(dq);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
|
|
/*
|
|
* Update the disk quota in the quota file.
|
|
*/
|
|
int
|
|
dqsync_locked(struct dquot *dq)
|
|
{
|
|
struct vfs_context context;
|
|
struct vnode *dqvp;
|
|
struct dqblk dqb, *dqblkp;
|
|
uio_t auio;
|
|
int error;
|
|
UIO_STACKBUF(uio_buf, 1);
|
|
|
|
if (dq->dq_id == 0) {
|
|
dq->dq_flags &= ~DQ_MOD;
|
|
return 0;
|
|
}
|
|
if (dq->dq_qfile == NULL) {
|
|
panic("dqsync: NULL dq_qfile");
|
|
}
|
|
if ((dqvp = dq->dq_qfile->qf_vp) == NULLVP) {
|
|
panic("dqsync: NULL qf_vp");
|
|
}
|
|
|
|
auio = uio_createwithbuffer(1, dqoffset(dq->dq_index), UIO_SYSSPACE,
|
|
UIO_WRITE, &uio_buf[0], sizeof(uio_buf));
|
|
uio_addiov(auio, CAST_USER_ADDR_T(&dqb), sizeof(struct dqblk));
|
|
|
|
context.vc_thread = current_thread(); /* XXX */
|
|
context.vc_ucred = dq->dq_qfile->qf_cred;
|
|
|
|
dqblkp = &dq->dq_dqb;
|
|
dqb.dqb_bhardlimit = OSSwapHostToBigInt64(dqblkp->dqb_bhardlimit);
|
|
dqb.dqb_bsoftlimit = OSSwapHostToBigInt64(dqblkp->dqb_bsoftlimit);
|
|
dqb.dqb_curbytes = OSSwapHostToBigInt64(dqblkp->dqb_curbytes);
|
|
dqb.dqb_ihardlimit = OSSwapHostToBigInt32(dqblkp->dqb_ihardlimit);
|
|
dqb.dqb_isoftlimit = OSSwapHostToBigInt32(dqblkp->dqb_isoftlimit);
|
|
dqb.dqb_curinodes = OSSwapHostToBigInt32(dqblkp->dqb_curinodes);
|
|
dqb.dqb_btime = OSSwapHostToBigInt32(dqblkp->dqb_btime);
|
|
dqb.dqb_itime = OSSwapHostToBigInt32(dqblkp->dqb_itime);
|
|
dqb.dqb_id = OSSwapHostToBigInt32(dqblkp->dqb_id);
|
|
dqb.dqb_spare[0] = 0;
|
|
dqb.dqb_spare[1] = 0;
|
|
dqb.dqb_spare[2] = 0;
|
|
dqb.dqb_spare[3] = 0;
|
|
|
|
error = VNOP_WRITE(dqvp, auio, 0, &context);
|
|
if (uio_resid(auio) && error == 0) {
|
|
error = EIO;
|
|
}
|
|
dq->dq_flags &= ~DQ_MOD;
|
|
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Flush all entries from the cache for a particular vnode.
|
|
*/
|
|
void
|
|
dqflush(struct vnode *vp)
|
|
{
|
|
struct dquot *dq, *nextdq;
|
|
struct dqhash *dqh;
|
|
|
|
if (!dqisinitialized()) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Move all dquot's that used to refer to this quota
|
|
* file off their hash chains (they will eventually
|
|
* fall off the head of the free list and be re-used).
|
|
*/
|
|
dq_list_lock();
|
|
|
|
for (dqh = &dqhashtbl[dqhash]; dqh >= dqhashtbl; dqh--) {
|
|
for (dq = dqh->lh_first; dq; dq = nextdq) {
|
|
nextdq = dq->dq_hash.le_next;
|
|
if (dq->dq_qfile->qf_vp != vp) {
|
|
continue;
|
|
}
|
|
if (dq->dq_cnt) {
|
|
panic("dqflush: stray dquot");
|
|
}
|
|
LIST_REMOVE(dq, dq_hash);
|
|
dq->dq_qfile = NULL;
|
|
}
|
|
}
|
|
dq_list_unlock();
|
|
}
|
|
|
|
/*
|
|
* LP64 support for munging dqblk structure.
|
|
* XXX conversion of user_time_t to time_t loses precision; not an issue for
|
|
* XXX us now, since we are only ever setting 32 bits worth of time into it.
|
|
*/
|
|
__private_extern__ void
|
|
munge_dqblk(struct dqblk *dqblkp, struct user_dqblk *user_dqblkp, boolean_t to64)
|
|
{
|
|
if (to64) {
|
|
/* munge kernel (32 bit) dqblk into user (64 bit) dqblk */
|
|
bcopy((caddr_t)dqblkp, (caddr_t)user_dqblkp, offsetof(struct dqblk, dqb_btime));
|
|
user_dqblkp->dqb_id = dqblkp->dqb_id;
|
|
user_dqblkp->dqb_itime = dqblkp->dqb_itime;
|
|
user_dqblkp->dqb_btime = dqblkp->dqb_btime;
|
|
} else {
|
|
/* munge user (64 bit) dqblk into kernel (32 bit) dqblk */
|
|
bcopy((caddr_t)user_dqblkp, (caddr_t)dqblkp, offsetof(struct dqblk, dqb_btime));
|
|
dqblkp->dqb_id = user_dqblkp->dqb_id;
|
|
dqblkp->dqb_itime = user_dqblkp->dqb_itime; /* XXX - lose precision */
|
|
dqblkp->dqb_btime = user_dqblkp->dqb_btime; /* XXX - lose precision */
|
|
}
|
|
}
|