. common/include/arch/i386 is not actually an imported sys/arch/i386/include but leftover Minix files; remove and move to include/ . move include/ufs to sys/ufs, where it came from, now that we have a sys/ hierarchy . move mdocml/ to external/bsd/, now we have that . single sys/arch/i386/stand/ import for boot stuff
1013 lines
24 KiB
C
1013 lines
24 KiB
C
/* $NetBSD: ufs_quota2.c,v 1.4 2011/06/07 14:56:13 bouyer Exp $ */
|
|
/*-
|
|
* Copyright (c) 2010 Manuel Bouyer
|
|
* All rights reserved.
|
|
*
|
|
* 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.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__KERNEL_RCSID(0, "$NetBSD: ufs_quota2.c,v 1.4 2011/06/07 14:56:13 bouyer Exp $");
|
|
|
|
#include <sys/buf.h>
|
|
#include <sys/param.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/namei.h>
|
|
#include <sys/file.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/vnode.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/fstrans.h>
|
|
#include <sys/kauth.h>
|
|
#include <sys/wapbl.h>
|
|
|
|
#include <ufs/ufs/quota2.h>
|
|
#include <ufs/ufs/inode.h>
|
|
#include <ufs/ufs/ufsmount.h>
|
|
#include <ufs/ufs/ufs_bswap.h>
|
|
#include <ufs/ufs/ufs_extern.h>
|
|
#include <ufs/ufs/ufs_quota.h>
|
|
#include <ufs/ufs/ufs_wapbl.h>
|
|
#include <quota/quotaprop.h>
|
|
|
|
/*
|
|
* LOCKING:
|
|
* Data in the entries are protected by the associated struct dquot's
|
|
* dq_interlock (this means we can't read or change a quota entry without
|
|
* grabing a dquot for it).
|
|
* The header and lists (including pointers in the data entries, and q2e_uid)
|
|
* are protected by the global dqlock.
|
|
* the locking order is dq_interlock -> dqlock
|
|
*/
|
|
|
|
static int quota2_bwrite(struct mount *, struct buf *);
|
|
static int getinoquota2(struct inode *, bool, bool, struct buf **,
|
|
struct quota2_entry **);
|
|
static int getq2h(struct ufsmount *, int, struct buf **,
|
|
struct quota2_header **, int);
|
|
static int getq2e(struct ufsmount *, int, daddr_t, int, struct buf **,
|
|
struct quota2_entry **, int);
|
|
static int quota2_walk_list(struct ufsmount *, struct buf *, int,
|
|
uint64_t *, int, void *,
|
|
int (*func)(struct ufsmount *, uint64_t *, struct quota2_entry *,
|
|
uint64_t, void *));
|
|
|
|
static int quota2_dict_update_q2e_limits(prop_dictionary_t,
|
|
struct quota2_entry *);
|
|
static prop_dictionary_t q2etoprop(struct quota2_entry *, int);
|
|
|
|
static const char *limnames[] = INITQLNAMES;
|
|
|
|
static int
|
|
quota2_dict_update_q2e_limits(prop_dictionary_t data,
|
|
struct quota2_entry *q2e)
|
|
{
|
|
const char *val_limitsonly_names[] = INITQVNAMES_LIMITSONLY;
|
|
|
|
int i, error;
|
|
prop_dictionary_t val;
|
|
|
|
for (i = 0; i < N_QL; i++) {
|
|
if (!prop_dictionary_get_dict(data, limnames[i], &val))
|
|
return EINVAL;
|
|
error = quotaprop_dict_get_uint64(val,
|
|
&q2e->q2e_val[i].q2v_hardlimit,
|
|
val_limitsonly_names, N_QV, true);
|
|
if (error)
|
|
return error;
|
|
}
|
|
return 0;
|
|
}
|
|
static prop_dictionary_t
|
|
q2etoprop(struct quota2_entry *q2e, int def)
|
|
{
|
|
const char *val_names[] = INITQVNAMES_ALL;
|
|
prop_dictionary_t dict1 = prop_dictionary_create();
|
|
prop_dictionary_t dict2;
|
|
int i;
|
|
|
|
if (dict1 == NULL)
|
|
return NULL;
|
|
|
|
if (def) {
|
|
if (!prop_dictionary_set_cstring_nocopy(dict1, "id",
|
|
"default")) {
|
|
goto err;
|
|
}
|
|
} else {
|
|
if (!prop_dictionary_set_uint32(dict1, "id", q2e->q2e_uid)) {
|
|
goto err;
|
|
}
|
|
}
|
|
for (i = 0; i < N_QL; i++) {
|
|
dict2 = limits64toprop(&q2e->q2e_val[i].q2v_hardlimit,
|
|
val_names, N_QV);
|
|
if (dict2 == NULL)
|
|
goto err;
|
|
if (!prop_dictionary_set_and_rel(dict1, limnames[i], dict2))
|
|
goto err;
|
|
}
|
|
return dict1;
|
|
|
|
err:
|
|
prop_object_release(dict1);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int
|
|
quota2_bwrite(struct mount *mp, struct buf *bp)
|
|
{
|
|
if (mp->mnt_flag & MNT_SYNCHRONOUS)
|
|
return bwrite(bp);
|
|
else {
|
|
bdwrite(bp);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int
|
|
getq2h(struct ufsmount *ump, int type,
|
|
struct buf **bpp, struct quota2_header **q2hp, int flags)
|
|
{
|
|
#ifdef FFS_EI
|
|
const int needswap = UFS_MPNEEDSWAP(ump);
|
|
#endif
|
|
int error;
|
|
struct buf *bp;
|
|
struct quota2_header *q2h;
|
|
|
|
KASSERT(mutex_owned(&dqlock));
|
|
error = bread(ump->um_quotas[type], 0, ump->umq2_bsize,
|
|
ump->um_cred[type], flags, &bp);
|
|
if (error)
|
|
return error;
|
|
if (bp->b_resid != 0)
|
|
panic("dq2get: %s quota file truncated", quotatypes[type]);
|
|
|
|
q2h = (void *)bp->b_data;
|
|
if (ufs_rw32(q2h->q2h_magic_number, needswap) != Q2_HEAD_MAGIC ||
|
|
q2h->q2h_type != type)
|
|
panic("dq2get: corrupted %s quota header", quotatypes[type]);
|
|
*bpp = bp;
|
|
*q2hp = q2h;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
getq2e(struct ufsmount *ump, int type, daddr_t lblkno, int blkoffset,
|
|
struct buf **bpp, struct quota2_entry **q2ep, int flags)
|
|
{
|
|
int error;
|
|
struct buf *bp;
|
|
|
|
if (blkoffset & (sizeof(uint64_t) - 1)) {
|
|
panic("dq2get: %s quota file corrupted",
|
|
quotatypes[type]);
|
|
}
|
|
error = bread(ump->um_quotas[type], lblkno, ump->umq2_bsize,
|
|
ump->um_cred[type], flags, &bp);
|
|
if (error)
|
|
return error;
|
|
if (bp->b_resid != 0) {
|
|
panic("dq2get: %s quota file corrupted",
|
|
quotatypes[type]);
|
|
}
|
|
*q2ep = (void *)((char *)bp->b_data + blkoffset);
|
|
*bpp = bp;
|
|
return 0;
|
|
}
|
|
|
|
/* walk a quota entry list, calling the callback for each entry */
|
|
#define Q2WL_ABORT 0x10000000
|
|
|
|
static int
|
|
quota2_walk_list(struct ufsmount *ump, struct buf *hbp, int type,
|
|
uint64_t *offp, int flags, void *a,
|
|
int (*func)(struct ufsmount *, uint64_t *, struct quota2_entry *, uint64_t, void *))
|
|
{
|
|
#ifdef FFS_EI
|
|
const int needswap = UFS_MPNEEDSWAP(ump);
|
|
#endif
|
|
daddr_t off = ufs_rw64(*offp, needswap);
|
|
struct buf *bp, *obp = hbp;
|
|
int ret = 0, ret2 = 0;
|
|
struct quota2_entry *q2e;
|
|
daddr_t lblkno, blkoff, olblkno = 0;
|
|
|
|
KASSERT(mutex_owner(&dqlock));
|
|
|
|
while (off != 0) {
|
|
lblkno = (off >> ump->um_mountp->mnt_fs_bshift);
|
|
blkoff = (off & ump->umq2_bmask);
|
|
if (lblkno == 0) {
|
|
/* in the header block */
|
|
bp = hbp;
|
|
} else if (lblkno == olblkno) {
|
|
/* still in the same buf */
|
|
bp = obp;
|
|
} else {
|
|
ret = bread(ump->um_quotas[type], lblkno,
|
|
ump->umq2_bsize,
|
|
ump->um_cred[type], flags, &bp);
|
|
if (ret)
|
|
return ret;
|
|
if (bp->b_resid != 0) {
|
|
panic("quota2_walk_list: %s quota file corrupted",
|
|
quotatypes[type]);
|
|
}
|
|
}
|
|
q2e = (void *)((char *)(bp->b_data) + blkoff);
|
|
ret = (*func)(ump, offp, q2e, off, a);
|
|
if (off != ufs_rw64(*offp, needswap)) {
|
|
/* callback changed parent's pointer, redo */
|
|
off = ufs_rw64(*offp, needswap);
|
|
if (bp != hbp && bp != obp)
|
|
ret2 = bwrite(bp);
|
|
} else {
|
|
/* parent if now current */
|
|
if (obp != bp && obp != hbp) {
|
|
if (flags & B_MODIFY)
|
|
ret2 = bwrite(obp);
|
|
else
|
|
brelse(obp, 0);
|
|
}
|
|
obp = bp;
|
|
olblkno = lblkno;
|
|
offp = &(q2e->q2e_next);
|
|
off = ufs_rw64(*offp, needswap);
|
|
}
|
|
if (ret)
|
|
break;
|
|
if (ret2) {
|
|
ret = ret2;
|
|
break;
|
|
}
|
|
}
|
|
if (obp != hbp) {
|
|
if (flags & B_MODIFY)
|
|
ret2 = bwrite(obp);
|
|
else
|
|
brelse(obp, 0);
|
|
}
|
|
if (ret & Q2WL_ABORT)
|
|
return 0;
|
|
if (ret == 0)
|
|
return ret2;
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
quota2_umount(struct mount *mp, int flags)
|
|
{
|
|
int i, error;
|
|
struct ufsmount *ump = VFSTOUFS(mp);
|
|
|
|
if ((ump->um_flags & UFS_QUOTA2) == 0)
|
|
return 0;
|
|
|
|
for (i = 0; i < MAXQUOTAS; i++) {
|
|
if (ump->um_quotas[i] != NULLVP) {
|
|
error = vn_close(ump->um_quotas[i], FREAD|FWRITE,
|
|
ump->um_cred[i]);
|
|
if (error) {
|
|
printf("quota2_umount failed: close(%p) %d\n",
|
|
ump->um_quotas[i], error);
|
|
return error;
|
|
}
|
|
}
|
|
ump->um_quotas[i] = NULLVP;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
quota2_q2ealloc(struct ufsmount *ump, int type, uid_t uid, struct dquot *dq,
|
|
struct buf **bpp, struct quota2_entry **q2ep)
|
|
{
|
|
int error, error2;
|
|
struct buf *hbp, *bp;
|
|
struct quota2_header *q2h;
|
|
struct quota2_entry *q2e;
|
|
daddr_t offset;
|
|
u_long hash_mask;
|
|
const int needswap = UFS_MPNEEDSWAP(ump);
|
|
|
|
KASSERT(mutex_owned(&dq->dq_interlock));
|
|
KASSERT(mutex_owned(&dqlock));
|
|
error = getq2h(ump, type, &hbp, &q2h, B_MODIFY);
|
|
if (error)
|
|
return error;
|
|
offset = ufs_rw64(q2h->q2h_free, needswap);
|
|
if (offset == 0) {
|
|
struct vnode *vp = ump->um_quotas[type];
|
|
struct inode *ip = VTOI(vp);
|
|
uint64_t size = ip->i_size;
|
|
/* need to alocate a new disk block */
|
|
error = UFS_BALLOC(vp, size, ump->umq2_bsize,
|
|
ump->um_cred[type], B_CLRBUF | B_SYNC, &bp);
|
|
if (error) {
|
|
brelse(hbp, 0);
|
|
return error;
|
|
}
|
|
KASSERT((ip->i_size % ump->umq2_bsize) == 0);
|
|
ip->i_size += ump->umq2_bsize;
|
|
DIP_ASSIGN(ip, size, ip->i_size);
|
|
ip->i_flag |= IN_CHANGE | IN_UPDATE;
|
|
uvm_vnp_setsize(vp, ip->i_size);
|
|
quota2_addfreeq2e(q2h, bp->b_data, size, ump->umq2_bsize,
|
|
needswap);
|
|
error = bwrite(bp);
|
|
error2 = UFS_UPDATE(vp, NULL, NULL, UPDATE_WAIT);
|
|
if (error || error2) {
|
|
brelse(hbp, 0);
|
|
if (error)
|
|
return error;
|
|
return error2;
|
|
}
|
|
offset = ufs_rw64(q2h->q2h_free, needswap);
|
|
KASSERT(offset != 0);
|
|
}
|
|
dq->dq2_lblkno = (offset >> ump->um_mountp->mnt_fs_bshift);
|
|
dq->dq2_blkoff = (offset & ump->umq2_bmask);
|
|
if (dq->dq2_lblkno == 0) {
|
|
bp = hbp;
|
|
q2e = (void *)((char *)bp->b_data + dq->dq2_blkoff);
|
|
} else {
|
|
error = getq2e(ump, type, dq->dq2_lblkno,
|
|
dq->dq2_blkoff, &bp, &q2e, B_MODIFY);
|
|
if (error) {
|
|
brelse(hbp, 0);
|
|
return error;
|
|
}
|
|
}
|
|
hash_mask = ((1 << q2h->q2h_hash_shift) - 1);
|
|
/* remove from free list */
|
|
q2h->q2h_free = q2e->q2e_next;
|
|
|
|
memcpy(q2e, &q2h->q2h_defentry, sizeof(*q2e));
|
|
q2e->q2e_uid = ufs_rw32(uid, needswap);
|
|
/* insert in hash list */
|
|
q2e->q2e_next = q2h->q2h_entries[uid & hash_mask];
|
|
q2h->q2h_entries[uid & hash_mask] = ufs_rw64(offset, needswap);
|
|
if (hbp != bp) {
|
|
bwrite(hbp);
|
|
}
|
|
*q2ep = q2e;
|
|
*bpp = bp;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
getinoquota2(struct inode *ip, bool alloc, bool modify, struct buf **bpp,
|
|
struct quota2_entry **q2ep)
|
|
{
|
|
int error;
|
|
int i;
|
|
struct dquot *dq;
|
|
struct ufsmount *ump = ip->i_ump;
|
|
u_int32_t ino_ids[MAXQUOTAS];
|
|
|
|
error = getinoquota(ip);
|
|
if (error)
|
|
return error;
|
|
|
|
if (alloc) {
|
|
UFS_WAPBL_JLOCK_ASSERT(ump->um_mountp);
|
|
}
|
|
ino_ids[USRQUOTA] = ip->i_uid;
|
|
ino_ids[GRPQUOTA] = ip->i_gid;
|
|
/* first get the interlock for all dquot */
|
|
for (i = 0; i < MAXQUOTAS; i++) {
|
|
dq = ip->i_dquot[i];
|
|
if (dq == NODQUOT)
|
|
continue;
|
|
mutex_enter(&dq->dq_interlock);
|
|
}
|
|
/* now get the corresponding quota entry */
|
|
for (i = 0; i < MAXQUOTAS; i++) {
|
|
bpp[i] = NULL;
|
|
q2ep[i] = NULL;
|
|
dq = ip->i_dquot[i];
|
|
if (dq == NODQUOT)
|
|
continue;
|
|
if (__predict_false(ump->um_quotas[i] == NULL)) {
|
|
/*
|
|
* quotas have been turned off. This can happen
|
|
* at umount time.
|
|
*/
|
|
mutex_exit(&dq->dq_interlock);
|
|
dqrele(NULLVP, dq);
|
|
ip->i_dquot[i] = NULL;
|
|
continue;
|
|
}
|
|
|
|
if ((dq->dq2_lblkno | dq->dq2_blkoff) == 0) {
|
|
if (!alloc) {
|
|
continue;
|
|
}
|
|
/* need to alloc a new on-disk quot */
|
|
mutex_enter(&dqlock);
|
|
error = quota2_q2ealloc(ump, i, ino_ids[i], dq,
|
|
&bpp[i], &q2ep[i]);
|
|
mutex_exit(&dqlock);
|
|
if (error)
|
|
return error;
|
|
} else {
|
|
error = getq2e(ump, i, dq->dq2_lblkno,
|
|
dq->dq2_blkoff, &bpp[i], &q2ep[i],
|
|
modify ? B_MODIFY : 0);
|
|
if (error)
|
|
return error;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
quota2_check(struct inode *ip, int vtype, int64_t change, kauth_cred_t cred,
|
|
int flags)
|
|
{
|
|
int error;
|
|
struct buf *bp[MAXQUOTAS];
|
|
struct quota2_entry *q2e[MAXQUOTAS];
|
|
struct quota2_val *q2vp;
|
|
struct dquot *dq;
|
|
uint64_t ncurblks;
|
|
struct ufsmount *ump = ip->i_ump;
|
|
struct mount *mp = ump->um_mountp;
|
|
const int needswap = UFS_MPNEEDSWAP(ump);
|
|
int i;
|
|
|
|
if ((error = getinoquota2(ip, change > 0, change != 0, bp, q2e)) != 0)
|
|
return error;
|
|
if (change == 0) {
|
|
for (i = 0; i < MAXQUOTAS; i++) {
|
|
dq = ip->i_dquot[i];
|
|
if (dq == NODQUOT)
|
|
continue;
|
|
if (bp[i])
|
|
brelse(bp[i], 0);
|
|
mutex_exit(&dq->dq_interlock);
|
|
}
|
|
return 0;
|
|
}
|
|
if (change < 0) {
|
|
for (i = 0; i < MAXQUOTAS; i++) {
|
|
dq = ip->i_dquot[i];
|
|
if (dq == NODQUOT)
|
|
continue;
|
|
if (q2e[i] == NULL) {
|
|
mutex_exit(&dq->dq_interlock);
|
|
continue;
|
|
}
|
|
q2vp = &q2e[i]->q2e_val[vtype];
|
|
ncurblks = ufs_rw64(q2vp->q2v_cur, needswap);
|
|
if (ncurblks < -change)
|
|
ncurblks = 0;
|
|
else
|
|
ncurblks += change;
|
|
q2vp->q2v_cur = ufs_rw64(ncurblks, needswap);
|
|
quota2_bwrite(mp, bp[i]);
|
|
mutex_exit(&dq->dq_interlock);
|
|
}
|
|
return 0;
|
|
}
|
|
/* see if the allocation is allowed */
|
|
for (i = 0; i < MAXQUOTAS; i++) {
|
|
struct quota2_val q2v;
|
|
int ql_stat;
|
|
dq = ip->i_dquot[i];
|
|
if (dq == NODQUOT)
|
|
continue;
|
|
KASSERT(q2e[i] != NULL);
|
|
quota2_ufs_rwq2v(&q2e[i]->q2e_val[vtype], &q2v, needswap);
|
|
ql_stat = quota2_check_limit(&q2v, change, time_second);
|
|
|
|
if ((flags & FORCE) == 0 &&
|
|
kauth_authorize_system(cred, KAUTH_SYSTEM_FS_QUOTA,
|
|
KAUTH_REQ_SYSTEM_FS_QUOTA_NOLIMIT,
|
|
KAUTH_ARG(i), KAUTH_ARG(vtype), NULL) != 0) {
|
|
/* enforce this limit */
|
|
switch(QL_STATUS(ql_stat)) {
|
|
case QL_S_DENY_HARD:
|
|
if ((dq->dq_flags & DQ_WARN(vtype)) == 0) {
|
|
uprintf("\n%s: write failed, %s %s "
|
|
"limit reached\n",
|
|
mp->mnt_stat.f_mntonname,
|
|
quotatypes[i], limnames[vtype]);
|
|
dq->dq_flags |= DQ_WARN(vtype);
|
|
}
|
|
error = EDQUOT;
|
|
break;
|
|
case QL_S_DENY_GRACE:
|
|
if ((dq->dq_flags & DQ_WARN(vtype)) == 0) {
|
|
uprintf("\n%s: write failed, %s %s "
|
|
"limit reached\n",
|
|
mp->mnt_stat.f_mntonname,
|
|
quotatypes[i], limnames[vtype]);
|
|
dq->dq_flags |= DQ_WARN(vtype);
|
|
}
|
|
error = EDQUOT;
|
|
break;
|
|
case QL_S_ALLOW_SOFT:
|
|
if ((dq->dq_flags & DQ_WARN(vtype)) == 0) {
|
|
uprintf("\n%s: warning, %s %s "
|
|
"quota exceeded\n",
|
|
mp->mnt_stat.f_mntonname,
|
|
quotatypes[i], limnames[vtype]);
|
|
dq->dq_flags |= DQ_WARN(vtype);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
* always do this; we don't know if the allocation will
|
|
* succed or not in the end. if we don't do the allocation
|
|
* q2v_time will be ignored anyway
|
|
*/
|
|
if (ql_stat & QL_F_CROSS) {
|
|
q2v.q2v_time = time_second + q2v.q2v_grace;
|
|
quota2_ufs_rwq2v(&q2v, &q2e[i]->q2e_val[vtype],
|
|
needswap);
|
|
}
|
|
}
|
|
|
|
/* now do the allocation if allowed */
|
|
for (i = 0; i < MAXQUOTAS; i++) {
|
|
dq = ip->i_dquot[i];
|
|
if (dq == NODQUOT)
|
|
continue;
|
|
KASSERT(q2e[i] != NULL);
|
|
if (error == 0) {
|
|
q2vp = &q2e[i]->q2e_val[vtype];
|
|
ncurblks = ufs_rw64(q2vp->q2v_cur, needswap);
|
|
q2vp->q2v_cur = ufs_rw64(ncurblks + change, needswap);
|
|
quota2_bwrite(mp, bp[i]);
|
|
} else
|
|
brelse(bp[i], 0);
|
|
mutex_exit(&dq->dq_interlock);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
int
|
|
chkdq2(struct inode *ip, int64_t change, kauth_cred_t cred, int flags)
|
|
{
|
|
return quota2_check(ip, QL_BLOCK, change, cred, flags);
|
|
}
|
|
|
|
int
|
|
chkiq2(struct inode *ip, int32_t change, kauth_cred_t cred, int flags)
|
|
{
|
|
return quota2_check(ip, QL_FILE, change, cred, flags);
|
|
}
|
|
|
|
int
|
|
quota2_handle_cmd_set(struct ufsmount *ump, int type, int id,
|
|
int defaultq, prop_dictionary_t data)
|
|
{
|
|
int error;
|
|
struct dquot *dq;
|
|
struct quota2_header *q2h;
|
|
struct quota2_entry q2e, *q2ep;
|
|
struct buf *bp;
|
|
const int needswap = UFS_MPNEEDSWAP(ump);
|
|
|
|
if (ump->um_quotas[type] == NULLVP)
|
|
return ENODEV;
|
|
error = UFS_WAPBL_BEGIN(ump->um_mountp);
|
|
if (error)
|
|
return error;
|
|
|
|
if (defaultq) {
|
|
mutex_enter(&dqlock);
|
|
error = getq2h(ump, type, &bp, &q2h, B_MODIFY);
|
|
if (error) {
|
|
mutex_exit(&dqlock);
|
|
goto out_wapbl;
|
|
}
|
|
quota2_ufs_rwq2e(&q2h->q2h_defentry, &q2e, needswap);
|
|
error = quota2_dict_update_q2e_limits(data, &q2e);
|
|
if (error) {
|
|
mutex_exit(&dqlock);
|
|
brelse(bp, 0);
|
|
goto out_wapbl;
|
|
}
|
|
quota2_ufs_rwq2e(&q2e, &q2h->q2h_defentry, needswap);
|
|
mutex_exit(&dqlock);
|
|
quota2_bwrite(ump->um_mountp, bp);
|
|
goto out_wapbl;
|
|
}
|
|
|
|
error = dqget(NULLVP, id, ump, type, &dq);
|
|
if (error)
|
|
goto out_wapbl;
|
|
|
|
mutex_enter(&dq->dq_interlock);
|
|
if (dq->dq2_lblkno == 0 && dq->dq2_blkoff == 0) {
|
|
/* need to alloc a new on-disk quot */
|
|
mutex_enter(&dqlock);
|
|
error = quota2_q2ealloc(ump, type, id, dq, &bp, &q2ep);
|
|
mutex_exit(&dqlock);
|
|
} else {
|
|
error = getq2e(ump, type, dq->dq2_lblkno, dq->dq2_blkoff,
|
|
&bp, &q2ep, B_MODIFY);
|
|
}
|
|
if (error)
|
|
goto out_il;
|
|
|
|
quota2_ufs_rwq2e(q2ep, &q2e, needswap);
|
|
error = quota2_dict_update_q2e_limits(data, &q2e);
|
|
if (error) {
|
|
brelse(bp, 0);
|
|
goto out_il;
|
|
}
|
|
quota2_ufs_rwq2e(&q2e, q2ep, needswap);
|
|
quota2_bwrite(ump->um_mountp, bp);
|
|
|
|
out_il:
|
|
mutex_exit(&dq->dq_interlock);
|
|
dqrele(NULLVP, dq);
|
|
out_wapbl:
|
|
UFS_WAPBL_END(ump->um_mountp);
|
|
return error;
|
|
}
|
|
|
|
struct dq2clear_callback {
|
|
uid_t id;
|
|
struct dquot *dq;
|
|
struct quota2_header *q2h;
|
|
};
|
|
|
|
static int
|
|
dq2clear_callback(struct ufsmount *ump, uint64_t *offp, struct quota2_entry *q2e,
|
|
uint64_t off, void *v)
|
|
{
|
|
struct dq2clear_callback *c = v;
|
|
#ifdef FFS_EI
|
|
const int needswap = UFS_MPNEEDSWAP(ump);
|
|
#endif
|
|
uint64_t myoff;
|
|
|
|
if (ufs_rw32(q2e->q2e_uid, needswap) == c->id) {
|
|
KASSERT(mutex_owned(&c->dq->dq_interlock));
|
|
c->dq->dq2_lblkno = 0;
|
|
c->dq->dq2_blkoff = 0;
|
|
myoff = *offp;
|
|
/* remove from hash list */
|
|
*offp = q2e->q2e_next;
|
|
/* add to free list */
|
|
q2e->q2e_next = c->q2h->q2h_free;
|
|
c->q2h->q2h_free = myoff;
|
|
return Q2WL_ABORT;
|
|
}
|
|
return 0;
|
|
}
|
|
int
|
|
quota2_handle_cmd_clear(struct ufsmount *ump, int type, int id,
|
|
int defaultq, prop_dictionary_t data)
|
|
{
|
|
int error, i;
|
|
struct dquot *dq;
|
|
struct quota2_header *q2h;
|
|
struct quota2_entry q2e, *q2ep;
|
|
struct buf *hbp, *bp;
|
|
u_long hash_mask;
|
|
struct dq2clear_callback c;
|
|
|
|
if (ump->um_quotas[type] == NULLVP)
|
|
return ENODEV;
|
|
if (defaultq)
|
|
return EOPNOTSUPP;
|
|
|
|
/* get the default entry before locking the entry's buffer */
|
|
mutex_enter(&dqlock);
|
|
error = getq2h(ump, type, &hbp, &q2h, 0);
|
|
if (error) {
|
|
mutex_exit(&dqlock);
|
|
return error;
|
|
}
|
|
/* we'll copy to another disk entry, so no need to swap */
|
|
memcpy(&q2e, &q2h->q2h_defentry, sizeof(q2e));
|
|
mutex_exit(&dqlock);
|
|
brelse(hbp, 0);
|
|
|
|
error = dqget(NULLVP, id, ump, type, &dq);
|
|
if (error)
|
|
return error;
|
|
|
|
mutex_enter(&dq->dq_interlock);
|
|
if (dq->dq2_lblkno == 0 && dq->dq2_blkoff == 0) {
|
|
/* already clear, nothing to do */
|
|
error = ENOENT;
|
|
goto out_il;
|
|
}
|
|
error = UFS_WAPBL_BEGIN(ump->um_mountp);
|
|
if (error)
|
|
goto out_dq;
|
|
|
|
error = getq2e(ump, type, dq->dq2_lblkno, dq->dq2_blkoff,
|
|
&bp, &q2ep, B_MODIFY);
|
|
if (error)
|
|
goto out_wapbl;
|
|
|
|
if (q2ep->q2e_val[QL_BLOCK].q2v_cur != 0 ||
|
|
q2ep->q2e_val[QL_FILE].q2v_cur != 0) {
|
|
/* can't free this entry; revert to default */
|
|
for (i = 0; i < N_QL; i++) {
|
|
q2ep->q2e_val[i].q2v_softlimit =
|
|
q2e.q2e_val[i].q2v_softlimit;
|
|
q2ep->q2e_val[i].q2v_hardlimit =
|
|
q2e.q2e_val[i].q2v_hardlimit;
|
|
q2ep->q2e_val[i].q2v_grace =
|
|
q2e.q2e_val[i].q2v_grace;
|
|
q2ep->q2e_val[i].q2v_time = 0;
|
|
}
|
|
quota2_bwrite(ump->um_mountp, bp);
|
|
goto out_wapbl;
|
|
}
|
|
/* we can free it. release bp so we can walk the list */
|
|
brelse(bp, 0);
|
|
mutex_enter(&dqlock);
|
|
error = getq2h(ump, type, &hbp, &q2h, 0);
|
|
if (error)
|
|
goto out_dqlock;
|
|
|
|
hash_mask = ((1 << q2h->q2h_hash_shift) - 1);
|
|
c.dq = dq;
|
|
c.id = id;
|
|
c.q2h = q2h;
|
|
error = quota2_walk_list(ump, hbp, type,
|
|
&q2h->q2h_entries[id & hash_mask], B_MODIFY, &c,
|
|
dq2clear_callback);
|
|
|
|
bwrite(hbp);
|
|
|
|
out_dqlock:
|
|
mutex_exit(&dqlock);
|
|
out_wapbl:
|
|
UFS_WAPBL_END(ump->um_mountp);
|
|
out_il:
|
|
mutex_exit(&dq->dq_interlock);
|
|
out_dq:
|
|
dqrele(NULLVP, dq);
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
quota2_array_add_q2e(struct ufsmount *ump, int type,
|
|
int id, prop_array_t replies)
|
|
{
|
|
struct dquot *dq;
|
|
int error;
|
|
struct quota2_entry *q2ep, q2e;
|
|
struct buf *bp;
|
|
const int needswap = UFS_MPNEEDSWAP(ump);
|
|
prop_dictionary_t dict;
|
|
|
|
error = dqget(NULLVP, id, ump, type, &dq);
|
|
if (error)
|
|
return error;
|
|
|
|
mutex_enter(&dq->dq_interlock);
|
|
if (dq->dq2_lblkno == 0 && dq->dq2_blkoff == 0) {
|
|
mutex_exit(&dq->dq_interlock);
|
|
dqrele(NULLVP, dq);
|
|
return ENOENT;
|
|
}
|
|
error = getq2e(ump, type, dq->dq2_lblkno, dq->dq2_blkoff,
|
|
&bp, &q2ep, 0);
|
|
if (error) {
|
|
mutex_exit(&dq->dq_interlock);
|
|
dqrele(NULLVP, dq);
|
|
return error;
|
|
}
|
|
quota2_ufs_rwq2e(q2ep, &q2e, needswap);
|
|
brelse(bp, 0);
|
|
mutex_exit(&dq->dq_interlock);
|
|
dqrele(NULLVP, dq);
|
|
dict = q2etoprop(&q2e, 0);
|
|
if (dict == NULL)
|
|
return ENOMEM;
|
|
if (!prop_array_add_and_rel(replies, dict))
|
|
return ENOMEM;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
quota2_handle_cmd_get(struct ufsmount *ump, int type, int id,
|
|
int defaultq, prop_array_t replies)
|
|
{
|
|
int error;
|
|
struct quota2_header *q2h;
|
|
struct quota2_entry q2e;
|
|
struct buf *bp;
|
|
prop_dictionary_t dict;
|
|
const int needswap = UFS_MPNEEDSWAP(ump);
|
|
|
|
if (ump->um_quotas[type] == NULLVP)
|
|
return ENODEV;
|
|
if (defaultq) {
|
|
mutex_enter(&dqlock);
|
|
error = getq2h(ump, type, &bp, &q2h, 0);
|
|
if (error) {
|
|
mutex_exit(&dqlock);
|
|
return error;
|
|
}
|
|
quota2_ufs_rwq2e(&q2h->q2h_defentry, &q2e, needswap);
|
|
mutex_exit(&dqlock);
|
|
brelse(bp, 0);
|
|
dict = q2etoprop(&q2e, defaultq);
|
|
if (dict == NULL)
|
|
return ENOMEM;
|
|
if (!prop_array_add_and_rel(replies, dict))
|
|
return ENOMEM;
|
|
} else
|
|
error = quota2_array_add_q2e(ump, type, id, replies);
|
|
|
|
return error;
|
|
}
|
|
|
|
struct getuids {
|
|
long nuids; /* number of uids in array */
|
|
long size; /* size of array */
|
|
uid_t *uids; /* array of uids, dynamically allocated */
|
|
};
|
|
|
|
static int
|
|
quota2_getuids_callback(struct ufsmount *ump, uint64_t *offp,
|
|
struct quota2_entry *q2ep, uint64_t off, void *v)
|
|
{
|
|
struct getuids *gu = v;
|
|
uid_t *newuids;
|
|
#ifdef FFS_EI
|
|
const int needswap = UFS_MPNEEDSWAP(ump);
|
|
#endif
|
|
|
|
if (gu->nuids == gu->size) {
|
|
newuids = realloc(gu->uids, gu->size + PAGE_SIZE, M_TEMP,
|
|
M_WAITOK);
|
|
if (newuids == NULL) {
|
|
free(gu->uids, M_TEMP);
|
|
return ENOMEM;
|
|
}
|
|
gu->uids = newuids;
|
|
gu->size += (PAGE_SIZE / sizeof(uid_t));
|
|
}
|
|
gu->uids[gu->nuids] = ufs_rw32(q2ep->q2e_uid, needswap);
|
|
gu->nuids++;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
quota2_handle_cmd_getall(struct ufsmount *ump, int type, prop_array_t replies)
|
|
{
|
|
int error;
|
|
struct quota2_header *q2h;
|
|
struct quota2_entry q2e;
|
|
struct buf *hbp;
|
|
prop_dictionary_t dict;
|
|
uint64_t offset;
|
|
int i, j;
|
|
int quota2_hash_size;
|
|
const int needswap = UFS_MPNEEDSWAP(ump);
|
|
struct getuids gu;
|
|
|
|
if (ump->um_quotas[type] == NULLVP)
|
|
return ENODEV;
|
|
mutex_enter(&dqlock);
|
|
error = getq2h(ump, type, &hbp, &q2h, 0);
|
|
if (error) {
|
|
mutex_exit(&dqlock);
|
|
return error;
|
|
}
|
|
quota2_ufs_rwq2e(&q2h->q2h_defentry, &q2e, needswap);
|
|
dict = q2etoprop(&q2e, 1);
|
|
if (!prop_array_add_and_rel(replies, dict)) {
|
|
error = ENOMEM;
|
|
goto error_bp;
|
|
}
|
|
/*
|
|
* we can't directly get entries as we can't walk the list
|
|
* with qdlock and grab dq_interlock to read the entries
|
|
* at the same time. So just walk the lists to build a list of uid,
|
|
* and then read entries for these uids
|
|
*/
|
|
memset(&gu, 0, sizeof(gu));
|
|
quota2_hash_size = ufs_rw16(q2h->q2h_hash_size, needswap);
|
|
for (i = 0; i < quota2_hash_size ; i++) {
|
|
offset = q2h->q2h_entries[i];
|
|
error = quota2_walk_list(ump, hbp, type, &offset, 0, &gu,
|
|
quota2_getuids_callback);
|
|
if (error) {
|
|
if (gu.uids != NULL)
|
|
free(gu.uids, M_TEMP);
|
|
break;
|
|
}
|
|
}
|
|
error_bp:
|
|
mutex_exit(&dqlock);
|
|
brelse(hbp, 0);
|
|
if (error)
|
|
return error;
|
|
for (j = 0; j < gu.nuids; j++) {
|
|
error = quota2_array_add_q2e(ump, type,
|
|
gu.uids[j], replies);
|
|
if (error && error != ENOENT)
|
|
break;
|
|
}
|
|
free(gu.uids, M_TEMP);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
q2sync(struct mount *mp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
struct dq2get_callback {
|
|
uid_t id;
|
|
struct dquot *dq;
|
|
};
|
|
|
|
static int
|
|
dq2get_callback(struct ufsmount *ump, uint64_t *offp, struct quota2_entry *q2e,
|
|
uint64_t off, void *v)
|
|
{
|
|
struct dq2get_callback *c = v;
|
|
daddr_t lblkno;
|
|
int blkoff;
|
|
#ifdef FFS_EI
|
|
const int needswap = UFS_MPNEEDSWAP(ump);
|
|
#endif
|
|
|
|
if (ufs_rw32(q2e->q2e_uid, needswap) == c->id) {
|
|
KASSERT(mutex_owned(&c->dq->dq_interlock));
|
|
lblkno = (off >> ump->um_mountp->mnt_fs_bshift);
|
|
blkoff = (off & ump->umq2_bmask);
|
|
c->dq->dq2_lblkno = lblkno;
|
|
c->dq->dq2_blkoff = blkoff;
|
|
return Q2WL_ABORT;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
dq2get(struct vnode *dqvp, u_long id, struct ufsmount *ump, int type,
|
|
struct dquot *dq)
|
|
{
|
|
struct buf *bp;
|
|
struct quota2_header *q2h;
|
|
int error;
|
|
daddr_t offset;
|
|
u_long hash_mask;
|
|
struct dq2get_callback c = {
|
|
.id = id,
|
|
.dq = dq
|
|
};
|
|
|
|
KASSERT(mutex_owned(&dq->dq_interlock));
|
|
mutex_enter(&dqlock);
|
|
error = getq2h(ump, type, &bp, &q2h, 0);
|
|
if (error)
|
|
goto out_mutex;
|
|
/* look for our entry */
|
|
hash_mask = ((1 << q2h->q2h_hash_shift) - 1);
|
|
offset = q2h->q2h_entries[id & hash_mask];
|
|
error = quota2_walk_list(ump, bp, type, &offset, 0, (void *)&c,
|
|
dq2get_callback);
|
|
brelse(bp, 0);
|
|
out_mutex:
|
|
mutex_exit(&dqlock);
|
|
return error;
|
|
}
|
|
|
|
int
|
|
dq2sync(struct vnode *vp, struct dquot *dq)
|
|
{
|
|
return 0;
|
|
}
|