/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* University Copyright- Copyright (c) 1982, 1986, 1988
* The Regents of the University of California
* All Rights Reserved
*
* University Acknowledgment- Portions of this document are derived from
* software developed by the University of California, Berkeley, and its
* contributors.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Quota system calls.
*/
#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();
/*
* Quota sub-system init flag.
*/
int quotas_initialized = 0;
/*
* Sys call to allow users to find out
* their current position wrt quota's
* and to allow privileged users to alter it.
*/
/*ARGSUSED*/
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, &quot, sizeof (struct quotctl)))
return (EFAULT);
}
#ifdef _SYSCALL32_IMPL
else {
/* quotctl struct from ILP32 callers */
struct quotctl32 quot32;
if (copyin((caddr_t)arg, &quot32, sizeof (struct quotctl32)))
return (EFAULT);
quot.op = quot32.op;
quot.uid = quot32.uid;
quot.addr = (caddr_t)(uintptr_t)quot32.addr;
}
#endif /* _SYSCALL32_IMPL */
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;
/*
* wrong file system or this is the quota inode; keep looking
*/
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);
/*
* This inode is in the cache (by definition), is still valid,
* and is not a shadow inode or extended attribute directory inode,
* but does not have a quota so get the quota information.
*/
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);
}
/*
* Set the quota file up for a particular file system.
* Called as the result of a quotaon (Q_QUOTAON) ioctl.
*/
static int
opendq(
struct ufsvfs *ufsvfsp,
struct vnode *vp, /* quota file */
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);
/*
* Check to be sure its a regular file.
*/
if (vp->v_type != VREG) {
VN_RELE(vp);
return (EACCES);
}
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_WRITER);
/*
* We have vfs_dqrwlock as writer, so if quotas are disabled,
* then vfs_qinod should be NULL or we have a race somewhere.
*/
ASSERT((ufsvfsp->vfs_qflags & MQ_ENABLED) || (ufsvfsp->vfs_qinod == 0));
if ((ufsvfsp->vfs_qflags & MQ_ENABLED) != 0) {
/*
* Quotas are already enabled on this file system.
*
* If the "quotas" file was replaced (different inode)
* while quotas were enabled we don't want to re-enable
* them with a new "quotas" file. Simply print a warning
* message to the console, release the new vnode, and
* return.
* XXX - The right way to fix this is to return EBUSY
* for the ioctl() issued by 'quotaon'.
*/
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, /* do_lock */ 0);
/* remove extra hold on quota file */
VN_RELE(vp);
quotaon++;
qip = ufsvfsp->vfs_qinod;
} else {
int qlen;
ufsvfsp->vfs_qinod = VTOI(vp);
qip = ufsvfsp->vfs_qinod;
/*
* Force the file to have no partially allocated blocks
* to prevent a realloc from changing the location of
* the data. We must do this even if not logging in
* case we later remount to logging.
*/
qlen = qip->i_fs->fs_bsize * NDADDR;
/*
* Largefiles: i_size needs to be atomically accessed now.
*/
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);
}
/*
* The file system time limits are in the dquot for uid 0.
* The time limits set the relative time the other users
* can be over quota for this file system.
* If it is zero a default is used (see quota.h).
*/
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; /* enable quotas */
vfs_setmntopt(ufsvfsp->vfs_vfs, MNTOPT_QUOTA, NULL, 0);
dqput(dqp);
mutex_exit(&dqp->dq_lock);
} else if (!quotaon) {
/*
* Some sort of I/O error on the quota file, and quotas were
* not already on when we got here so clean up.
*/
ufsvfsp->vfs_qflags = 0;
ufsvfsp->vfs_qinod = NULL;
VN_RELE(ITOV(qip));
}
/*
* If quotas are enabled update all valid inodes in the
* cache with quota information.
*/
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;
/*
* wrong file system; keep looking
*/
if (ufsvfsp != (struct ufsvfs *)arg)
return (0);
ASSERT(RW_WRITE_HELD(&ufsvfsp->vfs_dqrwlock));
rw_enter(&ip->i_contents, RW_WRITER);
/*
* Shadow inodes and extended attribute directories
* do not have quota info records.
*/
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 we have a pending logging file system quota
* transaction, then cancel it. Clear the flag to
* prevent ufs_trans_push_quota() from trying to
* deal with this transaction just in case it is
* waiting for the mutex. We decrement the counter
* since the transaction won't be needing the quota
* info record anymore.
*/
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);
}
/*
* Close off disk quotas for a file system.
*/
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);
/*
* Quotas are not enabled on this file system so there is
* nothing more to do.
*/
if ((ufsvfsp->vfs_qflags & MQ_ENABLED) == 0) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (0);
}
/*
* At this point, the quota subsystem is quiescent on this file
* system so we can do all the work necessary to dismantle the
* quota stuff.
*/
qip = ufsvfsp->vfs_qinod;
if (!qip)
return (ufs_fault(ufsvfsp->vfs_root, "closedq: NULL qip"));
ufsvfsp->vfs_qflags = 0; /* disable quotas */
vfs_setmntopt(ufsvfsp->vfs_vfs, MNTOPT_NOQUOTA, NULL, 0);
/*
* ufs_scan_inodes() depends on vfs_qinod, so we can't
* clear it until afterwards.
*/
(void) ufs_scan_inodes(0, closedq_scan_inode, ufsvfsp, ufsvfsp);
ufsvfsp->vfs_qinod = NULL;
rw_exit(&ufsvfsp->vfs_dqrwlock);
/*
* Sync and release the quota file inode. Since we have a private
* pointer to the quota inode and vfs_qinod is now clear we do not
* need to hold vfs_dqrwlock.
*/
(void) TRANS_SYNCIP(qip, 0, I_SYNC, TOP_SYNCIP_CLOSEDQ);
VN_RELE(ITOV(qip));
return (0);
}
/*
* Private data between setquota() and setquota_scan_inode().
*/
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;
/*
* wrong file system; keep looking
*/
if (ufsvfsp != sqdp->sqd_ufsvfsp)
return (0);
ASSERT(RW_WRITE_HELD(&ufsvfsp->vfs_dqrwlock));
/*
* The file system does not have quotas enabled or this is the
* file system's quota inode; keep looking.
*/
if ((ufsvfsp->vfs_qflags & MQ_ENABLED) == 0 ||
ip == ufsvfsp->vfs_qinod) {
return (0);
}
rw_enter(&ip->i_contents, RW_WRITER);
/*
* This inode is in the cache (by definition), is still valid,
* is not a shadow inode or extended attribute directory inode
* and has the right uid.
*/
if (ip->i_mode && (ip->i_mode & IFMT) != IFSHAD &&
(ip->i_mode & IFMT) != IFATTRDIR && ip->i_uid == sqdp->sqd_uid) {
/*
* Transition is "no limit" to "at least one limit":
*/
if (sqdp->sqd_type == SQD_TYPE_LIMIT &&
ip->i_dquot == NULL) {
ip->i_dquot = getinoquota(ip);
}
/*
* Transition is "at least one limit" to "no limit":
*/
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);
}
/*
* Set various fields of the dqblk according to the command.
* Q_SETQUOTA - assign an entire dqblk structure.
* Q_SETQLIM - assign a dqblk structure except for the usage.
*/
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);
/*
* Quotas are not enabled on this file system so there is
* nothing more to do.
*/
if ((ufsvfsp->vfs_qflags & MQ_ENABLED) == 0) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
return (ESRCH);
}
/*
* At this point, the quota subsystem is quiescent on this file
* system so we can do all the work necessary to modify the quota
* information for this user.
*/
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;
/*
* Don't change disk usage on Q_SETQLIM
*/
mutex_enter(&dqp->dq_lock);
if (cmd == Q_SETQLIM) {
newlim.dqb_curblocks = dqp->dq_curblocks;
newlim.dqb_curfiles = dqp->dq_curfiles;
}
if (uid == 0) {
/*
* Timelimits for uid 0 set the relative time
* the other users can be over quota for this file system.
* If it is zero a default is used (see quota.h).
*/
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) {
/* If we're suddenly over the limit(s), */
/* start the timer(s) */
newlim.dqb_btimelimit =
(uint32_t)gethrestime_sec() +
ufsvfsp->vfs_btimelimit;
dqp->dq_flags &= ~DQ_BLKS;
} else {
/* If we're currently over the soft */
/* limit and were previously over the */
/* soft limit then preserve the old */
/* time limit but make sure the DQ_BLKS */
/* flag is set since we must have been */
/* previously warned. */
newlim.dqb_btimelimit = dqp->dq_btimelimit;
dqp->dq_flags |= DQ_BLKS;
}
} else {
/* Either no quota or under quota, clear time limit */
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) {
/* If we're suddenly over the limit(s), */
/* start the timer(s) */
newlim.dqb_ftimelimit =
(uint32_t)gethrestime_sec() +
ufsvfsp->vfs_ftimelimit;
dqp->dq_flags &= ~DQ_FILES;
} else {
/* If we're currently over the soft */
/* limit and were previously over the */
/* soft limit then preserve the old */
/* time limit but make sure the */
/* DQ_FILES flag is set since we must */
/* have been previously warned. */
newlim.dqb_ftimelimit = dqp->dq_ftimelimit;
dqp->dq_flags |= DQ_FILES;
}
} else {
/* Either no quota or under quota, clear time limit */
newlim.dqb_ftimelimit = 0;
dqp->dq_flags &= ~DQ_FILES;
}
}
/*
* If there was previously no limit and there is now at least
* one limit, then any inodes in the cache have NULL d_iquot
* fields (getinoquota() returns NULL when there are no limits).
*/
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;
}
/*
* If there was previously at least one limit and there is now
* no limit, then any inodes in the cache have non-NULL d_iquot
* fields need to be reset to NULL.
*/
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;
/*
* push the new quota to disk now. If this is a trans device
* then force the page out with ufs_putpage so it will be deltaed
* by ufs_startio.
*/
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);
/*
* We must set the dq_mof even if not we are not logging in case
* we are later remount to logging.
*/
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);
}
/*
* Q_GETQUOTA - return current values in a dqblk structure.
*/
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);
}
/*
* Q_SYNC - sync quota files to disk.
*/
int
qsync(struct ufsvfs *ufsvfsp)
{
return (quotasync(ufsvfsp, /* do_lock */ 1));
}
/*
* Sync quota information records to disk for the specified file system
* or all file systems with quotas if ufsvfsp == NULL. Grabs a reader
* lock on vfs_dqrwlock if it is needed.
*
* Currently, if ufsvfsp is NULL, then do_lock is always true, but the
* routine is coded to account for either do_lock value. This seemed
* to be the safer thing to do.
*/
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);
/*
* The operation applies to a specific file system only.
*/
if (ufsvfsp) {
if (do_lock) {
rw_enter(&ufsvfsp->vfs_dqrwlock, RW_READER);
}
/*
* Quotas are not enabled on this file system so bail.
*/
if ((ufsvfsp->vfs_qflags & MQ_ENABLED) == 0) {
if (do_lock) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
}
return (ESRCH);
}
/*
* This operation is a no-op on a logging file system because
* quota information is treated as metadata and is in the log.
* This code path treats quota information as user data which
* is not necessary on a logging file system.
*/
if (TRANS_ISTRANS(ufsvfsp)) {
if (do_lock) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
}
return (0);
}
/*
* Try to sync all the quota info records for this
* file system:
*/
for (dqp = dquot; dqp < dquotNDQUOT; dqp++) {
/*
* If someone else has it, then ignore it.
*/
if (!mutex_tryenter(&dqp->dq_lock)) {
continue;
}
/*
* The quota info record is for this file system
* and it has changes.
*/
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);
}
/*
* Try to sync all the quota info records for *all* file systems
* for which quotas are enabled.
*/
for (dqp = dquot; dqp < dquotNDQUOT; dqp++) {
/*
* If someone else has it, then ignore it.
*/
if (!mutex_tryenter(&dqp->dq_lock)) {
continue;
}
ufsvfsp = dqp->dq_ufsvfsp; /* shorthand */
/*
* This quota info record has no changes or is
* not a valid quota info record yet.
*/
if ((dqp->dq_flags & DQ_MOD) == 0 || ufsvfsp == NULL) {
mutex_exit(&dqp->dq_lock);
continue;
}
/*
* Now we have a potential lock order problem:
*
* vfs_dqrwlock > dq_lock
*
* so if we have to get vfs_dqrwlock, then go thru hoops
* to avoid deadlock. If we cannot get the order right,
* then we ignore this quota info record.
*/
if (do_lock) {
/*
* If we can't grab vfs_dqrwlock, then we don't
* want to wait to avoid deadlock.
*/
if (rw_tryenter(&ufsvfsp->vfs_dqrwlock,
RW_READER) == 0) {
mutex_exit(&dqp->dq_lock);
continue;
}
/*
* Okay, now we have both dq_lock and vfs_dqrwlock.
* We should not deadlock for the following reasons:
* - If another thread has a reader lock on
* vfs_dqrwlock and is waiting for dq_lock,
* there is no conflict because we can also have
* a reader lock on vfs_dqrwlock.
* - If another thread has a writer lock on
* vfs_dqrwlock and is waiting for dq_lock,
* we would have failed the rw_tryenter() above
* and given up dq_lock.
* - Since we have dq_lock another thread cannot
* have it and be waiting for vfs_dqrwlock.
*/
}
/*
* Since we got to this file system via a quota info
* record and we have vfs_dqrwlock this is paranoia
* to make sure that quotas are enabled.
*/
ASSERT(ufsvfsp->vfs_qflags & MQ_ENABLED);
/*
* We are not logging. See above logging file system
* comment.
*/
if (!TRANS_ISTRANS(ufsvfsp)) {
dqupdate(dqp);
}
/*
* Since we have a private copy of dqp->dq_ufsvfsp,
* we can drop dq_lock now.
*/
mutex_exit(&dqp->dq_lock);
if (do_lock) {
rw_exit(&ufsvfsp->vfs_dqrwlock);
}
}
return (0);
}