]> git.proxmox.com Git - mirror_zfs.git/blobdiff - module/zfs/zfs_vfsops.c
Project Quota on ZFS
[mirror_zfs.git] / module / zfs / zfs_vfsops.c
index bb380c920cbc10e24a7e9cd81ef3cb04bd34456f..971fd54bc9dd28824ac75cac4b5ec63f5d406023 100644 (file)
@@ -536,8 +536,14 @@ unregister:
 
 static int
 zfs_space_delta_cb(dmu_object_type_t bonustype, void *data,
-    uint64_t *userp, uint64_t *groupp)
+    uint64_t *userp, uint64_t *groupp, uint64_t *projectp)
 {
+       sa_hdr_phys_t sa;
+       sa_hdr_phys_t *sap = data;
+       uint64_t flags;
+       int hdrsize;
+       boolean_t swap = B_FALSE;
+
        /*
         * Is it a valid type of object to track?
         */
@@ -557,42 +563,49 @@ zfs_space_delta_cb(dmu_object_type_t bonustype, void *data,
                znode_phys_t *znp = data;
                *userp = znp->zp_uid;
                *groupp = znp->zp_gid;
+               *projectp = ZFS_DEFAULT_PROJID;
+               return (0);
+       }
+
+       if (sap->sa_magic == 0) {
+               /*
+                * This should only happen for newly created files
+                * that haven't had the znode data filled in yet.
+                */
+               *userp = 0;
+               *groupp = 0;
+               *projectp = ZFS_DEFAULT_PROJID;
+               return (0);
+       }
+
+       sa = *sap;
+       if (sa.sa_magic == BSWAP_32(SA_MAGIC)) {
+               sa.sa_magic = SA_MAGIC;
+               sa.sa_layout_info = BSWAP_16(sa.sa_layout_info);
+               swap = B_TRUE;
        } else {
-               int hdrsize;
-               sa_hdr_phys_t *sap = data;
-               sa_hdr_phys_t sa = *sap;
-               boolean_t swap = B_FALSE;
-
-               ASSERT(bonustype == DMU_OT_SA);
-
-               if (sa.sa_magic == 0) {
-                       /*
-                        * This should only happen for newly created
-                        * files that haven't had the znode data filled
-                        * in yet.
-                        */
-                       *userp = 0;
-                       *groupp = 0;
-                       return (0);
-               }
-               if (sa.sa_magic == BSWAP_32(SA_MAGIC)) {
-                       sa.sa_magic = SA_MAGIC;
-                       sa.sa_layout_info = BSWAP_16(sa.sa_layout_info);
-                       swap = B_TRUE;
-               } else {
-                       VERIFY3U(sa.sa_magic, ==, SA_MAGIC);
-               }
+               VERIFY3U(sa.sa_magic, ==, SA_MAGIC);
+       }
 
-               hdrsize = sa_hdrsize(&sa);
-               VERIFY3U(hdrsize, >=, sizeof (sa_hdr_phys_t));
-               *userp = *((uint64_t *)((uintptr_t)data + hdrsize +
-                   SA_UID_OFFSET));
-               *groupp = *((uint64_t *)((uintptr_t)data + hdrsize +
-                   SA_GID_OFFSET));
-               if (swap) {
-                       *userp = BSWAP_64(*userp);
-                       *groupp = BSWAP_64(*groupp);
-               }
+       hdrsize = sa_hdrsize(&sa);
+       VERIFY3U(hdrsize, >=, sizeof (sa_hdr_phys_t));
+
+       *userp = *((uint64_t *)((uintptr_t)data + hdrsize + SA_UID_OFFSET));
+       *groupp = *((uint64_t *)((uintptr_t)data + hdrsize + SA_GID_OFFSET));
+       flags = *((uint64_t *)((uintptr_t)data + hdrsize + SA_FLAGS_OFFSET));
+       if (swap)
+               flags = BSWAP_64(flags);
+
+       if (flags & ZFS_PROJID)
+               *projectp = *((uint64_t *)((uintptr_t)data + hdrsize +
+                   SA_PROJID_OFFSET));
+       else
+               *projectp = ZFS_DEFAULT_PROJID;
+
+       if (swap) {
+               *userp = BSWAP_64(*userp);
+               *groupp = BSWAP_64(*groupp);
+               *projectp = BSWAP_64(*projectp);
        }
        return (0);
 }
