#include <sys/types.h>
#include <sys/t_lock.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/systm.h>
#include <sys/signal.h>
#include <sys/cred.h>
#include <sys/proc.h>
#include <sys/user.h>
#include <sys/proc.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <sys/uio.h>
#include <sys/buf.h>
#include <sys/file.h>
#include <sys/fs/ufs_inode.h>
#include <sys/fs/ufs_fs.h>
#include <sys/fs/ufs_quota.h>
#include <sys/errno.h>
#include <sys/debug.h>
#include <sys/cmn_err.h>
#include <sys/pathname.h>
#include <sys/mntent.h>
#include <sys/policy.h>
static int opendq();
static int setquota();
static int getquota();
static int quotasync();
int quotas_initialized = 0;
int
quotactl(struct vnode *vp, intptr_t arg, int flag, struct cred *cr)
{
struct quotctl quot;
struct ufsvfs *ufsvfsp;
int error = 0;
if ((flag & DATAMODEL_MASK) == DATAMODEL_NATIVE) {
if (copyin((caddr_t)arg, ", sizeof (struct quotctl)))
return (EFAULT);
}
#ifdef _SYSCALL32_IMPL
else {
struct quotctl32 quot32;
if (copyin((caddr_t)arg, "32, sizeof (struct quotctl32)))
return (EFAULT);
quot.op = quot32.op;
quot.uid = quot32.uid;
quot.addr = (caddr_t)(uintptr_t)quot32.addr;
}
#endif
if (quot.uid < 0)
quot.uid = crgetruid(cr);
if (quot.op == Q_SYNC && vp == NULL) {
ufsvfsp = NULL;
} else if (quot.op != Q_ALLSYNC) {
ufsvfsp = (struct ufsvfs *)(vp->v_vfsp->vfs_data);
}
switch (quot.op) {
case Q_QUOTAON:
rw_enter(&dq_rwlock, RW_WRITER);
if (quotas_initialized == 0) {
qtinit2();
quotas_initialized = 1;
}
rw_exit(&dq_rwlock);
error = opendq(ufsvfsp, vp, cr);
break;
case Q_QUOTAOFF:
error = closedq(ufsvfsp, cr);
if (!error) {
invalidatedq(ufsvfsp);
}
break;
case Q_SETQUOTA:
case Q_SETQLIM:
error = setquota(quot.op, (uid_t)quot.uid, ufsvfsp,
quot.addr, cr);
break;
case Q_GETQUOTA:
error = getquota((uid_t)quot.uid, ufsvfsp, (caddr_t)quot.addr,
cr);
break;
case Q_SYNC:
error = qsync(ufsvfsp);
break;
case Q_ALLSYNC:
(void) qsync(NULL);
break;
default:
error = EINVAL;
break;
}
return (error);
}
static int
opendq_scan_inode(struct inode *ip, void *arg)
{
struct ufsvfs *ufsvfsp = ip->i_ufsvfs;
if (ufsvfsp != (struct ufsvfs *)arg || ip == ip->i_ufsvfs->vfs_qinod) {
return (0);
}
ASSERT(RW_WRITE_HELD(&ufsvfsp->vfs_dqrwlock));
rw_enter(&ip->i_contents, RW_WRITER);
if (ip->i_mode && (ip->i_mode & IFMT) != IFSHAD &&
(ip->i_mode & IFMT) != IFATTRDIR && ip->i_dquot == NULL) {
ip->i_dquot = getinoquota(ip);
}
rw_exit(&ip->i_contents);
return (0);
}
static int
opendq(
struct ufsvfs *ufsvfsp,
struct vnode *vp,
struct cred *cr)
{
struct inode *qip;
struct dquot *dqp;
int error;
int quotaon = 0;
if (secpolicy_fs_quota(cr, ufsvfsp->vfs_vfs) != 0)
return (EPERM);
VN_HOLD(vp);
if (vp->v_type != VREG) {
VN_RELE(vp);
return (EACCES);
}
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_WRITER);
ASSERT((ufsvfsp->vfs_qflags & MQ_ENABLED) || (ufsvfsp->vfs_qinod == 0));
if ((ufsvfsp->vfs_qflags & MQ_ENABLED) != 0) {
if (VTOI(vp) != ufsvfsp->vfs_qinod) {
cmn_err(CE_WARN, "Previous quota file still in use."
" Disable quotas on %s before enabling.\n",
VTOI(vp)->i_fs->fs_fsmnt);
VN_RELE(vp);
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (0);
}
(void) quotasync(ufsvfsp, 0);
VN_RELE(vp);
quotaon++;
qip = ufsvfsp->vfs_qinod;
} else {
int qlen;
ufsvfsp->vfs_qinod = VTOI(vp);
qip = ufsvfsp->vfs_qinod;
qlen = qip->i_fs->fs_bsize * NDADDR;
rw_enter(&qip->i_contents, RW_WRITER);
if (qip->i_size < qlen) {
if (ufs_itrunc(qip, (u_offset_t)qlen, (int)0, cr) != 0)
cmn_err(CE_WARN, "opendq failed to remove frags"
" from quota file\n");
rw_exit(&qip->i_contents);
(void) VOP_PUTPAGE(vp, (offset_t)0, (size_t)qip->i_size,
B_INVAL, kcred, NULL);
} else {
rw_exit(&qip->i_contents);
}
TRANS_MATA_IGET(ufsvfsp, qip);
}
error = getdiskquota((uid_t)0, ufsvfsp, 1, &dqp);
if (error == 0) {
mutex_enter(&dqp->dq_lock);
ufsvfsp->vfs_btimelimit =
(dqp->dq_btimelimit? dqp->dq_btimelimit: DQ_BTIMELIMIT);
ufsvfsp->vfs_ftimelimit =
(dqp->dq_ftimelimit? dqp->dq_ftimelimit: DQ_FTIMELIMIT);
ufsvfsp->vfs_qflags = MQ_ENABLED;
vfs_setmntopt(ufsvfsp->vfs_vfs, MNTOPT_QUOTA, NULL, 0);
dqput(dqp);
mutex_exit(&dqp->dq_lock);
} else if (!quotaon) {
ufsvfsp->vfs_qflags = 0;
ufsvfsp->vfs_qinod = NULL;
VN_RELE(ITOV(qip));
}
if (ufsvfsp->vfs_qflags & MQ_ENABLED) {
(void) ufs_scan_inodes(0, opendq_scan_inode, ufsvfsp, ufsvfsp);
}
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (error);
}
static int
closedq_scan_inode(struct inode *ip, void *arg)
{
struct dquot *dqp;
struct ufsvfs *ufsvfsp = ip->i_ufsvfs;
if (ufsvfsp != (struct ufsvfs *)arg)
return (0);
ASSERT(RW_WRITE_HELD(&ufsvfsp->vfs_dqrwlock));
rw_enter(&ip->i_contents, RW_WRITER);
if ((dqp = ip->i_dquot) != NULL) {
ASSERT((ip->i_mode & IFMT) != IFSHAD);
ASSERT((ip->i_mode & IFMT) != IFATTRDIR);
ip->i_dquot = NULL;
mutex_enter(&dqp->dq_lock);
dqput(dqp);
if (dqp->dq_flags & DQ_TRANS) {
dqp->dq_flags &= ~DQ_TRANS;
dqput(dqp);
}
mutex_exit(&dqp->dq_lock);
}
rw_exit(&ip->i_contents);
return (0);
}
int
closedq(struct ufsvfs *ufsvfsp, struct cred *cr)
{
struct inode *qip;
if (secpolicy_fs_quota(cr, ufsvfsp->vfs_vfs) != 0)
return (EPERM);
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_WRITER);
if ((ufsvfsp->vfs_qflags & MQ_ENABLED) == 0) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (0);
}
qip = ufsvfsp->vfs_qinod;
if (!qip)
return (ufs_fault(ufsvfsp->vfs_root, "closedq: NULL qip"));
ufsvfsp->vfs_qflags = 0;
vfs_setmntopt(ufsvfsp->vfs_vfs, MNTOPT_NOQUOTA, NULL, 0);
(void) ufs_scan_inodes(0, closedq_scan_inode, ufsvfsp, ufsvfsp);
ufsvfsp->vfs_qinod = NULL;
rw_exit(&ufsvfsp->vfs_dqrwlock);
(void) TRANS_SYNCIP(qip, 0, I_SYNC, TOP_SYNCIP_CLOSEDQ);
VN_RELE(ITOV(qip));
return (0);
}
struct setquota_data {
#define SQD_TYPE_NONE 0
#define SQD_TYPE_LIMIT 1
#define SQD_TYPE_NO_LIMIT 2
int sqd_type;
struct ufsvfs *sqd_ufsvfsp;
uid_t sqd_uid;
};
static int
setquota_scan_inode(struct inode *ip, void *arg)
{
struct setquota_data *sqdp = (struct setquota_data *)arg;
struct ufsvfs *ufsvfsp = ip->i_ufsvfs;
if (ufsvfsp != sqdp->sqd_ufsvfsp)
return (0);
ASSERT(RW_WRITE_HELD(&ufsvfsp->vfs_dqrwlock));
if ((ufsvfsp->vfs_qflags & MQ_ENABLED) == 0 ||
ip == ufsvfsp->vfs_qinod) {
return (0);
}
rw_enter(&ip->i_contents, RW_WRITER);
if (ip->i_mode && (ip->i_mode & IFMT) != IFSHAD &&
(ip->i_mode & IFMT) != IFATTRDIR && ip->i_uid == sqdp->sqd_uid) {
if (sqdp->sqd_type == SQD_TYPE_LIMIT &&
ip->i_dquot == NULL) {
ip->i_dquot = getinoquota(ip);
}
else if (sqdp->sqd_type == SQD_TYPE_NO_LIMIT && ip->i_dquot) {
mutex_enter(&ip->i_dquot->dq_lock);
dqput(ip->i_dquot);
mutex_exit(&ip->i_dquot->dq_lock);
ip->i_dquot = NULL;
}
}
rw_exit(&ip->i_contents);
return (0);
}
static int
setquota(int cmd, uid_t uid, struct ufsvfs *ufsvfsp,
caddr_t addr, struct cred *cr)
{
struct dquot *dqp;
struct inode *qip;
struct dquot *xdqp;
struct dqblk newlim;
int error;
int scan_type = SQD_TYPE_NONE;
daddr_t bn;
int contig;
if (secpolicy_fs_quota(cr, ufsvfsp->vfs_vfs) != 0)
return (EPERM);
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_WRITER);
if ((ufsvfsp->vfs_qflags & MQ_ENABLED) == 0) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (ESRCH);
}
if (copyin(addr, (caddr_t)&newlim, sizeof (struct dqblk)) != 0) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (EFAULT);
}
error = getdiskquota(uid, ufsvfsp, 0, &xdqp);
if (error) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (error);
}
dqp = xdqp;
mutex_enter(&dqp->dq_lock);
if (cmd == Q_SETQLIM) {
newlim.dqb_curblocks = dqp->dq_curblocks;
newlim.dqb_curfiles = dqp->dq_curfiles;
}
if (uid == 0) {
ufsvfsp->vfs_btimelimit =
newlim.dqb_btimelimit? newlim.dqb_btimelimit: DQ_BTIMELIMIT;
ufsvfsp->vfs_ftimelimit =
newlim.dqb_ftimelimit? newlim.dqb_ftimelimit: DQ_FTIMELIMIT;
} else {
if (newlim.dqb_bsoftlimit &&
newlim.dqb_curblocks >= newlim.dqb_bsoftlimit) {
if (dqp->dq_bsoftlimit == 0 ||
dqp->dq_curblocks < dqp->dq_bsoftlimit) {
newlim.dqb_btimelimit =
(uint32_t)gethrestime_sec() +
ufsvfsp->vfs_btimelimit;
dqp->dq_flags &= ~DQ_BLKS;
} else {
newlim.dqb_btimelimit = dqp->dq_btimelimit;
dqp->dq_flags |= DQ_BLKS;
}
} else {
newlim.dqb_btimelimit = 0;
dqp->dq_flags &= ~DQ_BLKS;
}
if (newlim.dqb_fsoftlimit &&
newlim.dqb_curfiles >= newlim.dqb_fsoftlimit) {
if (dqp->dq_fsoftlimit == 0 ||
dqp->dq_curfiles < dqp->dq_fsoftlimit) {
newlim.dqb_ftimelimit =
(uint32_t)gethrestime_sec() +
ufsvfsp->vfs_ftimelimit;
dqp->dq_flags &= ~DQ_FILES;
} else {
newlim.dqb_ftimelimit = dqp->dq_ftimelimit;
dqp->dq_flags |= DQ_FILES;
}
} else {
newlim.dqb_ftimelimit = 0;
dqp->dq_flags &= ~DQ_FILES;
}
}
if ((dqp->dq_fhardlimit == 0 && dqp->dq_fsoftlimit == 0 &&
dqp->dq_bhardlimit == 0 && dqp->dq_bsoftlimit == 0) &&
(newlim.dqb_fhardlimit || newlim.dqb_fsoftlimit ||
newlim.dqb_bhardlimit || newlim.dqb_bsoftlimit)) {
scan_type = SQD_TYPE_LIMIT;
}
else if ((dqp->dq_fhardlimit || dqp->dq_fsoftlimit ||
dqp->dq_bhardlimit || dqp->dq_bsoftlimit) &&
(newlim.dqb_fhardlimit == 0 && newlim.dqb_fsoftlimit == 0 &&
newlim.dqb_bhardlimit == 0 && newlim.dqb_bsoftlimit == 0)) {
scan_type = SQD_TYPE_NO_LIMIT;
}
dqp->dq_dqb = newlim;
dqp->dq_flags |= DQ_MOD;
qip = ufsvfsp->vfs_qinod;
rw_enter(&qip->i_contents, RW_WRITER);
(void) ufs_rdwri(UIO_WRITE, FWRITE | FSYNC, qip, (caddr_t)&dqp->dq_dqb,
sizeof (struct dqblk), dqoff(uid), UIO_SYSSPACE,
(int *)NULL, kcred);
rw_exit(&qip->i_contents);
(void) VOP_PUTPAGE(ITOV(qip), dqoff(dqp->dq_uid) & ~qip->i_fs->fs_bmask,
qip->i_fs->fs_bsize, B_INVAL, kcred, NULL);
contig = 0;
rw_enter(&qip->i_contents, RW_WRITER);
error = bmap_read(qip, dqoff(dqp->dq_uid), &bn, &contig);
rw_exit(&qip->i_contents);
if (error || (bn == UFS_HOLE)) {
dqp->dq_mof = UFS_HOLE;
} else {
dqp->dq_mof = ldbtob(bn) +
(offset_t)((dqoff(dqp->dq_uid)) & (DEV_BSIZE - 1));
}
dqp->dq_flags &= ~DQ_MOD;
dqput(dqp);
mutex_exit(&dqp->dq_lock);
if (scan_type) {
struct setquota_data sqd;
sqd.sqd_type = scan_type;
sqd.sqd_ufsvfsp = ufsvfsp;
sqd.sqd_uid = uid;
(void) ufs_scan_inodes(0, setquota_scan_inode, &sqd, ufsvfsp);
}
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (0);
}
static int
getquota(uid_t uid, struct ufsvfs *ufsvfsp, caddr_t addr, cred_t *cr)
{
struct dquot *dqp;
struct dquot *xdqp;
struct dqblk dqb;
int error = 0;
if (uid != crgetruid(cr) &&
secpolicy_fs_quota(cr, ufsvfsp->vfs_vfs) != 0)
return (EPERM);
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_READER);
if ((ufsvfsp->vfs_qflags & MQ_ENABLED) == 0) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (ESRCH);
}
error = getdiskquota(uid, ufsvfsp, 0, &xdqp);
if (error) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (error);
}
dqp = xdqp;
mutex_enter(&dqp->dq_lock);
if (dqp->dq_fhardlimit == 0 && dqp->dq_fsoftlimit == 0 &&
dqp->dq_bhardlimit == 0 && dqp->dq_bsoftlimit == 0) {
error = ESRCH;
} else {
bcopy(&dqp->dq_dqb, &dqb, sizeof (struct dqblk));
}
dqput(dqp);
mutex_exit(&dqp->dq_lock);
rw_exit(&ufsvfsp->vfs_dqrwlock);
if (error == 0 && copyout(&dqb, addr, sizeof (struct dqblk)) != 0)
error = EFAULT;
return (error);
}
int
qsync(struct ufsvfs *ufsvfsp)
{
return (quotasync(ufsvfsp, 1));
}
int
quotasync(struct ufsvfs *ufsvfsp, int do_lock)
{
struct dquot *dqp;
rw_enter(&dq_rwlock, RW_READER);
if (!quotas_initialized) {
rw_exit(&dq_rwlock);
return (ESRCH);
}
rw_exit(&dq_rwlock);
if (ufsvfsp) {
if (do_lock) {
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_READER);
}
if ((ufsvfsp->vfs_qflags & MQ_ENABLED) == 0) {
if (do_lock) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
}
return (ESRCH);
}
if (TRANS_ISTRANS(ufsvfsp)) {
if (do_lock) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
}
return (0);
}
for (dqp = dquot; dqp < dquotNDQUOT; dqp++) {
if (!mutex_tryenter(&dqp->dq_lock)) {
continue;
}
if (dqp->dq_ufsvfsp == ufsvfsp &&
(dqp->dq_flags & DQ_MOD)) {
ASSERT(ufsvfsp->vfs_qflags & MQ_ENABLED);
dqupdate(dqp);
}
mutex_exit(&dqp->dq_lock);
}
if (do_lock) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
}
return (0);
}
for (dqp = dquot; dqp < dquotNDQUOT; dqp++) {
if (!mutex_tryenter(&dqp->dq_lock)) {
continue;
}
ufsvfsp = dqp->dq_ufsvfsp;
if ((dqp->dq_flags & DQ_MOD) == 0 || ufsvfsp == NULL) {
mutex_exit(&dqp->dq_lock);
continue;
}
if (do_lock) {
if (rw_tryenter(&ufsvfsp->vfs_dqrwlock,
RW_READER) == 0) {
mutex_exit(&dqp->dq_lock);
continue;
}
}
ASSERT(ufsvfsp->vfs_qflags & MQ_ENABLED);
if (!TRANS_ISTRANS(ufsvfsp)) {
dqupdate(dqp);
}
mutex_exit(&dqp->dq_lock);
if (do_lock) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
}
}
return (0);
}