X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/ff6e181ae92fc6f1e89841290f461d1f2f9badd9..c18c124eaa464aaaa5549e99e5a70fc9cbb50944:/bsd/hfs/hfs_quota.c diff --git a/bsd/hfs/hfs_quota.c b/bsd/hfs/hfs_quota.c index 0fc403f9d..989bd67bf 100644 --- a/bsd/hfs/hfs_quota.c +++ b/bsd/hfs/hfs_quota.c @@ -1,14 +1,19 @@ /* - * 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 @@ -18,7 +23,7 @@ * 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 @@ -68,6 +73,7 @@ #include #include #include +#include #include #include #include @@ -77,6 +83,7 @@ #include #include + /* * Quota name to error message mapping. */ @@ -99,6 +106,7 @@ hfs_getinoquota(cp) 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); @@ -106,20 +114,30 @@ hfs_getinoquota(cp) * 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); } @@ -164,7 +182,11 @@ hfs_chkdq(cp, change, cred, 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++) { @@ -216,7 +238,7 @@ hfs_chkdqchg(cp, change, cred, type) 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; @@ -238,18 +260,18 @@ hfs_chkdqchg(cp, change, cred, type) 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 @@ -271,7 +293,7 @@ hfs_chkdqchg(cp, change, cred, type) int hfs_chkiq(cp, change, cred, flags) register struct cnode *cp; - long change; + int32_t change; kauth_cred_t cred; int flags; { @@ -305,7 +327,11 @@ hfs_chkiq(cp, change, cred, 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++) { @@ -332,6 +358,66 @@ hfs_chkiq(cp, change, cred, flags) 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. @@ -339,12 +425,12 @@ hfs_chkiq(cp, change, cred, flags) 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); @@ -357,7 +443,7 @@ hfs_chkiqchg(cp, change, cred, type) 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; @@ -379,18 +465,18 @@ hfs_chkiqchg(cp, change, cred, type) 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 @@ -468,6 +554,9 @@ hfs_quotaon(p, mp, type, fnamep) 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)) ) @@ -482,7 +571,10 @@ hfs_quotaon(p, mp, type, fnamep) 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. @@ -496,8 +588,8 @@ hfs_quotaon(p, mp, type, fnamep) 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; } @@ -564,9 +656,14 @@ hfs_quotaoff(__unused struct proc *p, struct mount *mp, register int type) 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)) ) @@ -581,7 +678,7 @@ hfs_quotaoff(__unused struct proc *p, struct mount *mp, register int type) /* * 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 @@ -599,29 +696,93 @@ hfs_quotaoff(__unused struct proc *p, struct mount *mp, register int type) 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; { @@ -647,7 +808,7 @@ hfs_getquota(mp, id, type, datap) int hfs_setquota(mp, id, type, datap) struct mount *mp; - u_long id; + u_int32_t id; int type; caddr_t datap; { @@ -709,7 +870,7 @@ hfs_setquota(mp, id, type, datap) int hfs_setuse(mp, id, type, datap) struct mount *mp; - u_long id; + u_int32_t id; int type; caddr_t datap; { @@ -780,6 +941,9 @@ hfs_qsync(mp) struct hfsmount *hfsmp = VFSTOHFS(mp); int i; + if (!dqisinitialized()) + return (0); + /* * Check if the mount point has any quotas. * If not, simply return.