root/usr/src/uts/common/fs/ufs/quotacalls.c
/*
 * 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.
 */

/*
 * 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);
}