/*
- * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2002-2008 Apple Inc. All rights reserved.
*
- * @APPLE_LICENSE_HEADER_START@
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
- * compliance with the License. Please obtain a copy of the License at
- * http://www.opensource.apple.com/apsl/ and read it before using this
- * file.
+ * compliance with the License. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ *
+ * Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* Please see the License for the specific language governing rights and
* limitations under the License.
*
- * @APPLE_LICENSE_HEADER_END@
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
*/
/*
* Copyright (c) 1982, 1986, 1990, 1993, 1995
#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_quota.h>
#include <hfs/hfs_mount.h>
+
/*
* Quota name to error message mapping.
*/
struct hfsmount *hfsmp;
struct vnode *vp;
int error;
+ int drop_usrquota = false;
vp = cp->c_vp ? cp->c_vp : cp->c_rsrc_vp;
hfsmp = VTOHFS(vp);
* Set up the user quota based on file uid.
* EINVAL means that quotas are not enabled.
*/
- 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[USRQUOTA] == NODQUOT) {
+ error = dqget(cp->c_uid, &hfsmp->hfs_qfiles[USRQUOTA], USRQUOTA, &cp->c_dquot[USRQUOTA]);
+ if ((error != 0) && (error != EINVAL)) {
+ return error;
+ } else if (error == 0) {
+ drop_usrquota = true;
+ }
+ }
+
/*
* Set up the group quota based on file gid.
* EINVAL means that quotas are not enabled.
*/
- if (cp->c_dquot[GRPQUOTA] == NODQUOT &&
- (error =
- dqget(cp->c_gid, &hfsmp->hfs_qfiles[GRPQUOTA], GRPQUOTA, &cp->c_dquot[GRPQUOTA])) &&
- error != EINVAL)
- return (error);
+ if (cp->c_dquot[GRPQUOTA] == NODQUOT) {
+ error = dqget(cp->c_gid, &hfsmp->hfs_qfiles[GRPQUOTA], GRPQUOTA, &cp->c_dquot[GRPQUOTA]);
+ if ((error != 0) && (error != EINVAL)) {
+ if (drop_usrquota == true) {
+ dqrele(cp->c_dquot[USRQUOTA]);
+ cp->c_dquot[USRQUOTA] = NODQUOT;
+ }
+ return error;
+ }
+ }
+
return (0);
}
return (0);
}
p = current_proc();
- if (cred == NOCRED)
+ /*
+ * This use of proc_ucred() is safe because kernproc credential never
+ * changes.
+ */
+ if (!IS_VALID_CRED(cred))
cred = proc_ucred(kernproc);
if (suser(cred, NULL) || proc_forcequota(p)) {
for (i = 0; i < MAXQUOTAS; i++) {
if ((dq->dq_flags & DQ_BLKS) == 0 &&
cp->c_uid == kauth_cred_getuid(cred)) {
#if 0
- printf("\nwrite failed, %s disk limit reached\n",
+ printf("\nhfs: write failed, %s disk limit reached\n",
quotatypes[type]);
#endif
dq->dq_flags |= DQ_BLKS;
VTOHFS(vp)->hfs_qfiles[type].qf_btime;
#if 0
if (cp->c_uid == kauth_cred_getuid(cred))
- printf("\nwarning, %s %s\n",
+ printf("\nhfs: warning, %s %s\n",
quotatypes[type], "disk quota exceeded");
#endif
dqunlock(dq);
return (0);
}
- if (tv.tv_sec > dq->dq_btime) {
+ 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("\nwrite failed, %s %s\n",
+ printf("\nhfs: write failed, %s %s\n",
quotatypes[type],
"disk quota exceeded for too long");
#endif
int
hfs_chkiq(cp, change, cred, flags)
register struct cnode *cp;
- long change;
+ int32_t change;
kauth_cred_t cred;
int flags;
{
return (0);
}
p = current_proc();
- if (cred == NOCRED)
+ /*
+ * This use of proc_ucred() is safe because kernproc credential never
+ * changes.
+ */
+ if (!IS_VALID_CRED(cred))
cred = proc_ucred(kernproc);
if (suser(cred, NULL) || proc_forcequota(p)) {
for (i = 0; i < MAXQUOTAS; i++) {
return (error);
}
+
+/*
+ * Check to see if a change to a user's allocation should be permitted or not.
+ * Issue an error message if it should not be permitted. Return 0 if
+ * it should be allowed.
+ */
+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 user would exceed their hard limit, disallow cnode allocation.
+ */
+ 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 user is over their soft limit for too long, disallow cnode
+ * allocation. Reset time limit as they cross their soft limit.
+ */
+ 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);
+}
+
+
/*
* Check for a valid change to a users allocation.
* Issue an error message if appropriate.
int
hfs_chkiqchg(cp, change, cred, type)
struct cnode *cp;
- long change;
+ int32_t change;
kauth_cred_t cred;
int type;
{
register struct dquot *dq = cp->c_dquot[type];
- long ncurinodes;
+ u_int32_t ncurinodes;
struct vnode *vp = cp->c_vp ? cp->c_vp : cp->c_rsrc_vp;
dqlock(dq);
if ((dq->dq_flags & DQ_INODS) == 0 &&
cp->c_uid == kauth_cred_getuid(cred)) {
#if 0
- printf("\nwrite failed, %s cnode limit reached\n",
+ printf("\nhfs: write failed, %s cnode limit reached\n",
quotatypes[type]);
#endif
dq->dq_flags |= DQ_INODS;
VTOHFS(vp)->hfs_qfiles[type].qf_itime;
#if 0
if (cp->c_uid == kauth_cred_getuid(cred))
- printf("\nwarning, %s %s\n",
+ printf("\nhfs: warning, %s %s\n",
quotatypes[type], "cnode quota exceeded");
#endif
dqunlock(dq);
return (0);
}
- if (tv.tv_sec > dq->dq_itime) {
+ 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("\nwrite failed, %s %s\n",
+ printf("\nhfs: write failed, %s %s\n",
quotatypes[type],
"cnode quota exceeded for too long");
#endif
int error = 0;
struct hfs_quotaon_cargs args;
+ /* Finish setting up quota structures. */
+ dqhashinit();
+
qfp = &hfsmp->hfs_qfiles[type];
if ( (qf_get(qfp, QTF_OPENING)) )
error = EACCES;
goto out;
}
- vfs_setflags(mp, (uint64_t)((unsigned int)MNT_QUOTA));
+ vfs_setflags(mp, (u_int64_t)((unsigned int)MNT_QUOTA));
+ hfs_lock_mount (hfsmp);
+ hfsmp->hfs_flags |= HFS_QUOTAS;
+ hfs_unlock_mount (hfsmp);
vnode_setnoflush(vp);
/*
* Save the credential of the process that turned on quotas.
if (error) {
(void) vnode_close(vp, FREAD|FWRITE, NULL);
- kauth_cred_rele(qfp->qf_cred);
- qfp->qf_cred = NOCRED;
+ if (IS_VALID_CRED(qfp->qf_cred))
+ kauth_cred_unref(&qfp->qf_cred);
qfp->qf_vp = NULLVP;
goto out;
}
struct hfsmount *hfsmp = VFSTOHFS(mp);
struct quotafile *qfp;
int error;
- kauth_cred_t cred;
struct hfs_quotaoff_cargs args;
+ /*
+ * If quotas haven't been initialized, there's no work to be done.
+ */
+ if (!dqisinitialized())
+ return (0);
+
qfp = &hfsmp->hfs_qfiles[type];
if ( (qf_get(qfp, QTF_CLOSING)) )
/*
* Search vnodes associated with this mount point,
* deleting any references to quota file being closed.
- *
+ *
* hfs_quotaoff_callback will be called for each vnode
* hung off of this mount point
* the vnode will be in an 'unbusy' state (VNODE_WAIT) and
error = vnode_close(qvp, FREAD|FWRITE, NULL);
qfp->qf_vp = NULLVP;
- cred = qfp->qf_cred;
- if (cred != NOCRED) {
- qfp->qf_cred = NOCRED;
- kauth_cred_rele(cred);
- }
+
+ 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, (uint64_t)((unsigned int)MNT_QUOTA));
+ if (type == MAXQUOTAS) {
+ vfs_clearflags(mp, (u_int64_t)((unsigned int)MNT_QUOTA));
+ hfs_lock_mount (hfsmp);
+ hfsmp->hfs_flags &= ~HFS_QUOTAS;
+ hfs_unlock_mount (hfsmp);
+ }
qf_put(qfp, QTF_CLOSING);
return (error);
}
+/*
+ * hfs_quotacheck - checks quotas mountwide for a hypothetical situation. It probes
+ * the quota data structures to see if adding an inode would be allowed or not. If it
+ * will be allowed, the change is made. Otherwise, it reports an error back out so the
+ * caller will know not to proceed with inode allocation in the HFS Catalog.
+ *
+ * Note that this function ONLY tests for addition of inodes, not subtraction.
+ */
+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)) {
+ /* This use of proc_ucred() is safe because kernproc credential never changes */
+ cred = proc_ucred(kernproc);
+ }
+
+ if (suser(cred, NULL) || proc_forcequota(p)) {
+ for (i = 0; i < MAXQUOTAS; i++) {
+ /* Select if user or group id should be used */
+ 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;
+
+ /* Check quota information */
+ error = hfs_isiqchg_allowed(dq, hfsmp, change, cred, i, id);
+ if (error) {
+ dqrele(dq);
+ break;
+ }
+
+ dqlock(dq);
+ /* Update quota information */
+ dq->dq_curinodes += change;
+ dqunlock(dq);
+ dqrele(dq);
+ }
+ }
+
+ return error;
+}
+
+
/*
* Q_GETQUOTA - return current values in a dqblk structure.
*/
int
hfs_getquota(mp, id, type, datap)
struct mount *mp;
- u_long id;
+ u_int32_t id;
int type;
caddr_t datap;
{
int
hfs_setquota(mp, id, type, datap)
struct mount *mp;
- u_long id;
+ u_int32_t id;
int type;
caddr_t datap;
{
int
hfs_setuse(mp, id, type, datap)
struct mount *mp;
- u_long id;
+ u_int32_t id;
int type;
caddr_t datap;
{
struct hfsmount *hfsmp = VFSTOHFS(mp);
int i;
+ if (!dqisinitialized())
+ return (0);
+
/*
* Check if the mount point has any quotas.
* If not, simply return.