@@ -624,6 +637,9 @@ zfs_userquota_prop_to_obj(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type)
        case ZFS_PROP_GROUPUSED:
        case ZFS_PROP_GROUPOBJUSED:
                return (DMU_GROUPUSED_OBJECT);
+       case ZFS_PROP_PROJECTUSED:
+       case ZFS_PROP_PROJECTOBJUSED:
+               return (DMU_PROJECTUSED_OBJECT);
        case ZFS_PROP_USERQUOTA:
                return (zfsvfs->z_userquota_obj);
        case ZFS_PROP_GROUPQUOTA:
@@ -632,6 +648,10 @@ zfs_userquota_prop_to_obj(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type)
                return (zfsvfs->z_userobjquota_obj);
        case ZFS_PROP_GROUPOBJQUOTA:
                return (zfsvfs->z_groupobjquota_obj);
+       case ZFS_PROP_PROJECTQUOTA:
+               return (zfsvfs->z_projectquota_obj);
+       case ZFS_PROP_PROJECTOBJQUOTA:
+               return (zfsvfs->z_projectobjquota_obj);
        default:
                return (ZFS_NO_OBJECT);
        }
@@ -651,8 +671,16 @@ zfs_userspace_many(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type,
        if (!dmu_objset_userspace_present(zfsvfs->z_os))
                return (SET_ERROR(ENOTSUP));
 
+       if ((type == ZFS_PROP_PROJECTQUOTA || type == ZFS_PROP_PROJECTUSED ||
+           type == ZFS_PROP_PROJECTOBJQUOTA ||
+           type == ZFS_PROP_PROJECTOBJUSED) &&
+           !dmu_objset_projectquota_present(zfsvfs->z_os))
+               return (SET_ERROR(ENOTSUP));
+
        if ((type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED ||
-           type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA) &&
+           type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA ||
+           type == ZFS_PROP_PROJECTOBJUSED ||
+           type == ZFS_PROP_PROJECTOBJQUOTA) &&
            !dmu_objset_userobjspace_present(zfsvfs->z_os))
                return (SET_ERROR(ENOTSUP));
 
@@ -662,7 +690,8 @@ zfs_userspace_many(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type,
                return (0);
        }
 
-       if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED)
+       if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED ||
+           type == ZFS_PROP_PROJECTOBJUSED)
                offset = DMU_OBJACCT_PREFIX_LEN;
 
        for (zap_cursor_init_serialized(&zc, zfsvfs->z_os, obj, *cookiep);
@@ -731,15 +760,27 @@ zfs_userspace_one(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type,
                return (SET_ERROR(ENOTSUP));
 
        if ((type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED ||
-           type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA) &&
+           type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA ||
+           type == ZFS_PROP_PROJECTOBJUSED ||
+           type == ZFS_PROP_PROJECTOBJQUOTA) &&
            !dmu_objset_userobjspace_present(zfsvfs->z_os))
                return (SET_ERROR(ENOTSUP));
 
+       if (type == ZFS_PROP_PROJECTQUOTA || type == ZFS_PROP_PROJECTUSED ||
+           type == ZFS_PROP_PROJECTOBJQUOTA ||
+           type == ZFS_PROP_PROJECTOBJUSED) {
+               if (!dmu_objset_projectquota_present(zfsvfs->z_os))
+                       return (SET_ERROR(ENOTSUP));
+               if (!zpl_is_valid_projid(rid))
+                       return (SET_ERROR(EINVAL));
+       }
+
        obj = zfs_userquota_prop_to_obj(zfsvfs, type);
        if (obj == ZFS_NO_OBJECT)
                return (0);
 
-       if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED) {
+       if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED ||
+           type == ZFS_PROP_PROJECTOBJUSED) {
                strlcpy(buf, DMU_OBJACCT_PREFIX, DMU_OBJACCT_PREFIX_LEN + 1);
                offset = DMU_OBJACCT_PREFIX_LEN;
        }
@@ -780,6 +821,22 @@ zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type,
        case ZFS_PROP_GROUPOBJQUOTA:
                objp = &zfsvfs->z_groupobjquota_obj;
                break;
