* 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
* 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
* @(#)ufs_quota.c 8.5 (Berkeley) 5/20/95
#include <ufs/ufs/quota.h>
#include <ufs/ufs/inode.h>
#include <ufs/ufs/ufsmount.h>
#include <ufs/ufs/ufs_extern.h>
* Quota name to error message mapping.
static char *quotatypes
[] = INITQFNAMES
;
* Set up the quotas for an inode.
* This routine completely defines the semantics of quotas.
* If other criterion want to be used to establish quotas, the
* MAXQUOTAS value in quotas.h should be increased, and the
* additional dquots set up here.
register struct inode
*ip
;
struct vnode
*vp
= ITOV(ip
);
ump
= VFSTOUFS(vp
->v_mount
);
* Set up the user quota based on file uid.
* EINVAL means that quotas are not enabled.
if (ip
->i_dquot
[USRQUOTA
] == NODQUOT
&&
dqget(vp
, ip
->i_uid
, ump
, USRQUOTA
, &ip
->i_dquot
[USRQUOTA
])) &&
* Set up the group quota based on file gid.
* EINVAL means that quotas are not enabled.
if (ip
->i_dquot
[GRPQUOTA
] == NODQUOT
&&
dqget(vp
, ip
->i_gid
, ump
, GRPQUOTA
, &ip
->i_dquot
[GRPQUOTA
])) &&
* Update disk usage, and take corrective action.
chkdq(ip
, change
, cred
, flags
)
register struct inode
*ip
;
register struct dquot
*dq
;
if ((flags
& CHOWN
) == 0)
for (i
= 0; i
< MAXQUOTAS
; i
++) {
if ((dq
= ip
->i_dquot
[i
]) == NODQUOT
)
while (dq
->dq_flags
& DQ_LOCK
) {
sleep((caddr_t
)dq
, PINOD
+1);
ncurblocks
= dq
->dq_curblocks
+ change
;
dq
->dq_curblocks
= ncurblocks
;
dq
->dq_flags
&= ~DQ_BLKS
;
if ((flags
& FORCE
) == 0 && cred
->cr_uid
!= 0) {
for (i
= 0; i
< MAXQUOTAS
; i
++) {
if ((dq
= ip
->i_dquot
[i
]) == NODQUOT
)
if (error
= chkdqchg(ip
, change
, cred
, i
))
for (i
= 0; i
< MAXQUOTAS
; i
++) {
if ((dq
= ip
->i_dquot
[i
]) == NODQUOT
)
while (dq
->dq_flags
& DQ_LOCK
) {
sleep((caddr_t
)dq
, PINOD
+1);
dq
->dq_curblocks
+= change
;
* Check for a valid change to a users allocation.
* Issue an error message if appropriate.
chkdqchg(ip
, change
, cred
, type
)
register struct dquot
*dq
= ip
->i_dquot
[type
];
long ncurblocks
= dq
->dq_curblocks
+ change
;
* If user would exceed their hard limit, disallow space allocation.
if (ncurblocks
>= dq
->dq_bhardlimit
&& dq
->dq_bhardlimit
) {
if ((dq
->dq_flags
& DQ_BLKS
) == 0 &&
ip
->i_uid
== cred
->cr_uid
) {
uprintf("\n%s: write failed, %s disk limit reached\n",
ITOV(ip
)->v_mount
->mnt_stat
.f_mntonname
,
* If user is over their soft limit for too long, disallow space
* allocation. Reset time limit as they cross their soft limit.
if (ncurblocks
>= dq
->dq_bsoftlimit
&& dq
->dq_bsoftlimit
) {
if (dq
->dq_curblocks
< dq
->dq_bsoftlimit
) {
dq
->dq_btime
= time
.tv_sec
+
VFSTOUFS(ITOV(ip
)->v_mount
)->um_btime
[type
];
if (ip
->i_uid
== cred
->cr_uid
)
uprintf("\n%s: warning, %s %s\n",
ITOV(ip
)->v_mount
->mnt_stat
.f_mntonname
,
quotatypes
[type
], "disk quota exceeded");
if (time
.tv_sec
> dq
->dq_btime
) {
if ((dq
->dq_flags
& DQ_BLKS
) == 0 &&
ip
->i_uid
== cred
->cr_uid
) {
uprintf("\n%s: write failed, %s %s\n",
ITOV(ip
)->v_mount
->mnt_stat
.f_mntonname
,
"disk quota exceeded for too long");
* Check the inode limit, applying corrective action.
chkiq(ip
, change
, cred
, flags
)
register struct inode
*ip
;
register struct dquot
*dq
;
if ((flags
& CHOWN
) == 0)
for (i
= 0; i
< MAXQUOTAS
; i
++) {
if ((dq
= ip
->i_dquot
[i
]) == NODQUOT
)
while (dq
->dq_flags
& DQ_LOCK
) {
sleep((caddr_t
)dq
, PINOD
+1);
ncurinodes
= dq
->dq_curinodes
+ change
;
dq
->dq_curinodes
= ncurinodes
;
dq
->dq_flags
&= ~DQ_INODS
;
if ((flags
& FORCE
) == 0 && cred
->cr_uid
!= 0) {
for (i
= 0; i
< MAXQUOTAS
; i
++) {
if ((dq
= ip
->i_dquot
[i
]) == NODQUOT
)
if (error
= chkiqchg(ip
, change
, cred
, i
))
for (i
= 0; i
< MAXQUOTAS
; i
++) {
if ((dq
= ip
->i_dquot
[i
]) == NODQUOT
)
while (dq
->dq_flags
& DQ_LOCK
) {
sleep((caddr_t
)dq
, PINOD
+1);
dq
->dq_curinodes
+= change
;
* Check for a valid change to a users allocation.
* Issue an error message if appropriate.
chkiqchg(ip
, change
, cred
, type
)
register struct dquot
*dq
= ip
->i_dquot
[type
];
long ncurinodes
= dq
->dq_curinodes
+ change
;
* If user would exceed their hard limit, disallow inode allocation.
if (ncurinodes
>= dq
->dq_ihardlimit
&& dq
->dq_ihardlimit
) {
if ((dq
->dq_flags
& DQ_INODS
) == 0 &&
ip
->i_uid
== cred
->cr_uid
) {
uprintf("\n%s: write failed, %s inode limit reached\n",
ITOV(ip
)->v_mount
->mnt_stat
.f_mntonname
,
dq
->dq_flags
|= DQ_INODS
;
* If user is over their soft limit for too long, disallow inode
* allocation. Reset time limit as they cross their soft limit.
if (ncurinodes
>= dq
->dq_isoftlimit
&& dq
->dq_isoftlimit
) {
if (dq
->dq_curinodes
< dq
->dq_isoftlimit
) {
dq
->dq_itime
= time
.tv_sec
+
VFSTOUFS(ITOV(ip
)->v_mount
)->um_itime
[type
];
if (ip
->i_uid
== cred
->cr_uid
)
uprintf("\n%s: warning, %s %s\n",
ITOV(ip
)->v_mount
->mnt_stat
.f_mntonname
,
quotatypes
[type
], "inode quota exceeded");
if (time
.tv_sec
> dq
->dq_itime
) {
if ((dq
->dq_flags
& DQ_INODS
) == 0 &&
ip
->i_uid
== cred
->cr_uid
) {
uprintf("\n%s: write failed, %s %s\n",
ITOV(ip
)->v_mount
->mnt_stat
.f_mntonname
,
"inode quota exceeded for too long");
dq
->dq_flags
|= DQ_INODS
;
* On filesystems with quotas enabled, it is an error for a file to change
* size and not to have a dquot structure associated with it.
register struct inode
*ip
;
struct ufsmount
*ump
= VFSTOUFS(ITOV(ip
)->v_mount
);
for (i
= 0; i
< MAXQUOTAS
; i
++) {
if (ump
->um_quotas
[i
] == NULLVP
||
(ump
->um_qflags
[i
] & (QTF_OPENING
|QTF_CLOSING
)))
if (ip
->i_dquot
[i
] == NODQUOT
) {
vprint("chkdquot: missing dquot", ITOV(ip
));
* Code to process quotactl commands.
* Q_QUOTAON - set up a quota file for a particular file system.
quotaon(p
, mp
, type
, fname
)
struct ufsmount
*ump
= VFSTOUFS(mp
);
vpp
= &ump
->um_quotas
[type
];
NDINIT(&nd
, LOOKUP
, FOLLOW
, UIO_USERSPACE
, fname
, p
);
if (error
= vn_open(&nd
, FREAD
|FWRITE
, 0))
if (vp
->v_type
!= VREG
) {
(void) vn_close(vp
, FREAD
|FWRITE
, p
->p_ucred
, p
);
ump
->um_qflags
[type
] |= QTF_OPENING
;
mp
->mnt_flag
|= MNT_QUOTA
;
* Save the credential of the process that turned on quotas.
* Set up the time limits for this quota.
ump
->um_cred
[type
] = p
->p_ucred
;
ump
->um_btime
[type
] = MAX_DQ_TIME
;
ump
->um_itime
[type
] = MAX_IQ_TIME
;
if (dqget(NULLVP
, 0, ump
, type
, &dq
) == 0) {
ump
->um_btime
[type
] = dq
->dq_btime
;
ump
->um_itime
[type
] = dq
->dq_itime
;
* Search vnodes associated with this mount point,
* adding references to quota file being opened.
* NB: only need to add dquot's for inodes being modified.
for (vp
= mp
->mnt_vnodelist
.lh_first
; vp
!= NULL
; vp
= nextvp
) {
nextvp
= vp
->v_mntvnodes
.le_next
;
if (vp
->v_writecount
== 0)
if (vget(vp
, LK_EXCLUSIVE
, p
))
if (error
= getinoquota(VTOI(vp
))) {
if (vp
->v_mntvnodes
.le_next
!= nextvp
|| vp
->v_mount
!= mp
)
ump
->um_qflags
[type
] &= ~QTF_OPENING
;
* Q_QUOTAOFF - turn off disk quotas for a filesystem.
struct vnode
*qvp
, *nextvp
;
struct ufsmount
*ump
= VFSTOUFS(mp
);
if ((qvp
= ump
->um_quotas
[type
]) == NULLVP
)
ump
->um_qflags
[type
] |= QTF_CLOSING
;
* Search vnodes associated with this mount point,
* deleting any references to quota file being closed.
for (vp
= mp
->mnt_vnodelist
.lh_first
; vp
!= NULL
; vp
= nextvp
) {
nextvp
= vp
->v_mntvnodes
.le_next
;
if (vget(vp
, LK_EXCLUSIVE
, p
))
ip
->i_dquot
[type
] = NODQUOT
;
if (vp
->v_mntvnodes
.le_next
!= nextvp
|| vp
->v_mount
!= mp
)
error
= vn_close(qvp
, FREAD
|FWRITE
, p
->p_ucred
, p
);
ump
->um_quotas
[type
] = NULLVP
;
crfree(ump
->um_cred
[type
]);
ump
->um_cred
[type
] = NOCRED
;
ump
->um_qflags
[type
] &= ~QTF_CLOSING
;
for (type
= 0; type
< MAXQUOTAS
; type
++)
if (ump
->um_quotas
[type
] != NULLVP
)
mp
->mnt_flag
&= ~MNT_QUOTA
;
* Q_GETQUOTA - return current values in a dqblk structure.
getquota(mp
, id
, type
, addr
)
if (error
= dqget(NULLVP
, id
, VFSTOUFS(mp
), type
, &dq
))
error
= copyout((caddr_t
)&dq
->dq_dqb
, addr
, sizeof (struct dqblk
));
* Q_SETQUOTA - assign an entire dqblk structure.
setquota(mp
, id
, type
, addr
)
register struct dquot
*dq
;
struct ufsmount
*ump
= VFSTOUFS(mp
);
if (error
= copyin(addr
, (caddr_t
)&newlim
, sizeof (struct dqblk
)))
if (error
= dqget(NULLVP
, id
, ump
, type
, &ndq
))
while (dq
->dq_flags
& DQ_LOCK
) {
sleep((caddr_t
)dq
, PINOD
+1);
* Copy all but the current values.
* Reset time limit if previously had no soft limit or were
* under it, but now have a soft limit and are over it.
newlim
.dqb_curblocks
= dq
->dq_curblocks
;
newlim
.dqb_curinodes
= dq
->dq_curinodes
;
newlim
.dqb_btime
= dq
->dq_btime
;
newlim
.dqb_itime
= dq
->dq_itime
;
if (newlim
.dqb_bsoftlimit
&&
dq
->dq_curblocks
>= newlim
.dqb_bsoftlimit
&&
(dq
->dq_bsoftlimit
== 0 || dq
->dq_curblocks
< dq
->dq_bsoftlimit
))
newlim
.dqb_btime
= time
.tv_sec
+ ump
->um_btime
[type
];
if (newlim
.dqb_isoftlimit
&&
dq
->dq_curinodes
>= newlim
.dqb_isoftlimit
&&
(dq
->dq_isoftlimit
== 0 || dq
->dq_curinodes
< dq
->dq_isoftlimit
))
newlim
.dqb_itime
= time
.tv_sec
+ ump
->um_itime
[type
];
if (dq
->dq_curblocks
< dq
->dq_bsoftlimit
)
dq
->dq_flags
&= ~DQ_BLKS
;
if (dq
->dq_curinodes
< dq
->dq_isoftlimit
)
dq
->dq_flags
&= ~DQ_INODS
;
if (dq
->dq_isoftlimit
== 0 && dq
->dq_bsoftlimit
== 0 &&
dq
->dq_ihardlimit
== 0 && dq
->dq_bhardlimit
== 0)
dq
->dq_flags
&= ~DQ_FAKE
;
* Q_SETUSE - set current inode and block usage.
setuse(mp
, id
, type
, addr
)
register struct dquot
*dq
;
struct ufsmount
*ump
= VFSTOUFS(mp
);
if (error
= copyin(addr
, (caddr_t
)&usage
, sizeof (struct dqblk
)))
if (error
= dqget(NULLVP
, id
, ump
, type
, &ndq
))
while (dq
->dq_flags
& DQ_LOCK
) {
sleep((caddr_t
)dq
, PINOD
+1);
* Reset time limit if have a soft limit and were
* previously under it, but are now over it.
if (dq
->dq_bsoftlimit
&& dq
->dq_curblocks
< dq
->dq_bsoftlimit
&&
usage
.dqb_curblocks
>= dq
->dq_bsoftlimit
)
dq
->dq_btime
= time
.tv_sec
+ ump
->um_btime
[type
];
if (dq
->dq_isoftlimit
&& dq
->dq_curinodes
< dq
->dq_isoftlimit
&&
usage
.dqb_curinodes
>= dq
->dq_isoftlimit
)
dq
->dq_itime
= time
.tv_sec
+ ump
->um_itime
[type
];
dq
->dq_curblocks
= usage
.dqb_curblocks
;
dq
->dq_curinodes
= usage
.dqb_curinodes
;
if (dq
->dq_curblocks
< dq
->dq_bsoftlimit
)
dq
->dq_flags
&= ~DQ_BLKS
;
if (dq
->dq_curinodes
< dq
->dq_isoftlimit
)
dq
->dq_flags
&= ~DQ_INODS
;
* Q_SYNC - sync quota files to disk.
struct ufsmount
*ump
= VFSTOUFS(mp
);
struct proc
*p
= curproc
; /* XXX */
struct vnode
*vp
, *nextvp
;
* Check if the mount point has any quotas.
for (i
= 0; i
< MAXQUOTAS
; i
++)
if (ump
->um_quotas
[i
] != NULLVP
)
* Search vnodes associated with this mount point,
* synchronizing any modified dquot structures.
simple_lock(&mntvnode_slock
);
for (vp
= mp
->mnt_vnodelist
.lh_first
; vp
!= NULL
; vp
= nextvp
) {
nextvp
= vp
->v_mntvnodes
.le_next
;
simple_lock(&vp
->v_interlock
);
simple_unlock(&mntvnode_slock
);
error
= vget(vp
, LK_EXCLUSIVE
| LK_NOWAIT
| LK_INTERLOCK
, p
);
simple_lock(&mntvnode_slock
);
for (i
= 0; i
< MAXQUOTAS
; i
++) {
dq
= VTOI(vp
)->i_dquot
[i
];
if (dq
!= NODQUOT
&& (dq
->dq_flags
& DQ_MOD
))
simple_lock(&mntvnode_slock
);
if (vp
->v_mntvnodes
.le_next
!= nextvp
)
simple_unlock(&mntvnode_slock
);
* 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
;
#define DQUOTINC 5 /* minimum free dquots desired */
TAILQ_HEAD(dqfreelist
, dquot
) dqfreelist
;
long numdquot
, desireddquot
= DQUOTINC
;
* Initialize the quota system.
dqhashtbl
= hashinit(desiredvnodes
, M_DQUOT
, &dqhash
);
* Obtain a dquot structure for the specified identifier and quota file
* reading the information from the file if necessary.
dqget(vp
, id
, ump
, type
, dqp
)
register struct ufsmount
*ump
;
struct proc
*p
= curproc
; /* XXX */
dqvp
= ump
->um_quotas
[type
];
if (dqvp
== NULLVP
|| (ump
->um_qflags
[type
] & QTF_CLOSING
)) {
for (dq
= dqh
->lh_first
; dq
; dq
= dq
->dq_hash
.le_next
) {
dq
->dq_ump
->um_quotas
[dq
->dq_type
] != dqvp
)
* Cache hit with no references. Take
* the structure off the free list.
TAILQ_REMOVE(&dqfreelist
, dq
, dq_freelist
);
* 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
);
if ((dq
= dqfreelist
.tqh_first
) == NULL
) {
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.
vn_lock(dqvp
, LK_EXCLUSIVE
| LK_RETRY
, p
);
LIST_INSERT_HEAD(dqh
, dq
, dq_hash
);
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
)(id
* sizeof (struct dqblk
));
auio
.uio_segflg
= UIO_SYSSPACE
;
auio
.uio_procp
= (struct proc
*)0;
error
= VOP_READ(dqvp
, &auio
, 0, ump
->um_cred
[type
]);
if (auio
.uio_resid
== sizeof(struct dqblk
) && error
== 0)
bzero((caddr_t
)&dq
->dq_dqb
, sizeof(struct dqblk
));
if (dq
->dq_flags
& DQ_WANT
)
* I/O error in reading quota file, release
* quota structure and reflect problem to caller.
LIST_REMOVE(dq
, dq_hash
);
* 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_btime
= time
.tv_sec
+ ump
->um_btime
[type
];
dq
->dq_itime
= time
.tv_sec
+ ump
->um_itime
[type
];
* Obtain a reference to a dquot.
* Release a reference to a dquot.
register struct dquot
*dq
;
if (dq
->dq_flags
& DQ_MOD
)
TAILQ_INSERT_TAIL(&dqfreelist
, dq
, dq_freelist
);
* Update the disk quota in the quota file.
struct proc
*p
= curproc
; /* XXX */
if ((dq
->dq_flags
& DQ_MOD
) == 0)
if ((dqvp
= dq
->dq_ump
->um_quotas
[dq
->dq_type
]) == NULLVP
)
vn_lock(dqvp
, LK_EXCLUSIVE
| LK_RETRY
, p
);
while (dq
->dq_flags
& DQ_LOCK
) {
sleep((caddr_t
)dq
, PINOD
+2);
if ((dq
->dq_flags
& DQ_MOD
) == 0) {
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
)(dq
->dq_id
* sizeof (struct dqblk
));
auio
.uio_segflg
= UIO_SYSSPACE
;
auio
.uio_procp
= (struct proc
*)0;
error
= VOP_WRITE(dqvp
, &auio
, 0, dq
->dq_ump
->um_cred
[dq
->dq_type
]);
if (auio
.uio_resid
&& error
== 0)
if (dq
->dq_flags
& DQ_WANT
)
dq
->dq_flags
&= ~(DQ_MOD
|DQ_LOCK
|DQ_WANT
);
* Flush all entries from the cache for a particular vnode.
register struct vnode
*vp
;
register struct dquot
*dq
, *nextdq
;
* 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_ump
->um_quotas
[dq
->dq_type
] != vp
)
panic("dqflush: stray dquot");
LIST_REMOVE(dq
, dq_hash
);
dq
->dq_ump
= (struct ufsmount
*)0;