#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ufs_quota2.c,v 1.48 2026/01/22 03:24:19 riastradh Exp $");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/buf.h>
#include <sys/file.h>
#include <sys/kauth.h>
#include <sys/kernel.h>
#include <sys/mount.h>
#include <sys/namei.h>
#include <sys/proc.h>
#include <sys/quota.h>
#include <sys/quotactl.h>
#include <sys/sdt.h>
#include <sys/systm.h>
#include <sys/vnode.h>
#include <sys/wapbl.h>
#include <ufs/ufs/inode.h>
#include <ufs/ufs/quota2.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 <ufs/ufs/ufsmount.h>
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 const char *limnames[] = INITQLNAMES;
static void
quota2_dict_update_q2e_limits(int objtype, const struct quotaval *val,
struct quota2_entry *q2e)
{
CTASSERT(QUOTA_OBJTYPE_BLOCKS == QL_BLOCK);
CTASSERT(QUOTA_OBJTYPE_FILES == QL_FILE);
q2e->q2e_val[objtype].q2v_hardlimit = val->qv_hardlimit;
q2e->q2e_val[objtype].q2v_softlimit = val->qv_softlimit;
q2e->q2e_val[objtype].q2v_grace = val->qv_grace;
}
static void
q2val_to_quotaval(struct quota2_val *q2v, struct quotaval *qv)
{
qv->qv_softlimit = q2v->q2v_softlimit;
qv->qv_hardlimit = q2v->q2v_hardlimit;
qv->qv_usage = q2v->q2v_cur;
qv->qv_expiretime = q2v->q2v_time;
qv->qv_grace = q2v->q2v_grace;
}
static void
q2e_to_quotaval(struct quota2_entry *q2e, int def,
id_t *id, int objtype, struct quotaval *ret)
{
if (def) {
*id = QUOTA_DEFAULTID;
} else {
*id = q2e->q2e_uid;
}
KASSERT(objtype >= 0 && objtype < N_QL);
q2val_to_quotaval(&q2e->q2e_val[objtype], ret);
}
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)
{
const int needswap = UFS_MPNEEDSWAP(ump);
int error;
struct buf *bp;
struct quota2_header *q2h;
KASSERT(mutex_owned(&dqlock));
error = bread(ump->um_quotas[type], 0, ump->umq2_bsize,
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,
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;
}
#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 *))
{
const int needswap = UFS_MPNEEDSWAP(ump);
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_owned(&dqlock));
while (off != 0) {
lblkno = (off >> ump->um_mountp->mnt_fs_bshift);
blkoff = (off & ump->umq2_bmask);
if (lblkno == 0) {
bp = hbp;
} else if (lblkno == olblkno) {
bp = obp;
} else {
ret = bread(ump->um_quotas[type], lblkno,
ump->umq2_bsize, flags, &bp);
if (ret)
return ret;
if (bp->b_resid != 0) {
panic("%s: %s quota file corrupted",
__func__, quotatypes[type]);
}
}
q2e = (void *)((char *)(bp->b_data) + blkoff);
ret = (*func)(ump, offp, q2e, off, a);
if (off != ufs_rw64(*offp, needswap)) {
off = ufs_rw64(*offp, needswap);
if (bp != hbp && bp != obp)
ret2 = bwrite(bp);
} else {
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)
{
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;
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);
q2h->q2h_free = q2e->q2e_next;
memcpy(q2e, &q2h->q2h_defentry, sizeof(*q2e));
q2e->q2e_uid = ufs_rw32(uid, needswap);
q2e->q2e_next = q2h->q2h_entries[uid & hash_mask];
q2h->q2h_entries[uid & hash_mask] = ufs_rw64(offset, needswap);
if (hbp != bp) {
bwrite(hbp);
}
bwrite(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;
for (i = 0; i < MAXQUOTAS; i++) {
dq = ip->i_dquot[i];
if (dq == NODQUOT)
continue;
mutex_enter(&dq->dq_interlock);
}
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)) {
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;
}
mutex_enter(&dqlock);
error = quota2_q2ealloc(ump, i, ino_ids[i], dq);
mutex_exit(&dqlock);
if (error)
return error;
}
KASSERT(dq->dq2_lblkno != 0 || dq->dq2_blkoff != 0);
error = getq2e(ump, i, dq->dq2_lblkno,
dq->dq2_blkoff, &bpp[i], &q2ep[i],
modify ? B_MODIFY : 0);
if (error)
return error;
}
return 0;
}
__inline static int __unused
quota2_check_limit(struct quota2_val *q2v, uint64_t change, time_t now)
{
return quota_check_limit(q2v->q2v_cur, change, q2v->q2v_softlimit,
q2v->q2v_hardlimit, q2v->q2v_time, now);
}
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;
}
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) {
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 = SET_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 = SET_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;
}
}
if (ql_stat & QL_F_CROSS) {
q2v.q2v_time = time_second + q2v.q2v_grace;
quota2_ufs_rwq2v(&q2v, &q2e[i]->q2e_val[vtype],
needswap);
}
}
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_put(struct ufsmount *ump, const struct quotakey *key,
const struct quotaval *val)
{
int error;
struct dquot *dq;
struct quota2_header *q2h;
struct quota2_entry q2e, *q2ep;
struct buf *bp;
const int needswap = UFS_MPNEEDSWAP(ump);
CTASSERT(QUOTA_IDTYPE_USER == USRQUOTA);
CTASSERT(QUOTA_IDTYPE_GROUP == GRPQUOTA);
if (ump->um_quotas[key->qk_idtype] == NULLVP)
return SET_ERROR(ENODEV);
error = UFS_WAPBL_BEGIN(ump->um_mountp);
if (error)
return error;
if (key->qk_id == QUOTA_DEFAULTID) {
mutex_enter(&dqlock);
error = getq2h(ump, key->qk_idtype, &bp, &q2h, B_MODIFY);
if (error) {
mutex_exit(&dqlock);
goto out_wapbl;
}
quota2_ufs_rwq2e(&q2h->q2h_defentry, &q2e, needswap);
quota2_dict_update_q2e_limits(key->qk_objtype, val, &q2e);
quota2_ufs_rwq2e(&q2e, &q2h->q2h_defentry, needswap);
mutex_exit(&dqlock);
quota2_bwrite(ump->um_mountp, bp);
goto out_wapbl;
}
error = dqget(NULLVP, key->qk_id, ump, key->qk_idtype, &dq);
if (error)
goto out_wapbl;
mutex_enter(&dq->dq_interlock);
if (dq->dq2_lblkno == 0 && dq->dq2_blkoff == 0) {
mutex_enter(&dqlock);
error = quota2_q2ealloc(ump, key->qk_idtype, key->qk_id, dq);
mutex_exit(&dqlock);
if (error)
goto out_il;
}
KASSERT(dq->dq2_lblkno != 0 || dq->dq2_blkoff != 0);
error = getq2e(ump, key->qk_idtype, dq->dq2_lblkno,
dq->dq2_blkoff, &bp, &q2ep, B_MODIFY);
if (error)
goto out_il;
quota2_ufs_rwq2e(q2ep, &q2e, needswap);
if (val->qv_softlimit &&
q2e.q2e_val[key->qk_objtype].q2v_cur >= val->qv_softlimit &&
(q2e.q2e_val[key->qk_objtype].q2v_softlimit == 0 ||
(q2e.q2e_val[key->qk_objtype].q2v_cur <
q2e.q2e_val[key->qk_objtype].q2v_softlimit))) {
q2e.q2e_val[key->qk_objtype].q2v_time =
time_second + val->qv_grace;
}
quota2_dict_update_q2e_limits(key->qk_objtype, val, &q2e);
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;
const int needswap = UFS_MPNEEDSWAP(ump);
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;
*offp = q2e->q2e_next;
q2e->q2e_next = c->q2h->q2h_free;
c->q2h->q2h_free = myoff;
return Q2WL_ABORT;
}
return 0;
}
int
quota2_handle_cmd_del(struct ufsmount *ump, const struct quotakey *qk)
{
int idtype;
id_t id;
int objtype;
int error, i, canfree;
struct dquot *dq;
struct quota2_header *q2h;
struct quota2_entry q2e, *q2ep;
struct buf *hbp, *bp;
u_long hash_mask;
struct dq2clear_callback c;
idtype = qk->qk_idtype;
id = qk->qk_id;
objtype = qk->qk_objtype;
if (ump->um_quotas[idtype] == NULLVP)
return SET_ERROR(ENODEV);
if (id == QUOTA_DEFAULTID)
return SET_ERROR(EOPNOTSUPP);
mutex_enter(&dqlock);
error = getq2h(ump, idtype, &hbp, &q2h, 0);
if (error) {
mutex_exit(&dqlock);
return error;
}
memcpy(&q2e, &q2h->q2h_defentry, sizeof(q2e));
mutex_exit(&dqlock);
brelse(hbp, 0);
error = dqget(NULLVP, id, ump, idtype, &dq);
if (error)
return error;
mutex_enter(&dq->dq_interlock);
if (dq->dq2_lblkno == 0 && dq->dq2_blkoff == 0) {
error = SET_ERROR(ENOENT);
goto out_il;
}
error = UFS_WAPBL_BEGIN(ump->um_mountp);
if (error)
goto out_dq;
error = getq2e(ump, idtype, dq->dq2_lblkno, dq->dq2_blkoff,
&bp, &q2ep, B_MODIFY);
if (error)
goto out_wapbl;
CTASSERT(QUOTA_OBJTYPE_BLOCKS == QL_BLOCK);
CTASSERT(QUOTA_OBJTYPE_FILES == QL_FILE);
q2ep->q2e_val[objtype].q2v_softlimit =
q2e.q2e_val[objtype].q2v_softlimit;
q2ep->q2e_val[objtype].q2v_hardlimit =
q2e.q2e_val[objtype].q2v_hardlimit;
q2ep->q2e_val[objtype].q2v_grace =
q2e.q2e_val[objtype].q2v_grace;
q2ep->q2e_val[objtype].q2v_time = 0;
canfree = 1;
for (i = 0; i < N_QL; i++) {
if (q2ep->q2e_val[i].q2v_cur != 0 ||
(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)) {
canfree = 0;
break;
}
}
if (canfree == 0) {
quota2_bwrite(ump->um_mountp, bp);
goto out_wapbl;
}
brelse(bp, 0);
mutex_enter(&dqlock);
error = getq2h(ump, idtype, &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, idtype,
&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_fetch_q2e(struct ufsmount *ump, const struct quotakey *qk,
struct quota2_entry *ret)
{
struct dquot *dq;
int error;
struct quota2_entry *q2ep;
struct buf *bp;
const int needswap = UFS_MPNEEDSWAP(ump);
error = dqget(NULLVP, qk->qk_id, ump, qk->qk_idtype, &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 SET_ERROR(ENOENT);
}
error = getq2e(ump, qk->qk_idtype, 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, ret, needswap);
brelse(bp, 0);
mutex_exit(&dq->dq_interlock);
dqrele(NULLVP, dq);
return 0;
}
static int
quota2_fetch_quotaval(struct ufsmount *ump, const struct quotakey *qk,
struct quotaval *ret)
{
struct dquot *dq;
int error;
struct quota2_entry *q2ep, q2e;
struct buf *bp;
const int needswap = UFS_MPNEEDSWAP(ump);
id_t id2;
error = dqget(NULLVP, qk->qk_id, ump, qk->qk_idtype, &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 SET_ERROR(ENOENT);
}
error = getq2e(ump, qk->qk_idtype, 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);
q2e_to_quotaval(&q2e, 0, &id2, qk->qk_objtype, ret);
KASSERT(id2 == qk->qk_id);
return 0;
}
int
quota2_handle_cmd_get(struct ufsmount *ump, const struct quotakey *qk,
struct quotaval *qv)
{
int error;
struct quota2_header *q2h;
struct quota2_entry q2e;
struct buf *bp;
const int needswap = UFS_MPNEEDSWAP(ump);
id_t id2;
CTASSERT(QL_BLOCK == QUOTA_OBJTYPE_BLOCKS);
CTASSERT(QL_FILE == QUOTA_OBJTYPE_FILES);
CTASSERT(N_QL == 2);
if (qk->qk_objtype < 0 || qk->qk_objtype >= N_QL) {
return SET_ERROR(EINVAL);
}
if (ump->um_quotas[qk->qk_idtype] == NULLVP)
return SET_ERROR(ENODEV);
if (qk->qk_id == QUOTA_DEFAULTID) {
mutex_enter(&dqlock);
error = getq2h(ump, qk->qk_idtype, &bp, &q2h, 0);
if (error) {
mutex_exit(&dqlock);
return error;
}
quota2_ufs_rwq2e(&q2h->q2h_defentry, &q2e, needswap);
mutex_exit(&dqlock);
brelse(bp, 0);
q2e_to_quotaval(&q2e, qk->qk_id == QUOTA_DEFAULTID, &id2,
qk->qk_objtype, qv);
(void)id2;
} else
error = quota2_fetch_quotaval(ump, qk, qv);
return error;
}
struct ufsq2_cursor {
uint32_t q2c_magic;
int q2c_hashsize;
int q2c_users_done;
int q2c_groups_done;
int q2c_defaults_done;
int q2c_hashpos;
int q2c_uidpos;
int q2c_blocks_done;
};
struct q2cursor_state {
struct quotakey *keys;
struct quotaval *vals;
unsigned maxkeyvals;
unsigned numkeys;
int skipfirst;
int skiplast;
unsigned maxids;
unsigned numids;
};
struct q2cursor_getids {
struct q2cursor_state *state;
int idtype;
unsigned skip;
unsigned new_skip;
unsigned skipped;
int stopped;
};
#define Q2C_MAGIC (0xbeebe111)
#define Q2CURSOR(qkc) ((struct ufsq2_cursor *)&qkc->u.qkc_space[0])
static int
q2cursor_check(struct ufsq2_cursor *cursor)
{
if (cursor->q2c_magic != Q2C_MAGIC) {
return SET_ERROR(EINVAL);
}
if (cursor->q2c_hashsize < 0) {
return SET_ERROR(EINVAL);
}
if (cursor->q2c_users_done != 0 && cursor->q2c_users_done != 1) {
return SET_ERROR(EINVAL);
}
if (cursor->q2c_groups_done != 0 && cursor->q2c_groups_done != 1) {
return SET_ERROR(EINVAL);
}
if (cursor->q2c_defaults_done != 0 && cursor->q2c_defaults_done != 1) {
return SET_ERROR(EINVAL);
}
if (cursor->q2c_hashpos < 0 || cursor->q2c_uidpos < 0) {
return SET_ERROR(EINVAL);
}
if (cursor->q2c_blocks_done != 0 && cursor->q2c_blocks_done != 1) {
return SET_ERROR(EINVAL);
}
return 0;
}
static void
q2cursor_initstate(struct q2cursor_state *state, struct quotakey *keys,
struct quotaval *vals, unsigned maxkeyvals, int blocks_done)
{
state->keys = keys;
state->vals = vals;
state->maxkeyvals = maxkeyvals;
state->numkeys = 0;
state->maxids = (state->maxkeyvals + 1) / 2;
state->numids = 0;
if (state->maxkeyvals % 2) {
if (blocks_done) {
state->skipfirst = 1;
state->skiplast = 0;
} else {
state->skipfirst = 0;
state->skiplast = 1;
}
} else {
state->skipfirst = 0;
state->skiplast = 0;
}
}
static int
q2cursor_pickidtype(struct ufsq2_cursor *cursor, int *idtype_ret)
{
if (cursor->q2c_users_done == 0) {
*idtype_ret = QUOTA_IDTYPE_USER;
} else if (cursor->q2c_groups_done == 0) {
*idtype_ret = QUOTA_IDTYPE_GROUP;
} else {
return SET_ERROR(EAGAIN);
}
return 0;
}
static void
q2cursor_addid(struct q2cursor_state *state, int idtype, id_t id)
{
KASSERT(state->numids < state->maxids);
KASSERT(state->numkeys < state->maxkeyvals);
if (!state->skipfirst || state->numkeys > 0) {
state->keys[state->numkeys].qk_idtype = idtype;
state->keys[state->numkeys].qk_id = id;
state->keys[state->numkeys].qk_objtype = QUOTA_OBJTYPE_BLOCKS;
state->numkeys++;
}
if (state->numkeys < state->maxkeyvals) {
state->keys[state->numkeys].qk_idtype = idtype;
state->keys[state->numkeys].qk_id = id;
state->keys[state->numkeys].qk_objtype = QUOTA_OBJTYPE_FILES;
state->numkeys++;
} else {
KASSERT(state->skiplast);
}
state->numids++;
}
static int
q2cursor_getids_callback(struct ufsmount *ump, uint64_t *offp,
struct quota2_entry *q2ep, uint64_t off, void *v)
{
struct q2cursor_getids *gi = v;
id_t id;
const int needswap = UFS_MPNEEDSWAP(ump);
if (gi->skipped < gi->skip) {
gi->skipped++;
return 0;
}
id = ufs_rw32(q2ep->q2e_uid, needswap);
q2cursor_addid(gi->state, gi->idtype, id);
gi->new_skip++;
if (gi->state->numids >= gi->state->maxids) {
gi->stopped = 1;
return Q2WL_ABORT;
}
return 0;
}
static int
q2cursor_getkeys(struct ufsmount *ump, int idtype, struct ufsq2_cursor *cursor,
struct q2cursor_state *state,
int *hashsize_ret, struct quota2_entry *default_q2e_ret)
{
const int needswap = UFS_MPNEEDSWAP(ump);
struct buf *hbp;
struct quota2_header *q2h;
int quota2_hash_size;
struct q2cursor_getids gi;
uint64_t offset;
int error;
mutex_enter(&dqlock);
error = getq2h(ump, idtype, &hbp, &q2h, 0);
if (error) {
mutex_exit(&dqlock);
return error;
}
quota2_hash_size = ufs_rw16(q2h->q2h_hash_size, needswap);
if (cursor->q2c_hashsize == 0) {
cursor->q2c_hashsize = quota2_hash_size;
} else if (cursor->q2c_hashsize != quota2_hash_size) {
error = SET_ERROR(EDEADLK);
goto scanfail;
}
quota2_ufs_rwq2e(&q2h->q2h_defentry, default_q2e_ret, needswap);
if (cursor->q2c_defaults_done == 0) {
q2cursor_addid(state, idtype, QUOTA_DEFAULTID);
if (state->numids < state->maxids || !state->skiplast) {
cursor->q2c_defaults_done = 1;
}
}
gi.state = state;
gi.idtype = idtype;
while (state->numids < state->maxids) {
if (cursor->q2c_hashpos >= quota2_hash_size) {
break;
}
gi.skip = cursor->q2c_uidpos;
gi.new_skip = gi.skip;
gi.skipped = 0;
gi.stopped = 0;
offset = q2h->q2h_entries[cursor->q2c_hashpos];
error = quota2_walk_list(ump, hbp, idtype, &offset, 0, &gi,
q2cursor_getids_callback);
KASSERT(error != Q2WL_ABORT);
if (error) {
break;
}
if (gi.stopped) {
cursor->q2c_uidpos = gi.new_skip;
if (state->numids == state->maxids && state->skiplast){
KASSERT(cursor->q2c_uidpos > 0);
cursor->q2c_uidpos--;
}
} else {
if (state->numids < state->maxids || !state->skiplast){
cursor->q2c_uidpos = 0;
cursor->q2c_hashpos++;
}
}
}
scanfail:
mutex_exit(&dqlock);
brelse(hbp, 0);
if (error)
return error;
*hashsize_ret = quota2_hash_size;
return 0;
}
static int
q2cursor_getvals(struct ufsmount *ump, struct q2cursor_state *state,
const struct quota2_entry *default_q2e)
{
int hasid;
id_t loadedid, id;
unsigned pos;
struct quota2_entry q2e;
int objtype;
int error;
hasid = 0;
loadedid = 0;
for (pos = 0; pos < state->numkeys; pos++) {
id = state->keys[pos].qk_id;
if (!hasid || id != loadedid) {
hasid = 1;
loadedid = id;
if (id == QUOTA_DEFAULTID) {
q2e = *default_q2e;
} else {
error = quota2_fetch_q2e(ump,
&state->keys[pos],
&q2e);
if (error == ENOENT) {
error = SET_ERROR(EDEADLK);
}
if (error) {
return error;
}
}
}
objtype = state->keys[pos].qk_objtype;
KASSERT(objtype >= 0 && objtype < N_QL);
q2val_to_quotaval(&q2e.q2e_val[objtype], &state->vals[pos]);
}
return 0;
}
int
quota2_handle_cmd_cursorget(struct ufsmount *ump, struct quotakcursor *qkc,
struct quotakey *keys, struct quotaval *vals, unsigned maxreturn,
unsigned *ret)
{
int error;
struct ufsq2_cursor *cursor;
struct ufsq2_cursor newcursor;
struct q2cursor_state state;
struct quota2_entry default_q2e;
int idtype;
int quota2_hash_size = 0;
cursor = Q2CURSOR(qkc);
error = q2cursor_check(cursor);
if (error) {
return error;
}
CTASSERT(QUOTA_IDTYPE_USER == USRQUOTA);
CTASSERT(QUOTA_IDTYPE_GROUP == GRPQUOTA);
CTASSERT(QUOTA_OBJTYPE_BLOCKS == QL_BLOCK);
CTASSERT(QUOTA_OBJTYPE_FILES == QL_FILE);
if (cursor->q2c_users_done == 0 &&
ump->um_quotas[USRQUOTA] == NULLVP) {
cursor->q2c_users_done = 1;
}
if (cursor->q2c_groups_done == 0 &&
ump->um_quotas[GRPQUOTA] == NULLVP) {
cursor->q2c_groups_done = 1;
}
while (1) {
error = q2cursor_pickidtype(cursor, &idtype);
if (error == EAGAIN) {
*ret = 0;
return 0;
}
KASSERT(ump->um_quotas[idtype] != NULLVP);
q2cursor_initstate(&state, keys, vals, maxreturn,
cursor->q2c_blocks_done);
newcursor = *cursor;
error = q2cursor_getkeys(ump, idtype, &newcursor, &state,
"a2_hash_size, &default_q2e);
if (error) {
return error;
}
error = q2cursor_getvals(ump, &state, &default_q2e);
if (error) {
return error;
}
if (newcursor.q2c_hashpos >= quota2_hash_size) {
if (idtype == QUOTA_IDTYPE_USER)
cursor->q2c_users_done = 1;
else
cursor->q2c_groups_done = 1;
cursor->q2c_hashsize = 0;
cursor->q2c_defaults_done = 0;
cursor->q2c_hashpos = 0;
cursor->q2c_uidpos = 0;
cursor->q2c_blocks_done = 0;
} else {
*cursor = newcursor;
cursor->q2c_blocks_done = state.skiplast;
}
if (state.numkeys > 0) {
break;
}
}
*ret = state.numkeys;
return 0;
}
int
quota2_handle_cmd_cursoropen(struct ufsmount *ump, struct quotakcursor *qkc)
{
struct ufsq2_cursor *cursor;
CTASSERT(sizeof(*cursor) <= sizeof(qkc->u.qkc_space));
cursor = Q2CURSOR(qkc);
cursor->q2c_magic = Q2C_MAGIC;
cursor->q2c_hashsize = 0;
cursor->q2c_users_done = 0;
cursor->q2c_groups_done = 0;
cursor->q2c_defaults_done = 0;
cursor->q2c_hashpos = 0;
cursor->q2c_uidpos = 0;
cursor->q2c_blocks_done = 0;
return 0;
}
int
quota2_handle_cmd_cursorclose(struct ufsmount *ump, struct quotakcursor *qkc)
{
struct ufsq2_cursor *cursor;
int error;
cursor = Q2CURSOR(qkc);
error = q2cursor_check(cursor);
if (error) {
return error;
}
return 0;
}
int
quota2_handle_cmd_cursorskipidtype(struct ufsmount *ump,
struct quotakcursor *qkc, int idtype)
{
struct ufsq2_cursor *cursor;
int error;
cursor = Q2CURSOR(qkc);
error = q2cursor_check(cursor);
if (error) {
return error;
}
switch (idtype) {
case QUOTA_IDTYPE_USER:
cursor->q2c_users_done = 1;
break;
case QUOTA_IDTYPE_GROUP:
cursor->q2c_groups_done = 1;
break;
default:
return SET_ERROR(EINVAL);
}
return 0;
}
int
quota2_handle_cmd_cursoratend(struct ufsmount *ump, struct quotakcursor *qkc,
int *ret)
{
struct ufsq2_cursor *cursor;
int error;
cursor = Q2CURSOR(qkc);
error = q2cursor_check(cursor);
if (error) {
return error;
}
*ret = (cursor->q2c_users_done && cursor->q2c_groups_done);
return 0;
}
int
quota2_handle_cmd_cursorrewind(struct ufsmount *ump, struct quotakcursor *qkc)
{
struct ufsq2_cursor *cursor;
int error;
cursor = Q2CURSOR(qkc);
error = q2cursor_check(cursor);
if (error) {
return error;
}
cursor->q2c_hashsize = 0;
cursor->q2c_users_done = 0;
cursor->q2c_groups_done = 0;
cursor->q2c_defaults_done = 0;
cursor->q2c_hashpos = 0;
cursor->q2c_uidpos = 0;
cursor->q2c_blocks_done = 0;
return 0;
}
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;
const int needswap = UFS_MPNEEDSWAP(ump);
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;
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;
}