+       case ZFS_PROP_PROJECTQUOTA:
+               if (!dmu_objset_projectquota_enabled(zfsvfs->z_os))
+                       return (SET_ERROR(ENOTSUP));
+               if (!zpl_is_valid_projid(rid))
+                       return (SET_ERROR(EINVAL));
+
+               objp = &zfsvfs->z_projectquota_obj;
+               break;
+       case ZFS_PROP_PROJECTOBJQUOTA:
+               if (!dmu_objset_projectquota_enabled(zfsvfs->z_os))
+                       return (SET_ERROR(ENOTSUP));
+               if (!zpl_is_valid_projid(rid))
+                       return (SET_ERROR(EINVAL));
+
+               objp = &zfsvfs->z_projectobjquota_obj;
+               break;
        default:
                return (SET_ERROR(EINVAL));
        }
@@ -827,35 +884,51 @@ zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type,
 }
 
 boolean_t
-zfs_fuid_overobjquota(zfsvfs_t *zfsvfs, boolean_t isgroup, uint64_t fuid)
+zfs_id_overobjquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id)
 {
        char buf[20 + DMU_OBJACCT_PREFIX_LEN];
-       uint64_t used, quota, usedobj, quotaobj;
+       uint64_t used, quota, quotaobj;
        int err;
 
        if (!dmu_objset_userobjspace_present(zfsvfs->z_os)) {
                if (dmu_objset_userobjspace_upgradable(zfsvfs->z_os)) {
                        dsl_pool_config_enter(
                            dmu_objset_pool(zfsvfs->z_os), FTAG);
-                       dmu_objset_userobjspace_upgrade(zfsvfs->z_os);
+                       dmu_objset_id_quota_upgrade(zfsvfs->z_os);
                        dsl_pool_config_exit(
                            dmu_objset_pool(zfsvfs->z_os), FTAG);
                }
                return (B_FALSE);
        }
 
-       usedobj = isgroup ? DMU_GROUPUSED_OBJECT : DMU_USERUSED_OBJECT;
-       quotaobj = isgroup ? zfsvfs->z_groupobjquota_obj :
-           zfsvfs->z_userobjquota_obj;
+       if (usedobj == DMU_PROJECTUSED_OBJECT) {
+               if (!dmu_objset_projectquota_present(zfsvfs->z_os)) {
+                       if (dmu_objset_projectquota_upgradable(zfsvfs->z_os)) {
+                               dsl_pool_config_enter(
+                                   dmu_objset_pool(zfsvfs->z_os), FTAG);
+                               dmu_objset_id_quota_upgrade(zfsvfs->z_os);
+                               dsl_pool_config_exit(
+                                   dmu_objset_pool(zfsvfs->z_os), FTAG);
+                       }
+                       return (B_FALSE);
+               }
+               quotaobj = zfsvfs->z_projectobjquota_obj;
+       } else if (usedobj == DMU_USERUSED_OBJECT) {
+               quotaobj = zfsvfs->z_userobjquota_obj;
+       } else if (usedobj == DMU_GROUPUSED_OBJECT) {
+               quotaobj = zfsvfs->z_groupobjquota_obj;
+       } else {
+               return (B_FALSE);
+       }
        if (quotaobj == 0 || zfsvfs->z_replay)
                return (B_FALSE);
 
-       (void) sprintf(buf, "%llx", (longlong_t)fuid);
+       (void) sprintf(buf, "%llx", (longlong_t)id);
        err = zap_lookup(zfsvfs->z_os, quotaobj, buf, 8, 1, &quota);
        if (err != 0)
                return (B_FALSE);
 
-       (void) sprintf(buf, DMU_OBJACCT_PREFIX "%llx", (longlong_t)fuid);
+       (void) sprintf(buf, DMU_OBJACCT_PREFIX "%llx", (longlong_t)id);
        err = zap_lookup(zfsvfs->z_os, usedobj, buf, 8, 1, &used);
        if (err != 0)
                return (B_FALSE);
@@ -863,19 +936,35 @@ zfs_fuid_overobjquota(zfsvfs_t *zfsvfs, boolean_t isgroup, uint64_t fuid)
 }
 
 boolean_t
