/* * Copyright (c) 2002 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This 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 OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_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 #include #include #include #include #include #include #include #include static u_int32_t quotamagic[MAXQUOTAS] = INITQMAGICS; /* * Code pertaining to management of the in-core dquot data structures. */ #define DQHASH(dqvp, id) \ (&dqhashtbl[((((int)(dqvp)) >> 8) + id) & dqhash]) LIST_HEAD(dqhash, dquot) *dqhashtbl; u_long dqhash; /* * Dquot free list. */ #define DQUOTINC 5 /* minimum free dquots desired */ TAILQ_HEAD(dqfreelist, dquot) dqfreelist; long numdquot, desireddquot = DQUOTINC; static int dqlookup(struct quotafile *, u_long, struct dqblk *, u_int32_t *); /* * Initialize the quota system. */ void dqinit() { dqhashtbl = hashinit(desiredvnodes, M_DQUOT, &dqhash); TAILQ_INIT(&dqfreelist); } /* * Initialize a quota file */ int dqfileopen(qfp, type) struct quotafile *qfp; int type; { struct dqfilehdr header; struct vattr vattr; struct iovec aiov; struct uio auio; int error; /* Obtain the file size */ error = VOP_GETATTR(qfp->qf_vp, &vattr, qfp->qf_cred, current_proc()); if (error) return (error); /* Read the file header */ auio.uio_iov = &aiov; auio.uio_iovcnt = 1; aiov.iov_base = (caddr_t)&header; aiov.iov_len = sizeof (header); auio.uio_resid = sizeof (header); auio.uio_offset = (off_t)(0); auio.uio_segflg = UIO_SYSSPACE; auio.uio_rw = UIO_READ; auio.uio_procp = (struct proc *)0; error = VOP_READ(qfp->qf_vp, &auio, 0, qfp->qf_cred); if (error) return (error); else if (auio.uio_resid) return (EINVAL); /* Sanity check the quota file header. */ if ((header.dqh_magic != quotamagic[type]) || (header.dqh_version > QF_VERSION) || (!powerof2(header.dqh_maxentries)) || (header.dqh_maxentries > (vattr.va_size / sizeof(struct dqblk)))) return (EINVAL); /* Set up the time limits for this quota. */ if (header.dqh_btime > 0) qfp->qf_btime = header.dqh_btime; else qfp->qf_btime = MAX_DQ_TIME; if (header.dqh_itime > 0) qfp->qf_itime = header.dqh_itime; else qfp->qf_itime = MAX_IQ_TIME; /* Calculate the hash table constants. */ qfp->qf_maxentries = header.dqh_maxentries; qfp->qf_entrycnt = header.dqh_entrycnt; qfp->qf_shift = dqhashshift(header.dqh_maxentries); return (0); } /* * Close down a quota file */ void dqfileclose(qfp, type) struct quotafile *qfp; int type; { struct dqfilehdr header; struct iovec aiov; struct uio auio; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; aiov.iov_base = (caddr_t)&header; aiov.iov_len = sizeof (header); auio.uio_resid = sizeof (header); auio.uio_offset = (off_t)(0); auio.uio_segflg = UIO_SYSSPACE; auio.uio_rw = UIO_READ; auio.uio_procp = (struct proc *)0; if (VOP_READ(qfp->qf_vp, &auio, 0, qfp->qf_cred) == 0) { header.dqh_entrycnt = qfp->qf_entrycnt; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; aiov.iov_base = (caddr_t)&header; aiov.iov_len = sizeof (header); auio.uio_resid = sizeof (header); auio.uio_offset = (off_t)(0); auio.uio_segflg = UIO_SYSSPACE; auio.uio_rw = UIO_WRITE; auio.uio_procp = (struct proc *)0; (void) VOP_WRITE(qfp->qf_vp, &auio, 0, qfp->qf_cred); } } /* * Obtain a dquot structure for the specified identifier and quota file * reading the information from the file if necessary. */ int dqget(vp, id, qfp, type, dqp) struct vnode *vp; u_long id; struct quotafile *qfp; register int type; struct dquot **dqp; { struct proc *p = current_proc(); /* XXX */ struct dquot *dq; struct dqhash *dqh; struct vnode *dqvp; int error = 0; dqvp = qfp->qf_vp; if (id == 0 || dqvp == NULLVP || (qfp->qf_qflags & QTF_CLOSING)) { *dqp = NODQUOT; return (EINVAL); } /* * Check the cache first. */ dqh = DQHASH(dqvp, id); for (dq = dqh->lh_first; dq; dq = dq->dq_hash.le_next) { if (dq->dq_id != id || dq->dq_qfile->qf_vp != dqvp) continue; /* * Cache hit with no references. Take * the structure off the free list. */ if (dq->dq_cnt == 0) TAILQ_REMOVE(&dqfreelist, dq, dq_freelist); DQREF(dq); *dqp = dq; return (0); } /* * Not in cache, allocate a new one. */ if (dqfreelist.tqh_first == NODQUOT && numdquot < MAXQUOTAS * desiredvnodes) desireddquot += DQUOTINC; if (numdquot < desireddquot) { dq = (struct dquot *)_MALLOC(sizeof *dq, M_DQUOT, M_WAITOK); bzero((char *)dq, sizeof *dq); numdquot++; } else { if ((dq = dqfreelist.tqh_first) == NULL) { tablefull("dquot"); *dqp = NODQUOT; return (EUSERS); } if (dq->dq_cnt || (dq->dq_flags & DQ_MOD)) panic("free dquot isn't"); TAILQ_REMOVE(&dqfreelist, dq, dq_freelist); LIST_REMOVE(dq, dq_hash); } /* * Initialize the contents of the dquot structure. */ if (vp != dqvp) vn_lock(dqvp, LK_EXCLUSIVE | LK_RETRY, p); LIST_INSERT_HEAD(dqh, dq, dq_hash); DQREF(dq); dq->dq_flags = DQ_LOCK; dq->dq_id = id; dq->dq_qfile = qfp; dq->dq_type = type; error = dqlookup(qfp, id, &dq->dq_dqb, &dq->dq_index); if (vp != dqvp) VOP_UNLOCK(dqvp, 0, p); if (dq->dq_flags & DQ_WANT) wakeup((caddr_t)dq); dq->dq_flags = 0; /* * I/O error in reading quota file, release * quota structure and reflect problem to caller. */ if (error) { LIST_REMOVE(dq, dq_hash); dqrele(vp, 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) { if (dq->dq_btime == 0) dq->dq_btime = time.tv_sec + qfp->qf_btime; if (dq->dq_itime == 0) dq->dq_itime = time.tv_sec + qfp->qf_itime; } *dqp = dq; return (0); } /* * Lookup a dqblk structure for the specified identifier and * quota file. If there is no enetry for this identifier then * one is inserted. The actual hash table index is returned. */ static int dqlookup(qfp, id, dqb, index) struct quotafile *qfp; u_long id; struct dqblk *dqb; u_int32_t *index; { struct vnode *dqvp; struct ucred *cred; struct iovec aiov; struct uio auio; int i, skip, last; u_long mask; int error = 0; if (id == 0) return (EINVAL); dqvp = qfp->qf_vp; cred = qfp->qf_cred; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_segflg = UIO_SYSSPACE; auio.uio_procp = (struct proc *)0; 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) { aiov.iov_base = (caddr_t)dqb; aiov.iov_len = sizeof (struct dqblk); auio.uio_resid = sizeof (struct dqblk); auio.uio_offset = (off_t)dqoffset(i); auio.uio_rw = UIO_READ; error = VOP_READ(dqvp, &auio, 0, cred); if (error) { printf("dqlookup: error %d looking up id %d at index %d\n", error, id, i); break; } else if (auio.uio_resid) { error = EIO; printf("dqlookup: error looking up id %d 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 = id; /* * Write back to reserve entry for this id */ aiov.iov_base = (caddr_t)dqb; aiov.iov_len = sizeof (struct dqblk); auio.uio_resid = sizeof (struct dqblk); auio.uio_offset = (off_t)dqoffset(i); auio.uio_rw = UIO_WRITE; error = VOP_WRITE(dqvp, &auio, 0, cred); if (auio.uio_resid && error == 0) error = EIO; if (error == 0) ++qfp->qf_entrycnt; break; } /* An id match means an entry was found. */ if (dqb->dqb_id == id) break; } *index = i; /* remember index so we don't have to recompute it later */ return (error); } /* * Obtain a reference to a dquot. */ void dqref(dq) struct dquot *dq; { dq->dq_cnt++; } /* * Release a reference to a dquot. */ void dqrele(vp, dq) struct vnode *vp; register struct dquot *dq; { if (dq == NODQUOT) return; if (dq->dq_cnt > 1) { dq->dq_cnt--; return; } if (dq->dq_flags & DQ_MOD) (void) dqsync(vp, dq); if (--dq->dq_cnt > 0) return; TAILQ_INSERT_TAIL(&dqfreelist, dq, dq_freelist); } /* * Update the disk quota in the quota file. */ int dqsync(vp, dq) struct vnode *vp; struct dquot *dq; { struct proc *p = current_proc(); /* XXX */ struct vnode *dqvp; struct iovec aiov; struct uio auio; int error; if (dq == NODQUOT) panic("dqsync: dquot"); if ((dq->dq_flags & DQ_MOD) == 0) return (0); if (dq->dq_id == 0) return(0); if ((dqvp = dq->dq_qfile->qf_vp) == NULLVP) panic("dqsync: file"); if (vp != dqvp) vn_lock(dqvp, LK_EXCLUSIVE | LK_RETRY, p); while (dq->dq_flags & DQ_LOCK) { dq->dq_flags |= DQ_WANT; sleep((caddr_t)dq, PINOD+2); if ((dq->dq_flags & DQ_MOD) == 0) { if (vp != dqvp) VOP_UNLOCK(dqvp, 0, p); return (0); } } dq->dq_flags |= DQ_LOCK; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; aiov.iov_base = (caddr_t)&dq->dq_dqb; aiov.iov_len = sizeof (struct dqblk); auio.uio_resid = sizeof (struct dqblk); auio.uio_offset = (off_t)dqoffset(dq->dq_index); auio.uio_segflg = UIO_SYSSPACE; auio.uio_rw = UIO_WRITE; auio.uio_procp = (struct proc *)0; error = VOP_WRITE(dqvp, &auio, 0, dq->dq_qfile->qf_cred); if (auio.uio_resid && error == 0) error = EIO; if (dq->dq_flags & DQ_WANT) wakeup((caddr_t)dq); dq->dq_flags &= ~(DQ_MOD|DQ_LOCK|DQ_WANT); if (vp != dqvp) VOP_UNLOCK(dqvp, 0, p); return (error); } /* * Flush all entries from the cache for a particular vnode. */ void dqflush(vp) register struct vnode *vp; { register struct dquot *dq, *nextdq; struct dqhash *dqh; /* * 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). */ 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 = (struct quotafile *)0; } } }