X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/89b3af67bb32e691275bf6fa803d1834b2284115..c18c124eaa464aaaa5549e99e5a70fc9cbb50944:/bsd/hfs/hfs_quota.c diff --git a/bsd/hfs/hfs_quota.c b/bsd/hfs/hfs_quota.c index b2945e081..989bd67bf 100644 --- a/bsd/hfs/hfs_quota.c +++ b/bsd/hfs/hfs_quota.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * Copyright (c) 2002-2008 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * @@ -73,6 +73,7 @@ #include #include #include +#include #include #include #include @@ -82,6 +83,7 @@ #include #include + /* * Quota name to error message mapping. */ @@ -104,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); @@ -111,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); } @@ -225,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; @@ -247,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 @@ -280,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; { @@ -345,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. @@ -352,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); @@ -370,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; @@ -392,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 @@ -481,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)) ) @@ -495,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. @@ -579,6 +658,12 @@ hfs_quotaoff(__unused struct proc *p, struct mount *mp, register int type) int error; 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)) ) @@ -593,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 @@ -617,21 +702,87 @@ hfs_quotaoff(__unused struct proc *p, struct mount *mp, register int type) 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; { @@ -657,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; { @@ -719,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; { @@ -790,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.