-zfs_fuid_overquota(zfsvfs_t *zfsvfs, boolean_t isgroup, uint64_t fuid)
+zfs_id_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id)
 {
        char buf[20];
-       uint64_t used, quota, usedobj, quotaobj;
+       uint64_t used, quota, quotaobj;
        int err;
 
-       usedobj = isgroup ? DMU_GROUPUSED_OBJECT : DMU_USERUSED_OBJECT;
-       quotaobj = isgroup ? zfsvfs->z_groupquota_obj : zfsvfs->z_userquota_obj;
-
+       if (usedobj == DMU_PROJECTUSED_OBJECT) {
+               if (!dmu_objset_projectquota_present(zfsvfs->z_os)) {
+                       if (dmu_objset_projectquota_upgradable(zfsvfs->z_os)) {
+                               dsl_pool_config_enter(
+                                   dmu_objset_pool(zfsvfs->z_os), FTAG);
+                               dmu_objset_id_quota_upgrade(zfsvfs->z_os);
+                               dsl_pool_config_exit(
+                                   dmu_objset_pool(zfsvfs->z_os), FTAG);
+                       }
+                       return (B_FALSE);
+               }
+               quotaobj = zfsvfs->z_projectquota_obj;
+       } else if (usedobj == DMU_USERUSED_OBJECT) {
+               quotaobj = zfsvfs->z_userquota_obj;
+       } else if (usedobj == DMU_GROUPUSED_OBJECT) {
+               quotaobj = zfsvfs->z_groupquota_obj;
+       } else {
+               return (B_FALSE);
+       }
        if (quotaobj == 0 || zfsvfs->z_replay)
                return (B_FALSE);
 
-       (void) sprintf(buf, "%llx", (longlong_t)fuid);
+       (void) sprintf(buf, "%llx", (longlong_t)id);
        err = zap_lookup(zfsvfs->z_os, quotaobj, buf, 8, 1, &quota);
        if (err != 0)
                return (B_FALSE);
@@ -887,20 +976,10 @@ zfs_fuid_overquota(zfsvfs_t *zfsvfs, boolean_t isgroup, uint64_t fuid)
 }
 
 boolean_t
-zfs_owner_overquota(zfsvfs_t *zfsvfs, znode_t *zp, boolean_t isgroup)
+zfs_id_overquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id)
 {
-       uint64_t fuid;
-       uint64_t quotaobj;
-       struct inode *ip = ZTOI(zp);
-
-       quotaobj = isgroup ? zfsvfs->z_groupquota_obj : zfsvfs->z_userquota_obj;
-
-       fuid = isgroup ? KGID_TO_SGID(ip->i_gid) : KUID_TO_SUID(ip->i_uid);
-
-       if (quotaobj == 0 || zfsvfs->z_replay)
-               return (B_FALSE);
-
-       return (zfs_fuid_overquota(zfsvfs, isgroup, fuid));
+       return (zfs_id_overblockquota(zfsvfs, usedobj, id) ||
+           zfs_id_overobjquota(zfsvfs, usedobj, id));
 }
 
 /*
@@ -1007,6 +1086,14 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os)
        else if (error != 0)
                return (error);
 
+       error = zap_lookup(os, MASTER_NODE_OBJ,
+           zfs_userquota_prop_prefixes[ZFS_PROP_PROJECTQUOTA],
+           8, 1, &zfsvfs->z_projectquota_obj);
+       if (error == ENOENT)
+               zfsvfs->z_projectquota_obj = 0;
+       else if (error != 0)
+               return (error);
+
        error = zap_lookup(os, MASTER_NODE_OBJ,
            zfs_userquota_prop_prefixes[ZFS_PROP_USEROBJQUOTA],
            8, 1, &zfsvfs->z_userobjquota_obj);
@@ -1023,6 +1110,14 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os)
        else if (error != 0)
                return (error);
 
+       error = zap_lookup(os, MASTER_NODE_OBJ,
+           zfs_userquota_prop_prefixes[ZFS_PROP_PROJECTOBJQUOTA],
+           8, 1, &zfsvfs->z_projectobjquota_obj);
+       if (error == ENOENT)
+               zfsvfs->z_projectobjquota_obj = 0;
+       else if (error != 0)
+               return (error);
+
        error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_FUID_TABLES, 8, 1,
            &zfsvfs->z_fuid_obj);
        if (error == ENOENT)
@@ -1264,6 +1359,83 @@ zfs_check_global_label(const char *dsname, const char *hexsl)
 }
 #endif /* HAVE_MLSLABEL */
 
