]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/hfs/hfs_quota.c
xnu-2782.40.9.tar.gz
[apple/xnu.git] / bsd / hfs / hfs_quota.c
index 80b01d62c1814d05092cedd040524389e5a4597d..989bd67bf1f728548eef2b8d5cc809ab9fe879c9 100644 (file)
@@ -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 <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>
@@ -76,6 +83,7 @@
 #include <hfs/hfs_quota.h>
 #include <hfs/hfs_mount.h>
 
+
 /*
  * 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.