#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/mount.h>
#include <sys/malloc.h>
#include <sys/file.h>
#include <sys/proc.h>
#include <sys/kauth.h>
#include <sys/vnode.h>
#include <sys/vnode_internal.h>
#include <sys/quota.h>
#include <sys/proc_internal.h>
#include <kern/kalloc.h>
#include <hfs/hfs.h>
#include <hfs/hfs_cnode.h>
#include <hfs/hfs_quota.h>
#include <hfs/hfs_mount.h>
#if 0
static char *quotatypes[] = INITQFNAMES;
#endif
int
hfs_getinoquota(cp)
register struct cnode *cp;
{
struct hfsmount *hfsmp;
struct vnode *vp;
int error;
vp = cp->c_vp ? cp->c_vp : cp->c_rsrc_vp;
hfsmp = VTOHFS(vp);
if (cp->c_dquot[USRQUOTA] == NODQUOT &&
(error =
dqget(cp->c_uid, &hfsmp->hfs_qfiles[USRQUOTA], USRQUOTA, &cp->c_dquot[USRQUOTA])) &&
error != EINVAL)
return (error);
if (cp->c_dquot[GRPQUOTA] == NODQUOT &&
(error =
dqget(cp->c_gid, &hfsmp->hfs_qfiles[GRPQUOTA], GRPQUOTA, &cp->c_dquot[GRPQUOTA])) &&
error != EINVAL)
return (error);
return (0);
}
int
hfs_chkdq(cp, change, cred, flags)
register struct cnode *cp;
int64_t change;
kauth_cred_t cred;
int flags;
{
register struct dquot *dq;
register int i;
int64_t ncurbytes;
int error=0;
struct proc *p;
#if DIAGNOSTIC
if ((flags & CHOWN) == 0)
hfs_chkdquot(cp);
#endif
if (change == 0)
return (0);
if (change < 0) {
for (i = 0; i < MAXQUOTAS; i++) {
if ((dq = cp->c_dquot[i]) == NODQUOT)
continue;
dqlock(dq);
ncurbytes = dq->dq_curbytes + change;
if (ncurbytes >= 0)
dq->dq_curbytes = ncurbytes;
else
dq->dq_curbytes = 0;
dq->dq_flags &= ~DQ_BLKS;
dq->dq_flags |= DQ_MOD;
dqunlock(dq);
}
return (0);
}
p = current_proc();
if (!IS_VALID_CRED(cred))
cred = proc_ucred(kernproc);
if (suser(cred, NULL) || proc_forcequota(p)) {
for (i = 0; i < MAXQUOTAS; i++) {
if ((dq = cp->c_dquot[i]) == NODQUOT)
continue;
error = hfs_chkdqchg(cp, change, cred, i);
if (error) {
break;
}
}
}
if ((flags & FORCE) || error == 0) {
for (i = 0; i < MAXQUOTAS; i++) {
if ((dq = cp->c_dquot[i]) == NODQUOT)
continue;
dqlock(dq);
dq->dq_curbytes += change;
dq->dq_flags |= DQ_MOD;
dqunlock(dq);
}
}
return (error);
}
int
hfs_chkdqchg(cp, change, cred, type)
struct cnode *cp;
int64_t change;
kauth_cred_t cred;
int type;
{
register struct dquot *dq = cp->c_dquot[type];
u_int64_t ncurbytes;
struct vnode *vp = cp->c_vp ? cp->c_vp : cp->c_rsrc_vp;
dqlock(dq);
ncurbytes = dq->dq_curbytes + change;
if (ncurbytes >= dq->dq_bhardlimit && dq->dq_bhardlimit) {
if ((dq->dq_flags & DQ_BLKS) == 0 &&
cp->c_uid == kauth_cred_getuid(cred)) {
#if 0
printf("\nhfs: write failed, %s disk limit reached\n",
quotatypes[type]);
#endif
dq->dq_flags |= DQ_BLKS;
}
dqunlock(dq);
return (EDQUOT);
}
if (ncurbytes >= dq->dq_bsoftlimit && dq->dq_bsoftlimit) {
struct timeval tv;
microuptime(&tv);
if (dq->dq_curbytes < dq->dq_bsoftlimit) {
dq->dq_btime = tv.tv_sec +
VTOHFS(vp)->hfs_qfiles[type].qf_btime;
#if 0
if (cp->c_uid == kauth_cred_getuid(cred))
printf("\nhfs: warning, %s %s\n",
quotatypes[type], "disk quota exceeded");
#endif
dqunlock(dq);
return (0);
}
if (tv.tv_sec > (time_t)dq->dq_btime) {
if ((dq->dq_flags & DQ_BLKS) == 0 &&
cp->c_uid == kauth_cred_getuid(cred)) {
#if 0
printf("\nhfs: write failed, %s %s\n",
quotatypes[type],
"disk quota exceeded for too long");
#endif
dq->dq_flags |= DQ_BLKS;
}
dqunlock(dq);
return (EDQUOT);
}
}
dqunlock(dq);
return (0);
}
int
hfs_chkiq(cp, change, cred, flags)
register struct cnode *cp;
int32_t change;
kauth_cred_t cred;
int flags;
{
register struct dquot *dq;
register int i;
int ncurinodes, error=0;
struct proc *p;
#if DIAGNOSTIC
if ((flags & CHOWN) == 0)
hfs_chkdquot(cp);
#endif
if (change == 0)
return (0);
if (change < 0) {
for (i = 0; i < MAXQUOTAS; i++) {
if ((dq = cp->c_dquot[i]) == NODQUOT)
continue;
dqlock(dq);
ncurinodes = dq->dq_curinodes + change;
if (ncurinodes >= 0)
dq->dq_curinodes = ncurinodes;
else
dq->dq_curinodes = 0;
dq->dq_flags &= ~DQ_INODS;
dq->dq_flags |= DQ_MOD;
dqunlock(dq);
}
return (0);
}
p = current_proc();
if (!IS_VALID_CRED(cred))
cred = proc_ucred(kernproc);
if (suser(cred, NULL) || proc_forcequota(p)) {
for (i = 0; i < MAXQUOTAS; i++) {
if ((dq = cp->c_dquot[i]) == NODQUOT)
continue;
error = hfs_chkiqchg(cp, change, cred, i);
if (error) {
break;
}
}
}
if ((flags & FORCE) || error == 0) {
for (i = 0; i < MAXQUOTAS; i++) {
if ((dq = cp->c_dquot[i]) == NODQUOT)
continue;
dqlock(dq);
dq->dq_curinodes += change;
dq->dq_flags |= DQ_MOD;
dqunlock(dq);
}
}
return (error);
}
int hfs_isiqchg_allowed(dq, hfsmp, change, cred, type, uid)
struct dquot* dq;
struct hfsmount* hfsmp;
int32_t change;
kauth_cred_t cred;
int type;
uid_t uid;
{
u_int32_t ncurinodes;
dqlock(dq);
ncurinodes = dq->dq_curinodes + change;
if (ncurinodes >= dq->dq_ihardlimit && dq->dq_ihardlimit) {
if ((dq->dq_flags & DQ_INODS) == 0 &&
uid == kauth_cred_getuid(cred)) {
dq->dq_flags |= DQ_INODS;
}
dqunlock(dq);
return (EDQUOT);
}
if (ncurinodes >= dq->dq_isoftlimit && dq->dq_isoftlimit) {
struct timeval tv;
microuptime(&tv);
if (dq->dq_curinodes < dq->dq_isoftlimit) {
dq->dq_itime = tv.tv_sec + hfsmp->hfs_qfiles[type].qf_itime;
dqunlock(dq);
return (0);
}
if (tv.tv_sec > (time_t)dq->dq_itime) {
if (((dq->dq_flags & DQ_INODS) == 0) &&
(uid == kauth_cred_getuid(cred))) {
dq->dq_flags |= DQ_INODS;
}
dqunlock(dq);
return (EDQUOT);
}
}
dqunlock(dq);
return (0);
}
int
hfs_chkiqchg(cp, change, cred, type)
struct cnode *cp;
int32_t change;
kauth_cred_t cred;
int type;
{
register struct dquot *dq = cp->c_dquot[type];
u_int32_t ncurinodes;
struct vnode *vp = cp->c_vp ? cp->c_vp : cp->c_rsrc_vp;
dqlock(dq);
ncurinodes = dq->dq_curinodes + change;
if (ncurinodes >= dq->dq_ihardlimit && dq->dq_ihardlimit) {
if ((dq->dq_flags & DQ_INODS) == 0 &&
cp->c_uid == kauth_cred_getuid(cred)) {
#if 0
printf("\nhfs: write failed, %s cnode limit reached\n",
quotatypes[type]);
#endif
dq->dq_flags |= DQ_INODS;
}
dqunlock(dq);
return (EDQUOT);
}
if (ncurinodes >= dq->dq_isoftlimit && dq->dq_isoftlimit) {
struct timeval tv;
microuptime(&tv);
if (dq->dq_curinodes < dq->dq_isoftlimit) {
dq->dq_itime = tv.tv_sec +
VTOHFS(vp)->hfs_qfiles[type].qf_itime;
#if 0
if (cp->c_uid == kauth_cred_getuid(cred))
printf("\nhfs: warning, %s %s\n",
quotatypes[type], "cnode quota exceeded");
#endif
dqunlock(dq);
return (0);
}
if (tv.tv_sec > (time_t)dq->dq_itime) {
if ((dq->dq_flags & DQ_INODS) == 0 &&
cp->c_uid == kauth_cred_getuid(cred)) {
#if 0
printf("\nhfs: write failed, %s %s\n",
quotatypes[type],
"cnode quota exceeded for too long");
#endif
dq->dq_flags |= DQ_INODS;
}
dqunlock(dq);
return (EDQUOT);
}
}
dqunlock(dq);
return (0);
}
#if DIAGNOSTIC
void
hfs_chkdquot(cp)
register struct cnode *cp;
{
struct vnode *vp = cp->c_vp ? cp->c_vp : cp->c_rsrc_vp;
struct hfsmount *hfsmp = VTOHFS(vp);
register int i;
for (i = 0; i < MAXQUOTAS; i++) {
if (hfsmp->hfs_qfiles[i].qf_vp == NULLVP)
continue;
if (cp->c_dquot[i] == NODQUOT) {
vprint("chkdquot: missing dquot", vp);
panic("missing dquot");
}
}
}
#endif
struct hfs_quotaon_cargs {
int error;
};
static int
hfs_quotaon_callback(struct vnode *vp, void *cargs)
{
struct hfs_quotaon_cargs *args;
args = (struct hfs_quotaon_cargs *)cargs;
args->error = hfs_getinoquota(VTOC(vp));
if (args->error)
return (VNODE_RETURNED_DONE);
return (VNODE_RETURNED);
}
int
hfs_quotaon(p, mp, type, fnamep)
struct proc *p;
struct mount *mp;
register int type;
caddr_t fnamep;
{
struct hfsmount *hfsmp = VFSTOHFS(mp);
struct quotafile *qfp;
struct vnode *vp;
int error = 0;
struct hfs_quotaon_cargs args;
dqhashinit();
qfp = &hfsmp->hfs_qfiles[type];
if ( (qf_get(qfp, QTF_OPENING)) )
return (0);
error = vnode_open(fnamep, FREAD|FWRITE, 0, 0, &vp, NULL);
if (error) {
goto out;
}
if (!vnode_isreg(vp)) {
(void) vnode_close(vp, FREAD|FWRITE, NULL);
error = EACCES;
goto out;
}
vfs_setflags(mp, (u_int64_t)((unsigned int)MNT_QUOTA));
HFS_MOUNT_LOCK(hfsmp, TRUE)
hfsmp->hfs_flags |= HFS_QUOTAS;
HFS_MOUNT_UNLOCK(hfsmp, TRUE);
vnode_setnoflush(vp);
qfp->qf_cred = kauth_cred_proc_ref(p);
qfp->qf_vp = vp;
error = dqfileopen(qfp, type);
if (error) {
(void) vnode_close(vp, FREAD|FWRITE, NULL);
if (IS_VALID_CRED(qfp->qf_cred))
kauth_cred_unref(&qfp->qf_cred);
qfp->qf_vp = NULLVP;
goto out;
}
qf_put(qfp, QTF_OPENING);
args.error = 0;
vnode_iterate(mp, VNODE_WRITEABLE | VNODE_WAIT, hfs_quotaon_callback, (void *)&args);
error = args.error;
if (error) {
hfs_quotaoff(p, mp, type);
}
return (error);
out:
qf_put(qfp, QTF_OPENING);
return (error);
}
struct hfs_quotaoff_cargs {
int type;
};
static int
hfs_quotaoff_callback(struct vnode *vp, void *cargs)
{
struct hfs_quotaoff_cargs *args;
struct cnode *cp;
struct dquot *dq;
args = (struct hfs_quotaoff_cargs *)cargs;
cp = VTOC(vp);
dq = cp->c_dquot[args->type];
cp->c_dquot[args->type] = NODQUOT;
dqrele(dq);
return (VNODE_RETURNED);
}
int
hfs_quotaoff(__unused struct proc *p, struct mount *mp, register int type)
{
struct vnode *qvp;
struct hfsmount *hfsmp = VFSTOHFS(mp);
struct quotafile *qfp;
int error;
struct hfs_quotaoff_cargs args;
if (!dqisinitialized())
return (0);
qfp = &hfsmp->hfs_qfiles[type];
if ( (qf_get(qfp, QTF_CLOSING)) )
return (0);
qvp = qfp->qf_vp;
dqsync_orphans(qfp);
args.type = type;
vnode_iterate(mp, VNODE_WAIT, hfs_quotaoff_callback, (void *)&args);
dqflush(qvp);
dqfileclose(qfp, type);
vnode_clearnoflush(qvp);
error = vnode_close(qvp, FREAD|FWRITE, NULL);
qfp->qf_vp = NULLVP;
if (IS_VALID_CRED(qfp->qf_cred))
kauth_cred_unref(&qfp->qf_cred);
for (type = 0; type < MAXQUOTAS; type++)
if (hfsmp->hfs_qfiles[type].qf_vp != NULLVP)
break;
if (type == MAXQUOTAS) {
vfs_clearflags(mp, (u_int64_t)((unsigned int)MNT_QUOTA));
HFS_MOUNT_LOCK(hfsmp, TRUE)
hfsmp->hfs_flags &= ~HFS_QUOTAS;
HFS_MOUNT_UNLOCK(hfsmp, TRUE);
}
qf_put(qfp, QTF_CLOSING);
return (error);
}
int hfs_quotacheck(hfsmp, change, uid, gid, cred)
struct hfsmount *hfsmp;
int change;
uid_t uid;
gid_t gid;
kauth_cred_t cred;
{
struct dquot *dq = NULL;
struct proc *p;
int error = 0;
int i;
id_t id = uid;
p = current_proc();
if (!IS_VALID_CRED(cred)) {
cred = proc_ucred(kernproc);
}
if (suser(cred, NULL) || proc_forcequota(p)) {
for (i = 0; i < MAXQUOTAS; i++) {
if (i == USRQUOTA)
id = uid;
else if (i == GRPQUOTA)
id = gid;
error = dqget(id, &hfsmp->hfs_qfiles[i], i, &dq);
if (error && (error != EINVAL))
break;
error = 0;
if (dq == NODQUOT)
continue;
error = hfs_isiqchg_allowed(dq, hfsmp, change, cred, i, id);
if (error) {
dqrele(dq);
break;
}
dqlock(dq);
dq->dq_curinodes += change;
dqunlock(dq);
dqrele(dq);
}
}
return error;
}
int
hfs_getquota(mp, id, type, datap)
struct mount *mp;
u_int32_t id;
int type;
caddr_t datap;
{
struct dquot *dq;
int error;
error = dqget(id, &VFSTOHFS(mp)->hfs_qfiles[type], type, &dq);
if (error)
return (error);
dqlock(dq);
bcopy(&dq->dq_dqb, datap, sizeof(dq->dq_dqb));
dqunlock(dq);
dqrele(dq);
return (error);
}
int
hfs_setquota(mp, id, type, datap)
struct mount *mp;
u_int32_t id;
int type;
caddr_t datap;
{
struct dquot *dq;
struct hfsmount *hfsmp = VFSTOHFS(mp);
struct dqblk * newlimp = (struct dqblk *) datap;
struct timeval tv;
int error;
error = dqget(id, &hfsmp->hfs_qfiles[type], type, &dq);
if (error)
return (error);
dqlock(dq);
newlimp->dqb_curbytes = dq->dq_curbytes;
newlimp->dqb_curinodes = dq->dq_curinodes;
if (dq->dq_id != 0) {
newlimp->dqb_btime = dq->dq_btime;
newlimp->dqb_itime = dq->dq_itime;
}
if (newlimp->dqb_bsoftlimit &&
dq->dq_curbytes >= newlimp->dqb_bsoftlimit &&
(dq->dq_bsoftlimit == 0 || dq->dq_curbytes < dq->dq_bsoftlimit)) {
microuptime(&tv);
newlimp->dqb_btime = tv.tv_sec + hfsmp->hfs_qfiles[type].qf_btime;
}
if (newlimp->dqb_isoftlimit &&
dq->dq_curinodes >= newlimp->dqb_isoftlimit &&
(dq->dq_isoftlimit == 0 || dq->dq_curinodes < dq->dq_isoftlimit)) {
microuptime(&tv);
newlimp->dqb_itime = tv.tv_sec + hfsmp->hfs_qfiles[type].qf_itime;
}
bcopy(newlimp, &dq->dq_dqb, sizeof(dq->dq_dqb));
if (dq->dq_curbytes < 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;
else
dq->dq_flags &= ~DQ_FAKE;
dq->dq_flags |= DQ_MOD;
dqunlock(dq);
dqrele(dq);
return (0);
}
int
hfs_setuse(mp, id, type, datap)
struct mount *mp;
u_int32_t id;
int type;
caddr_t datap;
{
struct hfsmount *hfsmp = VFSTOHFS(mp);
struct dquot *dq;
struct timeval tv;
int error;
struct dqblk *quotablkp = (struct dqblk *) datap;
error = dqget(id, &hfsmp->hfs_qfiles[type], type, &dq);
if (error)
return (error);
dqlock(dq);
if (dq->dq_bsoftlimit && dq->dq_curbytes < dq->dq_bsoftlimit &&
quotablkp->dqb_curbytes >= dq->dq_bsoftlimit) {
microuptime(&tv);
dq->dq_btime = tv.tv_sec + hfsmp->hfs_qfiles[type].qf_btime;
}
if (dq->dq_isoftlimit && dq->dq_curinodes < dq->dq_isoftlimit &&
quotablkp->dqb_curinodes >= dq->dq_isoftlimit) {
microuptime(&tv);
dq->dq_itime = tv.tv_sec + hfsmp->hfs_qfiles[type].qf_itime;
}
dq->dq_curbytes = quotablkp->dqb_curbytes;
dq->dq_curinodes = quotablkp->dqb_curinodes;
if (dq->dq_curbytes < dq->dq_bsoftlimit)
dq->dq_flags &= ~DQ_BLKS;
if (dq->dq_curinodes < dq->dq_isoftlimit)
dq->dq_flags &= ~DQ_INODS;
dq->dq_flags |= DQ_MOD;
dqunlock(dq);
dqrele(dq);
return (0);
}
static int
hfs_qsync_callback(struct vnode *vp, __unused void *cargs)
{
struct cnode *cp;
struct dquot *dq;
int i;
cp = VTOC(vp);
for (i = 0; i < MAXQUOTAS; i++) {
dq = cp->c_dquot[i];
if (dq != NODQUOT && (dq->dq_flags & DQ_MOD))
dqsync(dq);
}
return (VNODE_RETURNED);
}
int
hfs_qsync(mp)
struct mount *mp;
{
struct hfsmount *hfsmp = VFSTOHFS(mp);
int i;
if (!dqisinitialized())
return (0);
for (i = 0; i < MAXQUOTAS; i++)
if (hfsmp->hfs_qfiles[i].qf_vp != NULLVP)
break;
if (i == MAXQUOTAS)
return (0);
for (i = 0; i < MAXQUOTAS; i++)
if (hfsmp->hfs_qfiles[i].qf_vp != NULLVP)
dqsync_orphans(&hfsmp->hfs_qfiles[i]);
vnode_iterate(mp, 0, hfs_qsync_callback, (void *)NULL);
return (0);
}
int
hfs_quotastat(mp, type, datap)
struct mount *mp;
register int type;
caddr_t datap;
{
struct hfsmount *hfsmp = VFSTOHFS(mp);
int error = 0;
int qstat;
if ((((unsigned int)vfs_flags(mp)) & MNT_QUOTA) && (hfsmp->hfs_qfiles[type].qf_vp != NULLVP))
qstat = 1;
else
qstat = 0;
*((int *)datap) = qstat;
return (error);
}