diff options
Diffstat (limited to 'module/zfs/zfs_quota.c')
-rw-r--r-- | module/zfs/zfs_quota.c | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/module/zfs/zfs_quota.c b/module/zfs/zfs_quota.c new file mode 100644 index 000000000..1acfbf1ea --- /dev/null +++ b/module/zfs/zfs_quota.c @@ -0,0 +1,475 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011 Pawel Jakub Dawidek <[email protected]>. + * All rights reserved. + * Copyright (c) 2012, 2015, 2018 by Delphix. All rights reserved. + * Copyright (c) 2014 Integros [integros.com] + * Copyright 2016 Nexenta Systems, Inc. All rights reserved. + */ + +/* Portions Copyright 2010 Robert Milkowski */ + +#include <sys/avl.h> +#include <sys/dmu_objset.h> +#include <sys/sa.h> +#include <sys/sa_impl.h> +#include <sys/zap.h> +#include <sys/zfs_project.h> +#include <sys/zfs_quota.h> +#include <sys/zfs_znode.h> + +int +zfs_space_delta_cb(dmu_object_type_t bonustype, void *data, + 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? + */ + if (bonustype != DMU_OT_ZNODE && bonustype != DMU_OT_SA) + return (SET_ERROR(ENOENT)); + + /* + * If we have a NULL data pointer + * then assume the id's aren't changing and + * return EEXIST to the dmu to let it know to + * use the same ids + */ + if (data == NULL) + return (SET_ERROR(EEXIST)); + + if (bonustype == DMU_OT_ZNODE) { + 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 { + 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)); + 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); +} + +static void +fuidstr_to_sid(zfsvfs_t *zfsvfs, const char *fuidstr, + char *domainbuf, int buflen, uid_t *ridp) +{ + uint64_t fuid; + const char *domain; + + fuid = zfs_strtonum(fuidstr, NULL); + + domain = zfs_fuid_find_by_idx(zfsvfs, FUID_INDEX(fuid)); + if (domain) + (void) strlcpy(domainbuf, domain, buflen); + else + domainbuf[0] = '\0'; + *ridp = FUID_RID(fuid); +} + +static uint64_t +zfs_userquota_prop_to_obj(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type) +{ + switch (type) { + case ZFS_PROP_USERUSED: + case ZFS_PROP_USEROBJUSED: + return (DMU_USERUSED_OBJECT); + 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: + return (zfsvfs->z_groupquota_obj); + case ZFS_PROP_USEROBJQUOTA: + 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); + } +} + +int +zfs_userspace_many(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, + uint64_t *cookiep, void *vbuf, uint64_t *bufsizep) +{ + int error; + zap_cursor_t zc; + zap_attribute_t za; + zfs_useracct_t *buf = vbuf; + uint64_t obj; + int offset = 0; + + 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_PROJECTOBJUSED || + type == ZFS_PROP_PROJECTOBJQUOTA) && + !dmu_objset_userobjspace_present(zfsvfs->z_os)) + return (SET_ERROR(ENOTSUP)); + + obj = zfs_userquota_prop_to_obj(zfsvfs, type); + if (obj == ZFS_NO_OBJECT) { + *bufsizep = 0; + return (0); + } + + 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); + (error = zap_cursor_retrieve(&zc, &za)) == 0; + zap_cursor_advance(&zc)) { + if ((uintptr_t)buf - (uintptr_t)vbuf + sizeof (zfs_useracct_t) > + *bufsizep) + break; + + /* + * skip object quota (with zap name prefix DMU_OBJACCT_PREFIX) + * when dealing with block quota and vice versa. + */ + if ((offset > 0) != (strncmp(za.za_name, DMU_OBJACCT_PREFIX, + DMU_OBJACCT_PREFIX_LEN) == 0)) + continue; + + fuidstr_to_sid(zfsvfs, za.za_name + offset, + buf->zu_domain, sizeof (buf->zu_domain), &buf->zu_rid); + + buf->zu_space = za.za_first_integer; + buf++; + } + if (error == ENOENT) + error = 0; + + ASSERT3U((uintptr_t)buf - (uintptr_t)vbuf, <=, *bufsizep); + *bufsizep = (uintptr_t)buf - (uintptr_t)vbuf; + *cookiep = zap_cursor_serialize(&zc); + zap_cursor_fini(&zc); + return (error); +} + +int +zfs_userspace_one(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, + const char *domain, uint64_t rid, uint64_t *valp) +{ + char buf[20 + DMU_OBJACCT_PREFIX_LEN]; + int offset = 0; + int err; + uint64_t obj; + + *valp = 0; + + if (!dmu_objset_userspace_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_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 || + type == ZFS_PROP_PROJECTOBJUSED) { + strlcpy(buf, DMU_OBJACCT_PREFIX, DMU_OBJACCT_PREFIX_LEN + 1); + offset = DMU_OBJACCT_PREFIX_LEN; + } + + err = zfs_id_to_fuidstr(zfsvfs, domain, rid, buf + offset, B_FALSE); + if (err) + return (err); + + err = zap_lookup(zfsvfs->z_os, obj, buf, 8, 1, valp); + if (err == ENOENT) + err = 0; + return (err); +} + +int +zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, + const char *domain, uint64_t rid, uint64_t quota) +{ + char buf[32]; + int err; + dmu_tx_t *tx; + uint64_t *objp; + boolean_t fuid_dirtied; + + if (zfsvfs->z_version < ZPL_VERSION_USERSPACE) + return (SET_ERROR(ENOTSUP)); + + switch (type) { + case ZFS_PROP_USERQUOTA: + objp = &zfsvfs->z_userquota_obj; + break; + case ZFS_PROP_GROUPQUOTA: + objp = &zfsvfs->z_groupquota_obj; + break; + case ZFS_PROP_USEROBJQUOTA: + objp = &zfsvfs->z_userobjquota_obj; + break; + 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)); + } + + err = zfs_id_to_fuidstr(zfsvfs, domain, rid, buf, B_TRUE); + if (err) + return (err); + fuid_dirtied = zfsvfs->z_fuid_dirty; + + tx = dmu_tx_create(zfsvfs->z_os); + dmu_tx_hold_zap(tx, *objp ? *objp : DMU_NEW_OBJECT, B_TRUE, NULL); + if (*objp == 0) { + dmu_tx_hold_zap(tx, MASTER_NODE_OBJ, B_TRUE, + zfs_userquota_prop_prefixes[type]); + } + if (fuid_dirtied) + zfs_fuid_txhold(zfsvfs, tx); + err = dmu_tx_assign(tx, TXG_WAIT); + if (err) { + dmu_tx_abort(tx); + return (err); + } + + mutex_enter(&zfsvfs->z_lock); + if (*objp == 0) { + *objp = zap_create(zfsvfs->z_os, DMU_OT_USERGROUP_QUOTA, + DMU_OT_NONE, 0, tx); + VERIFY(0 == zap_add(zfsvfs->z_os, MASTER_NODE_OBJ, + zfs_userquota_prop_prefixes[type], 8, 1, objp, tx)); + } + mutex_exit(&zfsvfs->z_lock); + + if (quota == 0) { + err = zap_remove(zfsvfs->z_os, *objp, buf, tx); + if (err == ENOENT) + err = 0; + } else { + err = zap_update(zfsvfs->z_os, *objp, buf, 8, 1, "a, tx); + } + ASSERT(err == 0); + if (fuid_dirtied) + zfs_fuid_sync(zfsvfs, tx); + dmu_tx_commit(tx); + return (err); +} + +boolean_t +zfs_id_overobjquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id) +{ + char buf[20 + DMU_OBJACCT_PREFIX_LEN]; + 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_id_quota_upgrade(zfsvfs->z_os); + dsl_pool_config_exit( + dmu_objset_pool(zfsvfs->z_os), FTAG); + } + return (B_FALSE); + } + + 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)id); + err = zap_lookup(zfsvfs->z_os, quotaobj, buf, 8, 1, "a); + if (err != 0) + return (B_FALSE); + + (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); + return (used >= quota); +} + +boolean_t +zfs_id_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id) +{ + char buf[20]; + uint64_t used, quota, quotaobj; + int err; + + 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)id); + err = zap_lookup(zfsvfs->z_os, quotaobj, buf, 8, 1, "a); + if (err != 0) + return (B_FALSE); + + err = zap_lookup(zfsvfs->z_os, usedobj, buf, 8, 1, &used); + if (err != 0) + return (B_FALSE); + return (used >= quota); +} + +boolean_t +zfs_id_overquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id) +{ + return (zfs_id_overblockquota(zfsvfs, usedobj, id) || + zfs_id_overobjquota(zfsvfs, usedobj, id)); +} + +EXPORT_SYMBOL(zfs_space_delta_cb); +EXPORT_SYMBOL(zfs_userspace_one); +EXPORT_SYMBOL(zfs_userspace_many); +EXPORT_SYMBOL(zfs_set_userquota); +EXPORT_SYMBOL(zfs_id_overblockquota); +EXPORT_SYMBOL(zfs_id_overobjquota); +EXPORT_SYMBOL(zfs_id_overquota); |