+static int
+zfs_statfs_project(zfsvfs_t *zfsvfs, znode_t *zp, struct kstatfs *statp,
+    uint32_t bshift)
+{
+       char buf[20 + DMU_OBJACCT_PREFIX_LEN];
+       uint64_t offset = DMU_OBJACCT_PREFIX_LEN;
+       uint64_t quota;
+       uint64_t used;
+       int err;
+
+       strlcpy(buf, DMU_OBJACCT_PREFIX, DMU_OBJACCT_PREFIX_LEN + 1);
+       err = id_to_fuidstr(zfsvfs, NULL, zp->z_projid, buf + offset, B_FALSE);
+       if (err)
+               return (err);
+
+       if (zfsvfs->z_projectquota_obj == 0)
+               goto objs;
+
+       err = zap_lookup(zfsvfs->z_os, zfsvfs->z_projectquota_obj,
+           buf + offset, 8, 1, &quota);
+       if (err == ENOENT)
+               goto objs;
+       else if (err)
+               return (err);
+
+       err = zap_lookup(zfsvfs->z_os, DMU_PROJECTUSED_OBJECT,
+           buf + offset, 8, 1, &used);
+       if (unlikely(err == ENOENT)) {
+               uint32_t blksize;
+               u_longlong_t nblocks;
+
+               /*
+                * Quota accounting is async, so it is possible race case.
+                * There is at least one object with the given project ID.
+                */
+               sa_object_size(zp->z_sa_hdl, &blksize, &nblocks);
+               if (unlikely(zp->z_blksz == 0))
+                       blksize = zfsvfs->z_max_blksz;
+
+               used = blksize * nblocks;
+       } else if (err) {
+               return (err);
+       }
+
+       statp->f_blocks = quota >> bshift;
+       statp->f_bfree = (quota > used) ? ((quota - used) >> bshift) : 0;
+       statp->f_bavail = statp->f_bfree;
+
+objs:
+       if (zfsvfs->z_projectobjquota_obj == 0)
+               return (0);
+
+       err = zap_lookup(zfsvfs->z_os, zfsvfs->z_projectobjquota_obj,
+           buf + offset, 8, 1, &quota);
+       if (err == ENOENT)
+               return (0);
+       else if (err)
+               return (err);
+
+       err = zap_lookup(zfsvfs->z_os, DMU_PROJECTUSED_OBJECT,
+           buf, 8, 1, &used);
+       if (unlikely(err == ENOENT)) {
+               /*
+                * Quota accounting is async, so it is possible race case.
+                * There is at least one object with the given project ID.
+                */
+               used = 1;
+       } else if (err) {
+               return (err);
+       }
+
+       statp->f_files = quota;
+       statp->f_ffree = (quota > used) ? (quota - used) : 0;
+
+       return (0);
+}
+
 int
 zfs_statvfs(struct dentry *dentry, struct kstatfs *statp)
 {
@@ -1271,6 +1443,7 @@ zfs_statvfs(struct dentry *dentry, struct kstatfs *statp)
        uint64_t refdbytes, availbytes, usedobjs, availobjs;
        uint64_t fsid;
        uint32_t bshift;
+       int err = 0;
 
        ZFS_ENTER(zfsvfs);
 
@@ -1322,8 +1495,17 @@ zfs_statvfs(struct dentry *dentry, struct kstatfs *statp)
         */
        bzero(statp->f_spare, sizeof (statp->f_spare));
 
+       if (dmu_objset_projectquota_enabled(zfsvfs->z_os) &&
+           dmu_objset_projectquota_present(zfsvfs->z_os)) {
+               znode_t *zp = ITOZ(dentry->d_inode);
+
+               if (zp->z_pflags & ZFS_PROJINHERIT && zp->z_projid &&
+                   zpl_is_valid_projid(zp->z_projid))
+                       err = zfs_statfs_project(zfsvfs, zp, statp, bshift);
+       }
+
        ZFS_EXIT(zfsvfs);
-       return (0);
+       return (err);
 }
 
 int
@@ -2170,9 +2352,9 @@ EXPORT_SYMBOL(zfs_resume_fs);
 EXPORT_SYMBOL(zfs_userspace_one);
 EXPORT_SYMBOL(zfs_userspace_many);
 EXPORT_SYMBOL(zfs_set_userquota);
-EXPORT_SYMBOL(zfs_owner_overquota);
-EXPORT_SYMBOL(zfs_fuid_overquota);
-EXPORT_SYMBOL(zfs_fuid_overobjquota);
+EXPORT_SYMBOL(zfs_id_overblockquota);
+EXPORT_SYMBOL(zfs_id_overobjquota);
+EXPORT_SYMBOL(zfs_id_overquota);
 EXPORT_SYMBOL(zfs_set_version);
 EXPORT_SYMBOL(zfsvfs_create);
 EXPORT_SYMBOL(zfsvfs_free);