X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/91447636331957f3d9b5ca5b508f07c526b0074d..c18c124eaa464aaaa5549e99e5a70fc9cbb50944:/bsd/hfs/hfs_quota.c diff --git a/bsd/hfs/hfs_quota.c b/bsd/hfs/hfs_quota.c index 80b01d62c..989bd67bf 100644 --- a/bsd/hfs/hfs_quota.c +++ b/bsd/hfs/hfs_quota.c @@ -1,23 +1,29 @@ /* - * 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@ * - * The contents of this file constitute Original Code as defined in and - * are subject to the Apple Public Source License Version 1.1 (the - * "License"). You may not use this file except in compliance with the - * License. Please obtain a copy of the License at - * http://www.apple.com/publicsource and read it before using this file. + * 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. 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. * - * This Original Code and all software distributed under the License are - * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * 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 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the - * License for the specific language governing rights and limitations - * under the License. + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * 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 @@ -67,6 +73,7 @@ #include #include #include +#include #include #include #include @@ -76,6 +83,7 @@ #include #include + /* * Quota name to error message mapping. */ @@ -98,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); @@ -105,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); } @@ -163,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++) { @@ -215,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; @@ -237,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 @@ -270,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; { @@ -304,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++) { @@ -331,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. @@ -338,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); @@ -356,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; @@ -378,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 @@ -467,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)) ) @@ -481,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. @@ -495,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; } @@ -563,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)) ) @@ -580,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 @@ -598,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; { @@ -646,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; { @@ -708,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; { @@ -779,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.