2.11BSD/sys/sys/quota_kern.c
/*
* Copyright (c) 1982, 1986 Regents of the University of California.
* All rights reserved. The Berkeley software License Agreement
* specifies the terms and conditions for redistribution.
*
* @(#)quota_kern.c 7.1.4 (2.11BSD GTE) 1997/1/18
*
* I'll say it here and not every other place i've had to hack:
* Mike Karels was right - " just buy a vax...". i have traded cpu cycles
* for kernel D space - if enough space ever becomes available then the
* p_quota, i_dquot members can be added to their respective structures,
* and the quota[] array might possibly be moved into the kernel, it is
* highly doubtful the dquot structures can ever be moved into the kernel
* D space. Then too, the mapping of the out of kernel quota space is
* swamped by the overhead of doing the quotas, and quotas sure as heck
* beat doing 'du's all the time!
* Steven M. Schultz 1/12/88
*/
/*
* MELBOURNE QUOTAS
*
* Code pertaining to management of the in-core data structures.
*/
#include "param.h"
#ifdef QUOTA
#include "systm.h"
#include "user.h"
#include "proc.h"
#include "inode.h"
#include "quota.h"
#include "fs.h"
#include "mount.h"
#include "namei.h"
/*
* Quota cache - hash chain headers.
*/
#ifndef pdp11
#define NQHASH 32 /* small power of two */
#endif
#define QHASH(uid) ((unsigned)(uid) & (NQHASH-1))
#ifndef pdp11
struct qhash {
struct qhash *qh_forw; /* MUST be first */
struct qhash *qh_back; /* MUST be second */
};
struct qhash qhash[NQHASH];
#else
struct qhash *qhash;
#endif
/*
* Quota free list.
*/
struct quota *qfreelist, **qfreetail;
typedef struct quota *Qptr;
/*
* Dquot cache - hash chain headers.
*/
#ifndef pdp11 /* and 51 isn't even prime, see quota.h */
#define NDQHASH 51 /* a smallish prime */
#endif
#define DQHASH(uid, dev) \
((unsigned)(((int)(dev) * 4) + (uid)) % NDQHASH)
#ifndef pdp11
struct dqhead {
struct dqhead *dqh_forw; /* MUST be first */
struct dqhead *dqh_back; /* MUST be second */
};
struct dqhead dqhead[NDQHASH];
#else
struct dqhead *dqhead;
#endif
/*
* Dquot free list.
*/
struct dquot *dqfreel, **dqback;
typedef struct dquot *DQptr;
/*
* Initialize quota caches.
*/
qtinit()
{
register i;
register struct quota *q = quota;
register struct qhash *qh = qhash;
register struct dquot *dq = dquot;
register struct dqhead *dh = dqhead;
/*
* First the cache of structures assigned users.
*/
for (i = NQHASH; --i >= 0; qh++)
qh->qh_forw = qh->qh_back = qh;
qfreelist = q;
qfreetail = &q->q_freef;
q->q_freeb = &qfreelist;
q->q_forw = q;
q->q_back = q;
for (i = nquota; --i > 0; ) {
++q;
q->q_forw = q;
q->q_back = q;
*qfreetail = q;
q->q_freeb = qfreetail;
qfreetail = &q->q_freef;
}
q->q_freef = NOQUOTA;
/*
* Next, the cache between the in-core structures
* and the per-filesystem quota files on disk.
*/
for (i = NDQHASH; --i >= 0; dh++)
dh->dqh_forw = dh->dqh_back = dh;
dqfreel = dq;
dqback = &dq->dq_freef;
dq->dq_freeb = &dqfreel;
dq->dq_forw = dq;
dq->dq_back = dq;
for (i = ndquot; --i > 0; ) {
++dq;
dq->dq_forw = dq;
dq->dq_back = dq;
*dqback = dq;
dq->dq_freeb = dqback;
dqback = &dq->dq_freef;
}
dq->dq_freef = NODQUOT;
}
/*
* Find an incore quota structure for a particular uid,
* or make one. If lookuponly is non-zero, just the lookup is performed.
* If nodq is non-zero, the dquot structures are left uninitialized.
*/
struct quota *
getquota(uid, lookuponly, nodq)
register uid_t uid;
int lookuponly, nodq;
{
register struct quota *q;
register struct qhash *qh;
register struct dquot **dqq;
register struct mount *mp;
register struct quota *qq;
/*
* Fast check to see if an existing structure
* can be reused with just a reference count change.
*/
q = u.u_quota;
if (q != NOQUOTA && q->q_uid == uid)
goto quick;
/*
* Search the quota chache for a hit.
*/
qh = &qhash[QHASH(uid)];
for (q = (Qptr)qh->qh_forw; q != (Qptr)qh; q = q->q_forw) {
if (q->q_uid == uid) {
if (q->q_cnt == 0) {
if (lookuponly)
return (NOQUOTA);
/*
* Take it off the free list.
*/
if ((qq = q->q_freef) != NOQUOTA)
qq->q_freeb = q->q_freeb;
else
qfreetail = q->q_freeb;
*q->q_freeb = qq;
/*
* Recover any lost dquot structs.
*/
if (!nodq)
for (dqq = q->q_dq, mp = mount;
dqq < &q->q_dq[NMOUNT]; dqq++, mp++)
#ifdef pdp11
if (*dqq == LOSTDQUOT && mp->m_inodp) {
#else
if (*dqq == LOSTDQUOT && mp->m_bufp) {
#endif
*dqq = discquota(uid,
mp->m_qinod);
if (*dqq != NODQUOT)
(*dqq)->dq_own = q;
}
}
quick:
q->q_cnt++;
while (q->q_flags & Q_LOCK) {
q->q_flags |= Q_WANT;
#ifdef pdp11
QUOTAUNMAP();
sleep((caddr_t) q, PINOD+1);
QUOTAMAP();
#else
sleep((caddr_t) q, PINOD+1);
#endif
}
if (q->q_cnt == 1)
q->q_flags |= Q_NEW | nodq;
return (q);
}
}
if (lookuponly)
return (NOQUOTA);
/*
* Take the quota that is at the head of the free list
* (the longest unused quota).
*/
q = qfreelist;
if (q == NOQUOTA) {
tablefull("quota");
u.u_error = EUSERS;
q = quota; /* the su's slot - we must have one */
q->q_cnt++;
return (q);
}
/*
* There is one - it is free no longer.
*/
qq = q->q_freef;
if (qq != NOQUOTA)
qq->q_freeb = &qfreelist;
qfreelist = qq;
/*
* Now we are about to change this from one user to another
* Must take this off hash chain for old user immediately, in
* case some other process claims it before we are done.
* We must then put it on the hash chain for the new user,
* to make sure that we don't make two quota structs for one uid.
* (the quota struct will then be locked till we are done).
*/
remque(q);
insque(q, qh);
q->q_uid = uid;
q->q_flags = Q_LOCK;
q->q_cnt++; /* q->q_cnt = 1; */
/*
* Next, before filling in info for the new owning user,
* we must get rid of any dquot structs that we own.
*/
for (mp = mount, dqq = q->q_dq; mp < &mount[NMOUNT]; mp++, dqq++)
if (*dqq != NODQUOT && *dqq != LOSTDQUOT) {
(*dqq)->dq_own = NOQUOTA;
putdq(mp, *dqq, 1);
}
for (mp = mount, dqq = q->q_dq; dqq < &q->q_dq[NMOUNT]; mp++, dqq++)
#ifdef pdp11
if (!nodq && mp->m_inodp) {
#else
if (!nodq && mp->m_bufp) {
#endif
*dqq = discquota(uid, mp->m_qinod);
if (*dqq != NODQUOT) {
if ((*dqq)->dq_uid != uid)
panic("got bad quota uid");
(*dqq)->dq_own = q;
}
} else
*dqq = NODQUOT;
if (q->q_flags & Q_WANT)
wakeup((caddr_t)q);
q->q_flags = Q_NEW | nodq;
return (q);
}
/*
* Delete a quota, wakeup anyone waiting.
*/
delquota(q)
register struct quota *q;
{
register struct dquot **dqq;
register struct mount *mp;
top:
if (q->q_cnt != 1) {
q->q_cnt--;
return;
}
if (q->q_flags & Q_LOCK) {
q->q_flags |= Q_WANT;
#ifdef pdp11
QUOTAUNMAP();
sleep((caddr_t)q, PINOD+2);
QUOTAMAP();
#else
sleep((caddr_t)q, PINOD+2);
#endif
/*
* Just so we don't sync dquots if not needed;
* 'if' would be 'while' if this was deleted.
*/
goto top;
}
/*
* If we own dquot structs, sync them to disc, but don't release
* them - we might be recalled from the LRU chain.
* As we will sit on the free list while we are waiting for that,
* if dquot structs run out, ours will be taken away.
*/
q->q_flags = Q_LOCK;
if ((q->q_flags & Q_NDQ) == 0) {
mp = mount;
for (dqq = q->q_dq; dqq < &q->q_dq[NMOUNT]; dqq++, mp++)
#ifdef pdp11
if (mp->m_inodp)
#else
if (mp->m_bufp)
#endif
putdq(mp, *dqq, 0);
}
if (q->q_flags & Q_WANT)
wakeup((caddr_t)q);
/*
* This test looks unnecessary, but someone might have claimed this
* quota while we have been getting rid of the dquot info
*/
if (--q->q_cnt == 0) { /* now able to be reallocated */
if (qfreelist != NOQUOTA) {
*qfreetail = q;
q->q_freeb = qfreetail;
} else {
qfreelist = q;
q->q_freeb = &qfreelist;
}
q->q_freef = NOQUOTA;
qfreetail = &q->q_freef;
q->q_flags = 0;
} else
q->q_flags &= ~(Q_LOCK|Q_WANT);
}
/*
* Obtain the user's on-disk quota limit
* from the file specified.
*/
struct dquot *
discquota(uid, ip)
uid_t uid;
register struct inode *ip;
{
register struct dquot *dq;
register struct dqhead *dh;
register struct dquot *dp;
int fail;
if (ip == NULL)
return (NODQUOT);
/*
* Check the cache first.
*/
dh = &dqhead[DQHASH(uid, ip->i_dev)];
for (dq = (DQptr)dh->dqh_forw; dq != (DQptr)dh; dq = dq->dq_forw) {
if (dq->dq_uid != uid || dq->dq_dev != ip->i_dev)
continue;
/*
* Cache hit with no references. Take
* the structure off the free list.
*/
if (dq->dq_cnt++ == 0) {
dp = dq->dq_freef;
if (dp != NODQUOT)
dp->dq_freeb = dq->dq_freeb;
else
dqback = dq->dq_freeb;
*dq->dq_freeb = dp;
dq->dq_own = NOQUOTA;
}
/*
* We do this test after the previous one so that
* the dquot will be moved to the end of the free
* list - frequently accessed ones ought to hang around.
*/
if (dq->dq_isoftlimit == 0 && dq->dq_bsoftlimit == 0) {
dqrele(dq);
return (NODQUOT);
}
return (dq);
}
/*
* Not in cache, allocate a new one and
* bring info in off disk.
*/
dq = dqalloc(uid, ip->i_dev);
if (dq == NODQUOT)
return (dq);
dq->dq_flags = DQ_LOCK;
#ifdef pdp11
{
struct dqblk xq;
QUOTAUNMAP();
ILOCK(ip);
fail = rdwri(UIO_READ, ip, &xq, sizeof (xq),
(off_t)uid * sizeof (xq), UIO_SYSSPACE, IO_UNIT,(int *)0);
QUOTAMAP();
dq->dq_dqb = xq;
}
#else
ILOCK(ip);
fail = rdwri(UIO_READ, ip, (caddr_t)&dq->dq_dqb, sizeof (struct dqblk),
(off_t)uid * sizeof(struct dqblk), UIO_SYSSPACE, IO_UNIT, (int *)0);
#endif
IUNLOCK(ip);
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 (fail) {
remque(dq);
dq->dq_forw = dq; /* on a private, unfindable hash list */
dq->dq_back = dq;
/* dqrele() (just below) will put dquot back on free list */
}
/* no quota exists */
if (fail || dq->dq_isoftlimit == 0 && dq->dq_bsoftlimit == 0) {
dqrele(dq);
return (NODQUOT);
}
return (dq);
}
/*
* Allocate a dquot structure. If there are
* no free slots in the cache, flush LRU entry from
* the cache to the appropriate quota file on disk.
*/
struct dquot *
dqalloc(uid, dev)
uid_t uid;
dev_t dev;
{
register struct dquot *dq;
register struct dqhead *dh;
register struct dquot *dp;
register struct quota *q;
register struct mount *mp;
static struct dqblk zdqb = { 0 };
top:
/*
* Locate inode of quota file for
* indicated file system in case i/o
* is necessary in claiming an entry.
*/
for (mp = mount; mp < &mount[NMOUNT]; mp++) {
#ifdef pdp11
if (mp->m_dev == dev && mp->m_inodp) {
#else
if (mp->m_dev == dev && mp->m_bufp) {
#endif
if (mp->m_qinod == NULL) {
u.u_error = EINVAL;
return (NODQUOT);
}
break;
}
}
if (mp >= &mount[NMOUNT]) {
u.u_error = EINVAL;
return (NODQUOT);
}
/*
* Check free list. If table is full, pull entries
* off the quota free list and flush any associated
* dquot references until something frees up on the
* dquot free list.
*/
if ((dq = dqfreel) == NODQUOT && (q = qfreelist) != NOQUOTA) {
do {
register struct dquot **dqq;
register struct mount *mountp = mount;
dqq = q->q_dq;
while (dqq < &q->q_dq[NMOUNT]) {
if ((dq = *dqq) != NODQUOT &&
dq != LOSTDQUOT) {
/*
* Mark entry as "lost" due to
* scavenging operation.
*/
if (dq->dq_cnt == 1) {
*dqq = LOSTDQUOT;
putdq(mountp, dq, 1);
goto top;
}
}
mountp++;
dqq++;
}
q = q->q_freef;
} while ((dq = dqfreel) == NODQUOT && q != NOQUOTA);
}
if (dq == NODQUOT) {
tablefull("dquot");
u.u_error = EUSERS;
return (dq);
}
/*
* This shouldn't happen, as we sync
* dquot before freeing it up.
*/
if (dq->dq_flags & DQ_MOD)
panic("discquota");
/*
* Now take the dquot off the free list,
*/
dp = dq->dq_freef;
if (dp != NODQUOT)
dp->dq_freeb = &dqfreel;
dqfreel = dp;
/*
* and off the hash chain it was on, & onto the new one.
*/
dh = &dqhead[DQHASH(uid, dev)];
remque(dq);
insque(dq, dh);
dq->dq_cnt = 1;
dq->dq_flags = 0;
dq->dq_uid = uid;
dq->dq_dev = dev;
dq->dq_dqb = zdqb;
dq->dq_own = NOQUOTA;
return (dq);
}
/*
* dqrele - layman's interface to putdq.
*/
dqrele(dq)
register struct dquot *dq;
{
register struct mount *mp;
if (dq == NODQUOT || dq == LOSTDQUOT)
return;
if (dq->dq_cnt > 1) {
dq->dq_cnt--;
return;
}
/*
* I/O required, find appropriate file system
* to sync the quota information to.
*/
for (mp = mount; mp < &mount[NMOUNT]; mp++)
#ifdef pdp11
if (mp->m_inodp && mp->m_dev == dq->dq_dev) {
#else
if (mp->m_bufp && mp->m_dev == dq->dq_dev) {
#endif
putdq(mp, dq, 1);
return;
}
panic("dqrele");
}
/*
* Update the disc quota in the quota file.
*/
putdq(mp, dq, free)
register struct mount *mp;
register struct dquot *dq;
{
register struct inode *ip;
if (dq == NODQUOT || dq == LOSTDQUOT)
return;
if (free && dq->dq_cnt > 1) {
dq->dq_cnt--;
return;
}
/*
* Disk quota not modified, just discard
* or return (having adjusted the reference
* count), as indicated by the "free" param.
*/
if ((dq->dq_flags & DQ_MOD) == 0) {
if (free) {
dq->dq_cnt = 0;
release:
if (dqfreel != NODQUOT) {
*dqback = dq;
dq->dq_freeb = dqback;
} else {
dqfreel = dq;
dq->dq_freeb = &dqfreel;
}
dq->dq_freef = NODQUOT;
dqback = &dq->dq_freef;
}
return;
}
/*
* Quota modified, write back to disk.
*/
while (dq->dq_flags & DQ_LOCK) {
dq->dq_flags |= DQ_WANT;
#ifdef pdp11
QUOTAUNMAP();
sleep((caddr_t)dq, PINOD+2);
QUOTAMAP();
#else
sleep((caddr_t)dq, PINOD+2);
#endif
/* someone could sneak in and grab it */
if (free && dq->dq_cnt > 1) {
dq->dq_cnt--;
return;
}
}
dq->dq_flags |= DQ_LOCK;
if ((ip = mp->m_qinod) == NULL)
panic("lost quota file");
#ifdef pdp11
{
struct dqblk xq;
uid_t uid;
xq = dq->dq_dqb;
uid = dq->dq_uid;
QUOTAUNMAP();
ILOCK(ip);
(void)rdwri(UIO_WRITE, ip, &xq, sizeof (xq),
(off_t)uid * sizeof (xq), UIO_SYSSPACE,
IO_UNIT, (int *)0);
QUOTAMAP();
}
#else
ILOCK(ip);
(void) rdwri(UIO_WRITE, ip, (caddr_t)&dq->dq_dqb, sizeof (struct dqblk),
(off_t)dq->dq_uid * sizeof (struct dqblk), UIO_SYSSPACE,
IO_UNIT, (int *)0);
#endif
IUNLOCK(ip);
if (dq->dq_flags & DQ_WANT)
wakeup((caddr_t)dq);
dq->dq_flags &= ~(DQ_MOD|DQ_LOCK|DQ_WANT);
if (free && --dq->dq_cnt == 0)
goto release;
}
/*
* See if there is a quota struct in core for user 'uid'.
*/
struct quota *
qfind(uid)
register uid_t uid;
{
register struct quota *q;
register struct qhash *qh;
/*
* Check common cases first: asking for own quota,
* or that of the super user (has reserved slot 0
* in the table).
*/
q = u.u_quota;
if (q != NOQUOTA && q->q_uid == uid)
return (q);
if (uid == 0) /* the second most likely case */
return (quota);
/*
* Search cache.
*/
qh = &qhash[QHASH(uid)];
for (q = (Qptr)qh->qh_forw; q != (Qptr)qh; q = q->q_forw)
if (q->q_uid == uid)
return (q);
return (NOQUOTA);
}
/*
* Set the quota file up for a particular file system.
* Called as the result of a setquota system call.
*/
opendq(mp, fname)
register struct mount *mp;
caddr_t fname;
{
register struct inode *ip;
register struct quota *q;
struct dquot *dq;
struct nameidata nd;
register struct nameidata *ndp = &nd;
int i;
if (mp->m_qinod)
closedq(mp);
QUOTAUNMAP(); /* paranoia */
NDINIT(ndp, LOOKUP, FOLLOW, UIO_USERSPACE, fname);
ip = namei(ndp);
QUOTAMAP();
if (ip == NULL)
return;
IUNLOCK(ip);
if (ip->i_dev != mp->m_dev) {
u.u_error = EACCES;
return;
}
if ((ip->i_mode & IFMT) != IFREG) {
u.u_error = EACCES;
return;
}
/*
* Flush in-core references to any previous
* quota file for this file system.
*/
mp->m_qinod = ip;
mp->m_flags |= MNT_QUOTA;
i = mp - mount;
for (q = quota; q < quotaNQUOTA; q++)
if ((q->q_flags & Q_NDQ) == 0) {
if (q->q_cnt == 0)
q->q_dq[i] = LOSTDQUOT;
else {
q->q_cnt++; /* cannot be released */
dq = discquota(q->q_uid, ip);
q->q_dq[i] = dq;
if (dq != NODQUOT)
dq->dq_own = q;
delquota(q);
}
}
}
/*
* Close off disc quotas for a file system.
*/
closedq(mp)
register struct mount *mp;
{
register struct dquot *dq;
register i = mp - mount;
register struct quota *q;
register struct inode *ip;
if (mp->m_qinod == NULL)
return;
/*
* Search inode table, delete any references
* to quota file being closed.
*/
for (ip = inode; ip < inodeNINODE; ip++)
if (ip->i_dev == mp->m_dev) {
#ifdef pdp11
dq = ix_dquot[ip - inode];
ix_dquot[ip - inode] = NODQUOT;
#else
dq = ip->i_dquot;
ip->i_dquot = NODQUOT;
#endif
putdq(mp, dq, 1);
}
/*
* Search quota table, flush any pending
* quota info to disk and also delete
* references to closing quota file.
*/
for (q = quota; q < quotaNQUOTA; q++) {
if ((q->q_flags & Q_NDQ) == 0) {
if (q->q_cnt) {
q->q_cnt++;
putdq(mp, q->q_dq[i], 1);
delquota(q);
} else
putdq(mp, q->q_dq[i], 1);
}
q->q_dq[i] = NODQUOT;
}
/*
* Move all dquot's that used to refer to this quota
* file of into the never-never (they will eventually
* fall off the head of the free list and be re-used).
*/
for (dq = dquot; dq < dquotNDQUOT; dq++)
if (dq->dq_dev == mp->m_dev) {
if (dq->dq_cnt)
panic("closedq: stray dquot");
remque(dq);
dq->dq_forw = dq;
dq->dq_back = dq;
dq->dq_dev = NODEV;
}
QUOTAUNMAP();
irele(mp->m_qinod);
QUOTAMAP();
mp->m_qinod = NULL;
mp->m_flags &= ~MNT_QUOTA;
}
#endif