From afaa9629efc1a2f41e3ed419cfdd1696630b1f03 Mon Sep 17 00:00:00 2001 From: Jinshan Xiong Date: Mon, 31 Aug 2015 17:01:03 -0700 Subject: [PATCH] Add support for user/group dnode accounting & quota This patch tracks dnode usage for each user/group in the DMU_USER/GROUPUSED_OBJECT ZAPs. ZAP entries dedicated to dnode accounting have the key prefixed with "dn-" followed by the UID/GID in string format (as done for the block accounting). A new SPA feature has been added for dnode accounting as well as a new ZPL version. The SPA feature must be enabled in the pool before upgrading the zfs filesystem. During the zfs version upgrade, a "quotacheck" will be executed by marking all dnode as dirty. ZoL-bug-id: https://github.com/zfsonlinux/zfs/issues/3500 Signed-off-by: Johann Lombardi Signed-off-by: Jinshan Xiong Change-Id: I899ff446cbf2aa7e355e7d98a83d614f1cd4624b --- cmd/zdb/zdb.c | 4 +- cmd/zfs/zfs_main.c | 36 +++++++++++-- include/sys/dmu_objset.h | 4 ++ include/sys/dnode.h | 3 ++ include/sys/dsl_deleg.h | 4 ++ include/sys/fs/zfs.h | 5 ++ include/sys/zfs_vfsops.h | 2 + include/zfeature_common.h | 1 + include/zfs_deleg.h | 4 ++ lib/libzfs/libzfs_dataset.c | 15 ++++-- man/man5/zpool-features.5 | 21 ++++++++ module/zcommon/zfs_deleg.c | 4 ++ module/zcommon/zfs_prop.c | 6 ++- module/zfs/dmu_objset.c | 99 ++++++++++++++++++++++++++++++++---- module/zfs/dnode_sync.c | 5 ++ module/zfs/zfeature_common.c | 4 ++ module/zfs/zfs_ioctl.c | 45 +++++++++++++++- module/zfs/zfs_vfsops.c | 58 ++++++++++++++++++--- 18 files changed, 293 insertions(+), 27 deletions(-) diff --git a/cmd/zdb/zdb.c b/cmd/zdb/zdb.c index cbc98d24c2f8..6cf30108fe78 100644 --- a/cmd/zdb/zdb.c +++ b/cmd/zdb/zdb.c @@ -1937,11 +1937,13 @@ dump_object(objset_t *os, uint64_t object, int verbosity, int *print_header) } if (verbosity >= 4) { - (void) printf("\tdnode flags: %s%s%s\n", + (void) printf("\tdnode flags: %s%s%s%s\n", (dn->dn_phys->dn_flags & DNODE_FLAG_USED_BYTES) ? "USED_BYTES " : "", (dn->dn_phys->dn_flags & DNODE_FLAG_USERUSED_ACCOUNTED) ? "USERUSED_ACCOUNTED " : "", + (dn->dn_phys->dn_flags & DNODE_FLAG_USEROBJUSED_ACCOUNTED) ? + "USEROBJUSED_ACCOUNTED " : "", (dn->dn_phys->dn_flags & DNODE_FLAG_SPILL_BLKPTR) ? "SPILL_BLKPTR" : ""); (void) printf("\tdnode maxblkid: %llu\n", diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index ee413a541c61..2ef1f429e3d2 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -2817,11 +2817,15 @@ zfs_do_userspace(int argc, char **argv) cb.cb_width[i] = strlen(gettext(us_field_hdr[i])); for (p = 0; p < ZFS_NUM_USERQUOTA_PROPS; p++) { - if (((p == ZFS_PROP_USERUSED || p == ZFS_PROP_USERQUOTA) && + if ((((p == ZFS_PROP_USERUSED || p == ZFS_PROP_USERQUOTA || + p == ZFS_PROP_USEROBJUSED || p == ZFS_PROP_USEROBJQUOTA) && !(types & (USTYPE_PSX_USR | USTYPE_SMB_USR))) || - ((p == ZFS_PROP_GROUPUSED || p == ZFS_PROP_GROUPQUOTA) && - !(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP)))) + ((p == ZFS_PROP_GROUPUSED || p == ZFS_PROP_GROUPQUOTA || + p == ZFS_PROP_GROUPOBJUSED || + p == ZFS_PROP_GROUPOBJQUOTA) && + !(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP))))) continue; + cb.cb_prop = p; if ((ret = zfs_userspace(zhp, p, userspace_cb, &cb)) != 0) return (ret); @@ -3993,6 +3997,11 @@ zfs_do_receive(int argc, char **argv) #define ZFS_DELEG_PERM_GROUPQUOTA "groupquota" #define ZFS_DELEG_PERM_USERUSED "userused" #define ZFS_DELEG_PERM_GROUPUSED "groupused" +#define ZFS_DELEG_PERM_USEROBJQUOTA "userobjquota" +#define ZFS_DELEG_PERM_GROUPOBJQUOTA "groupobjquota" +#define ZFS_DELEG_PERM_USEROBJUSED "userobjused" +#define ZFS_DELEG_PERM_GROUPOBJUSED "groupobjused" + #define ZFS_DELEG_PERM_HOLD "hold" #define ZFS_DELEG_PERM_RELEASE "release" #define ZFS_DELEG_PERM_DIFF "diff" @@ -4023,6 +4032,10 @@ static zfs_deleg_perm_tab_t zfs_deleg_perm_tbl[] = { { ZFS_DELEG_PERM_USERPROP, ZFS_DELEG_NOTE_USERPROP }, { ZFS_DELEG_PERM_USERQUOTA, ZFS_DELEG_NOTE_USERQUOTA }, { ZFS_DELEG_PERM_USERUSED, ZFS_DELEG_NOTE_USERUSED }, + { ZFS_DELEG_PERM_USEROBJQUOTA, ZFS_DELEG_NOTE_USEROBJQUOTA }, + { ZFS_DELEG_PERM_USEROBJUSED, ZFS_DELEG_NOTE_USEROBJUSED }, + { ZFS_DELEG_PERM_GROUPOBJQUOTA, ZFS_DELEG_NOTE_GROUPOBJQUOTA }, + { ZFS_DELEG_PERM_GROUPOBJUSED, ZFS_DELEG_NOTE_GROUPOBJUSED }, { NULL, ZFS_DELEG_NOTE_NONE } }; @@ -4100,6 +4113,10 @@ deleg_perm_type(zfs_deleg_note_t note) case ZFS_DELEG_NOTE_USERPROP: case ZFS_DELEG_NOTE_USERQUOTA: case ZFS_DELEG_NOTE_USERUSED: + case ZFS_DELEG_NOTE_USEROBJQUOTA: + case ZFS_DELEG_NOTE_USEROBJUSED: + case ZFS_DELEG_NOTE_GROUPOBJQUOTA: + case ZFS_DELEG_NOTE_GROUPOBJUSED: /* other */ return (gettext("other")); default: @@ -4603,6 +4620,19 @@ deleg_perm_comment(zfs_deleg_note_t note) case ZFS_DELEG_NOTE_USERUSED: str = gettext("Allows reading any userused@... property"); break; + case ZFS_DELEG_NOTE_USEROBJQUOTA: + str = gettext("Allows accessing any userobjquota@... property"); + break; + case ZFS_DELEG_NOTE_GROUPOBJQUOTA: + str = gettext("Allows accessing any \n\t\t\t\t" + "groupobjquota@... property"); + break; + case ZFS_DELEG_NOTE_GROUPOBJUSED: + str = gettext("Allows reading any groupobjused@... property"); + break; + case ZFS_DELEG_NOTE_USEROBJUSED: + str = gettext("Allows reading any userobjused@... property"); + break; /* other */ default: str = ""; diff --git a/include/sys/dmu_objset.h b/include/sys/dmu_objset.h index 837a0d5107b7..d022c72deef8 100644 --- a/include/sys/dmu_objset.h +++ b/include/sys/dmu_objset.h @@ -56,6 +56,7 @@ struct dmu_tx; (arc_buf_size(buf) > OBJSET_OLD_PHYS_SIZE) #define OBJSET_FLAG_USERACCOUNTING_COMPLETE (1ULL<<0) +#define OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE (1ULL<<1) typedef struct objset_phys { dnode_phys_t os_meta_dnode; @@ -172,6 +173,9 @@ void dmu_objset_userquota_get_ids(dnode_t *dn, boolean_t before, dmu_tx_t *tx); boolean_t dmu_objset_userused_enabled(objset_t *os); int dmu_objset_userspace_upgrade(objset_t *os); boolean_t dmu_objset_userspace_present(objset_t *os); +boolean_t dmu_objset_userobjused_enabled(objset_t *os); +int dmu_objset_userobjspace_upgrade(objset_t *os); +boolean_t dmu_objset_userobjspace_present(objset_t *os); int dmu_fsname(const char *snapname, char *buf); void dmu_objset_evict_done(objset_t *os); diff --git a/include/sys/dnode.h b/include/sys/dnode.h index c5250d51abf1..01cd98c06731 100644 --- a/include/sys/dnode.h +++ b/include/sys/dnode.h @@ -120,6 +120,9 @@ enum dnode_dirtycontext { /* Does dnode have a SA spill blkptr in bonus? */ #define DNODE_FLAG_SPILL_BLKPTR (1<<2) +/* User/Group dnode accounting */ +#define DNODE_FLAG_USEROBJUSED_ACCOUNTED (1<<3) + typedef struct dnode_phys { uint8_t dn_type; /* dmu_object_type_t */ uint8_t dn_indblkshift; /* ln2(indirect block size) */ diff --git a/include/sys/dsl_deleg.h b/include/sys/dsl_deleg.h index 59e8e055551a..d399d1da973b 100644 --- a/include/sys/dsl_deleg.h +++ b/include/sys/dsl_deleg.h @@ -51,8 +51,12 @@ extern "C" { #define ZFS_DELEG_PERM_VSCAN "vscan" #define ZFS_DELEG_PERM_USERQUOTA "userquota" #define ZFS_DELEG_PERM_GROUPQUOTA "groupquota" +#define ZFS_DELEG_PERM_USEROBJQUOTA "userobjquota" +#define ZFS_DELEG_PERM_GROUPOBJQUOTA "groupobjquota" #define ZFS_DELEG_PERM_USERUSED "userused" #define ZFS_DELEG_PERM_GROUPUSED "groupused" +#define ZFS_DELEG_PERM_USEROBJUSED "userobjused" +#define ZFS_DELEG_PERM_GROUPOBJUSED "groupobjused" #define ZFS_DELEG_PERM_HOLD "hold" #define ZFS_DELEG_PERM_RELEASE "release" #define ZFS_DELEG_PERM_DIFF "diff" diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 536fab785c6d..8d2df21f6593 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -164,6 +164,10 @@ typedef enum { ZFS_PROP_USERQUOTA, ZFS_PROP_GROUPUSED, ZFS_PROP_GROUPQUOTA, + ZFS_PROP_USEROBJUSED, + ZFS_PROP_USEROBJQUOTA, + ZFS_PROP_GROUPOBJUSED, + ZFS_PROP_GROUPOBJQUOTA, ZFS_NUM_USERQUOTA_PROPS } zfs_userquota_prop_t; @@ -885,6 +889,7 @@ typedef enum zfs_ioc { ZFS_IOC_BOOKMARK, ZFS_IOC_GET_BOOKMARKS, ZFS_IOC_DESTROY_BOOKMARKS, + ZFS_IOC_USEROBJSPACE_UPGRADE, /* * Linux - 3/64 numbers reserved. diff --git a/include/sys/zfs_vfsops.h b/include/sys/zfs_vfsops.h index efaefdaccbc1..75b5b913604a 100644 --- a/include/sys/zfs_vfsops.h +++ b/include/sys/zfs_vfsops.h @@ -110,6 +110,8 @@ typedef struct zfs_sb { kmutex_t z_lock; uint64_t z_userquota_obj; uint64_t z_groupquota_obj; + uint64_t z_userobjquota_obj; + uint64_t z_groupobjquota_obj; uint64_t z_replay_eof; /* New end of file - replay only */ sa_attr_type_t *z_attr_table; /* SA attr mapping->id */ uint64_t z_hold_size; /* znode hold array size */ diff --git a/include/zfeature_common.h b/include/zfeature_common.h index d481a28a8ffe..f70dad9f8dad 100644 --- a/include/zfeature_common.h +++ b/include/zfeature_common.h @@ -50,6 +50,7 @@ typedef enum spa_feature { SPA_FEATURE_BOOKMARKS, SPA_FEATURE_FS_SS_LIMIT, SPA_FEATURE_LARGE_BLOCKS, + SPA_FEATURE_USEROBJ_ACCOUNTING, SPA_FEATURES } spa_feature_t; diff --git a/include/zfs_deleg.h b/include/zfs_deleg.h index 16133c59f33f..95db9921f574 100644 --- a/include/zfs_deleg.h +++ b/include/zfs_deleg.h @@ -63,6 +63,10 @@ typedef enum { ZFS_DELEG_NOTE_GROUPQUOTA, ZFS_DELEG_NOTE_USERUSED, ZFS_DELEG_NOTE_GROUPUSED, + ZFS_DELEG_NOTE_USEROBJQUOTA, + ZFS_DELEG_NOTE_GROUPOBJQUOTA, + ZFS_DELEG_NOTE_USEROBJUSED, + ZFS_DELEG_NOTE_GROUPOBJUSED, ZFS_DELEG_NOTE_HOLD, ZFS_DELEG_NOTE_RELEASE, ZFS_DELEG_NOTE_DIFF, diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c index d2489c11a445..01767c58e99e 100644 --- a/lib/libzfs/libzfs_dataset.c +++ b/lib/libzfs/libzfs_dataset.c @@ -947,7 +947,9 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, } if (uqtype != ZFS_PROP_USERQUOTA && - uqtype != ZFS_PROP_GROUPQUOTA) { + uqtype != ZFS_PROP_GROUPQUOTA && + uqtype != ZFS_PROP_USEROBJQUOTA && + uqtype != ZFS_PROP_GROUPOBJQUOTA) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' is readonly"), propname); @@ -2736,8 +2738,12 @@ userquota_propname_decode(const char *propname, boolean_t zoned, return (EINVAL); *typep = type; - isuser = (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_USERUSED); - isgroup = (type == ZFS_PROP_GROUPQUOTA || type == ZFS_PROP_GROUPUSED); + isuser = (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_USERUSED || + type == ZFS_PROP_USEROBJQUOTA || + type == ZFS_PROP_USEROBJUSED); + isgroup = (type == ZFS_PROP_GROUPQUOTA || type == ZFS_PROP_GROUPUSED || + type == ZFS_PROP_GROUPOBJQUOTA || + type == ZFS_PROP_GROUPOBJUSED); cp = strchr(propname, '@') + 1; @@ -2870,7 +2876,8 @@ zfs_prop_get_userquota(zfs_handle_t *zhp, const char *propname, (void) snprintf(propbuf, proplen, "%llu", (u_longlong_t)propvalue); } else if (propvalue == 0 && - (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_GROUPQUOTA)) { + (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_GROUPQUOTA || + type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA)) { (void) strlcpy(propbuf, "none", proplen); } else { zfs_nicenum(propvalue, propbuf, proplen); diff --git a/man/man5/zpool-features.5 b/man/man5/zpool-features.5 index 6d74c9a78e59..f79b854e4b74 100644 --- a/man/man5/zpool-features.5 +++ b/man/man5/zpool-features.5 @@ -432,5 +432,26 @@ set larger than 128KB, and will return to being \fBenabled\fR once all filesystems that have ever had their recordsize larger than 128KB are destroyed. .RE +.sp +.ne 2 +.na +\fB\fBuserobj_accounting\fR\fR +.ad +.RS 4n +.TS +l l . +GUID org.zfsonlinux:user_object_accounting +READ\-ONLY COMPATIBLE yes +DEPENDENCIES none +.TE + +This feature allows administrators to account the object usage information +by user and group. + +This feature becomes \fBactive\fR as soon as it is enabled and will +never return to being \fBenabled\fR. + +.RE + .SH "SEE ALSO" \fBzpool\fR(8) diff --git a/module/zcommon/zfs_deleg.c b/module/zcommon/zfs_deleg.c index f6e41da9d7ea..647a24e5ff1f 100644 --- a/module/zcommon/zfs_deleg.c +++ b/module/zcommon/zfs_deleg.c @@ -62,6 +62,10 @@ zfs_deleg_perm_tab_t zfs_deleg_perm_tab[] = { {ZFS_DELEG_PERM_GROUPQUOTA}, {ZFS_DELEG_PERM_USERUSED}, {ZFS_DELEG_PERM_GROUPUSED}, + {ZFS_DELEG_PERM_USEROBJQUOTA}, + {ZFS_DELEG_PERM_GROUPOBJQUOTA}, + {ZFS_DELEG_PERM_USEROBJUSED}, + {ZFS_DELEG_PERM_GROUPOBJUSED}, {ZFS_DELEG_PERM_HOLD}, {ZFS_DELEG_PERM_RELEASE}, {NULL} diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c index 5a88cbe6c7b0..867ec6fb49ea 100644 --- a/module/zcommon/zfs_prop.c +++ b/module/zcommon/zfs_prop.c @@ -51,7 +51,11 @@ const char *zfs_userquota_prop_prefixes[] = { "userused@", "userquota@", "groupused@", - "groupquota@" + "groupquota@", + "userobjused@", + "userobjquota@", + "groupobjused@", + "groupobjquota@" }; zprop_desc_t * diff --git a/module/zfs/dmu_objset.c b/module/zfs/dmu_objset.c index acfc7f048479..ed5028d25574 100644 --- a/module/zfs/dmu_objset.c +++ b/module/zfs/dmu_objset.c @@ -30,6 +30,7 @@ /* Portions Copyright 2010 Robert Milkowski */ +#include #include #include #include @@ -795,6 +796,9 @@ dmu_objset_create_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, os->os_phys->os_type = type; if (dmu_objset_userused_enabled(os)) { os->os_phys->os_flags |= OBJSET_FLAG_USERACCOUNTING_COMPLETE; + if (dmu_objset_userobjused_enabled(os)) + os->os_phys->os_flags |= + OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE; os->os_flags = os->os_phys->os_flags; } @@ -1178,6 +1182,13 @@ dmu_objset_userused_enabled(objset_t *os) DMU_USERUSED_DNODE(os) != NULL); } +boolean_t +dmu_objset_userobjused_enabled(objset_t *os) +{ + return (dmu_objset_userused_enabled(os) && + spa_feature_is_enabled(os->os_spa, SPA_FEATURE_USEROBJ_ACCOUNTING)); +} + static void do_userquota_update(objset_t *os, uint64_t used, uint64_t flags, uint64_t user, uint64_t group, boolean_t subtract, dmu_tx_t *tx) @@ -1193,6 +1204,30 @@ do_userquota_update(objset_t *os, uint64_t used, uint64_t flags, } } +static void +do_userobjquota_update(objset_t *os, uint64_t flags, uint64_t user, + uint64_t group, boolean_t subtract, dmu_tx_t *tx) +{ + if (flags & DNODE_FLAG_USEROBJUSED_ACCOUNTED) { + char name[36]; + + if ((!spa_feature_is_active(os->os_spa, + SPA_FEATURE_USEROBJ_ACCOUNTING))) + spa_feature_incr(os->os_spa, + SPA_FEATURE_USEROBJ_ACCOUNTING, tx); + + (void) snprintf(name, sizeof (name), "obj-%llx", + (longlong_t)user); + VERIFY3U(0, ==, zap_increment(os, DMU_USERUSED_OBJECT, name, + subtract ? -1 : 1, tx)); + + (void) snprintf(name, sizeof (name), "obj-%llx", + (longlong_t)group); + VERIFY3U(0, ==, zap_increment(os, DMU_GROUPUSED_OBJECT, name, + subtract ? -1 : 1, tx)); + } +} + void dmu_objset_do_userquota_updates(objset_t *os, dmu_tx_t *tx) { @@ -1231,11 +1266,15 @@ dmu_objset_do_userquota_updates(objset_t *os, dmu_tx_t *tx) if (flags & DN_ID_OLD_EXIST) { do_userquota_update(os, dn->dn_oldused, dn->dn_oldflags, dn->dn_olduid, dn->dn_oldgid, B_TRUE, tx); + do_userobjquota_update(os, dn->dn_oldflags, + dn->dn_olduid, dn->dn_oldgid, B_TRUE, tx); } if (flags & DN_ID_NEW_EXIST) { do_userquota_update(os, DN_USED_BYTES(dn->dn_phys), dn->dn_phys->dn_flags, dn->dn_newuid, dn->dn_newgid, B_FALSE, tx); + do_userobjquota_update(os, dn->dn_phys->dn_flags, + dn->dn_newuid, dn->dn_newgid, B_FALSE, tx); } mutex_enter(&dn->dn_mtx); @@ -1407,19 +1446,19 @@ dmu_objset_userspace_present(objset_t *os) OBJSET_FLAG_USERACCOUNTING_COMPLETE); } -int -dmu_objset_userspace_upgrade(objset_t *os) +boolean_t +dmu_objset_userobjspace_present(objset_t *os) +{ + return (os->os_phys->os_flags & + OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE); +} + +static int +dmu_objset_space_upgrade(objset_t *os) { uint64_t obj; int err = 0; - if (dmu_objset_userspace_present(os)) - return (0); - if (!dmu_objset_userused_enabled(os)) - return (SET_ERROR(ENOTSUP)); - if (dmu_objset_is_snapshot(os)) - return (SET_ERROR(EINVAL)); - /* * We simply need to mark every object dirty, so that it will be * synced out and now accounted. If this is called @@ -1450,12 +1489,51 @@ dmu_objset_userspace_upgrade(objset_t *os) dmu_buf_rele(db, FTAG); dmu_tx_commit(tx); } + return (0); +} + +int +dmu_objset_userspace_upgrade(objset_t *os) +{ + int err = 0; + + if (dmu_objset_userspace_present(os)) + return (0); + if (!dmu_objset_userused_enabled(os)) + return (SET_ERROR(ENOTSUP)); + if (dmu_objset_is_snapshot(os)) + return (SET_ERROR(EINVAL)); + + err = dmu_objset_space_upgrade(os); + if (err) + return (err); os->os_flags |= OBJSET_FLAG_USERACCOUNTING_COMPLETE; txg_wait_synced(dmu_objset_pool(os), 0); return (0); } +int +dmu_objset_userobjspace_upgrade(objset_t *os) +{ + int err = 0; + + if (dmu_objset_userobjspace_present(os)) + return (0); + if (dmu_objset_is_snapshot(os)) + return (0); + if (!dmu_objset_userobjused_enabled(os)) + return (SET_ERROR(ENOTSUP)); + + err = dmu_objset_space_upgrade(os); + if (err) + return (err); + + os->os_flags |= OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE; + txg_wait_synced(dmu_objset_pool(os), 0); + return (0); +} + void dmu_objset_space(objset_t *os, uint64_t *refdbytesp, uint64_t *availbytesp, uint64_t *usedobjsp, uint64_t *availobjsp) @@ -2016,4 +2094,7 @@ EXPORT_SYMBOL(dmu_objset_userquota_get_ids); EXPORT_SYMBOL(dmu_objset_userused_enabled); EXPORT_SYMBOL(dmu_objset_userspace_upgrade); EXPORT_SYMBOL(dmu_objset_userspace_present); +EXPORT_SYMBOL(dmu_objset_userobjused_enabled); +EXPORT_SYMBOL(dmu_objset_userobjspace_upgrade); +EXPORT_SYMBOL(dmu_objset_userobjspace_present); #endif diff --git a/module/zfs/dnode_sync.c b/module/zfs/dnode_sync.c index b47395a1e26a..7a35c211c3de 100644 --- a/module/zfs/dnode_sync.c +++ b/module/zfs/dnode_sync.c @@ -576,12 +576,17 @@ dnode_sync(dnode_t *dn, dmu_tx_t *tx) dn->dn_oldused = DN_USED_BYTES(dn->dn_phys); dn->dn_oldflags = dn->dn_phys->dn_flags; dn->dn_phys->dn_flags |= DNODE_FLAG_USERUSED_ACCOUNTED; + if (dmu_objset_userobjused_enabled(dn->dn_objset)) + dn->dn_phys->dn_flags |= + DNODE_FLAG_USEROBJUSED_ACCOUNTED; mutex_exit(&dn->dn_mtx); dmu_objset_userquota_get_ids(dn, B_FALSE, tx); } else { /* Once we account for it, we should always account for it. */ ASSERT(!(dn->dn_phys->dn_flags & DNODE_FLAG_USERUSED_ACCOUNTED)); + ASSERT(!(dn->dn_phys->dn_flags & + DNODE_FLAG_USEROBJUSED_ACCOUNTED)); } mutex_enter(&dn->dn_mtx); diff --git a/module/zfs/zfeature_common.c b/module/zfs/zfeature_common.c index f57e5489cae9..314e38796d36 100644 --- a/module/zfs/zfeature_common.c +++ b/module/zfs/zfeature_common.c @@ -242,4 +242,8 @@ zpool_feature_init(void) "Support for blocks larger than 128KB.", ZFEATURE_FLAG_PER_DATASET, large_blocks_deps); } + zfeature_register(SPA_FEATURE_USEROBJ_ACCOUNTING, + "org.zfsonlinux:user_object_accounting", "userobj_accounting", + "User/Group object accounting.", + ZFEATURE_FLAG_READONLY_COMPAT | ZFEATURE_FLAG_PER_DATASET, NULL); } diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index 746a3f0fcb2d..ac47c61cb47b 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -234,9 +234,14 @@ static const char *userquota_perms[] = { ZFS_DELEG_PERM_USERQUOTA, ZFS_DELEG_PERM_GROUPUSED, ZFS_DELEG_PERM_GROUPQUOTA, + ZFS_DELEG_PERM_USEROBJUSED, + ZFS_DELEG_PERM_USEROBJQUOTA, + ZFS_DELEG_PERM_GROUPOBJUSED, + ZFS_DELEG_PERM_GROUPOBJQUOTA, }; static int zfs_ioc_userspace_upgrade(zfs_cmd_t *zc); +static int zfs_ioc_userobjspace_upgrade(zfs_cmd_t *zc); static int zfs_check_settable(const char *name, nvpair_t *property, cred_t *cr); static int zfs_check_clearable(char *dataset, nvlist_t *props, @@ -1154,7 +1159,9 @@ zfs_secpolicy_userspace_one(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr) * themself, allow it. */ if (zc->zc_objset_type == ZFS_PROP_USERUSED || - zc->zc_objset_type == ZFS_PROP_USERQUOTA) { + zc->zc_objset_type == ZFS_PROP_USERQUOTA || + zc->zc_objset_type == ZFS_PROP_USEROBJUSED || + zc->zc_objset_type == ZFS_PROP_USEROBJQUOTA) { if (zc->zc_guid == crgetuid(cr)) return (0); } else { @@ -3679,13 +3686,23 @@ zfs_check_settable(const char *dsname, nvpair_t *pair, cred_t *cr) zfs_userquota_prop_prefixes[ZFS_PROP_USERQUOTA]; const char *gq_prefix = zfs_userquota_prop_prefixes[ZFS_PROP_GROUPQUOTA]; + const char *uiq_prefix = + zfs_userquota_prop_prefixes[ZFS_PROP_USEROBJQUOTA]; + const char *giq_prefix = + zfs_userquota_prop_prefixes[ZFS_PROP_GROUPOBJQUOTA]; if (strncmp(propname, uq_prefix, strlen(uq_prefix)) == 0) { perm = ZFS_DELEG_PERM_USERQUOTA; + } else if (strncmp(propname, uiq_prefix, + strlen(uiq_prefix)) == 0) { + perm = ZFS_DELEG_PERM_USEROBJQUOTA; } else if (strncmp(propname, gq_prefix, strlen(gq_prefix)) == 0) { perm = ZFS_DELEG_PERM_GROUPQUOTA; + } else if (strncmp(propname, giq_prefix, + strlen(giq_prefix)) == 0) { + perm = ZFS_DELEG_PERM_GROUPOBJQUOTA; } else { /* USERUSED and GROUPUSED are read-only */ return (SET_ERROR(EINVAL)); @@ -4630,6 +4647,29 @@ zfs_ioc_userspace_upgrade(zfs_cmd_t *zc) return (error); } +/* + * inputs: + * zc_name name of filesystem + * + * outputs: + * none + */ +static int +zfs_ioc_userobjspace_upgrade(zfs_cmd_t *zc) +{ + int err; + zfs_sb_t *zsb; + + err = zfs_sb_hold(zc->zc_name, FTAG, &zsb, B_FALSE); + if (err != 0) + return (err); + + err = dmu_objset_userobjspace_upgrade(zsb->z_os); + zfs_sb_rele(zsb, FTAG); + + return (err); +} + static int zfs_ioc_share(zfs_cmd_t *zc) { @@ -5574,6 +5614,9 @@ zfs_ioctl_init(void) zfs_ioctl_register_dataset_nolog(ZFS_IOC_USERSPACE_UPGRADE, zfs_ioc_userspace_upgrade, zfs_secpolicy_userspace_upgrade, POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY); + zfs_ioctl_register_dataset_nolog(ZFS_IOC_USEROBJSPACE_UPGRADE, + zfs_ioc_userobjspace_upgrade, zfs_secpolicy_userspace_upgrade, + POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY); zfs_ioctl_register_dataset_nolog(ZFS_IOC_TMP_SNAPSHOT, zfs_ioc_tmp_snapshot, zfs_secpolicy_tmp_snapshot, POOL_CHECK_SUSPENDED | POOL_CHECK_READONLY); diff --git a/module/zfs/zfs_vfsops.c b/module/zfs/zfs_vfsops.c index 7696071f1fe7..c42085c0b4ea 100644 --- a/module/zfs/zfs_vfsops.c +++ b/module/zfs/zfs_vfsops.c @@ -431,13 +431,19 @@ zfs_userquota_prop_to_obj(zfs_sb_t *zsb, 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_USERQUOTA: return (zsb->z_userquota_obj); case ZFS_PROP_GROUPQUOTA: return (zsb->z_groupquota_obj); + case ZFS_PROP_USEROBJQUOTA: + return (zsb->z_userobjquota_obj); + case ZFS_PROP_GROUPOBJQUOTA: + return (zsb->z_groupobjquota_obj); default: return (SET_ERROR(ENOTSUP)); } @@ -453,16 +459,25 @@ zfs_userspace_many(zfs_sb_t *zsb, zfs_userquota_prop_t type, zap_attribute_t za; zfs_useracct_t *buf = vbuf; uint64_t obj; + int offset = 0; if (!dmu_objset_userspace_present(zsb->z_os)) return (SET_ERROR(ENOTSUP)); + if ((type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED || + type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA) && + !dmu_objset_userobjspace_present(zsb->z_os)) + return (SET_ERROR(ENOTSUP)); + obj = zfs_userquota_prop_to_obj(zsb, type); if (obj == 0) { *bufsizep = 0; return (0); } + if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED) + offset = strlen("obj-"); + for (zap_cursor_init_serialized(&zc, zsb->z_os, obj, *cookiep); (error = zap_cursor_retrieve(&zc, &za)) == 0; zap_cursor_advance(&zc)) { @@ -470,7 +485,14 @@ zfs_userspace_many(zfs_sb_t *zsb, zfs_userquota_prop_t type, *bufsizep) break; - fuidstr_to_sid(zsb, za.za_name, + /* + * skip object quota(with zap name "obj-") when dealing + * with block quota. + */ + if (za.za_name[0] == 'o' && offset == 0) + continue; + + fuidstr_to_sid(zsb, za.za_name + offset, buf->zu_domain, sizeof (buf->zu_domain), &buf->zu_rid); buf->zu_space = za.za_first_integer; @@ -511,7 +533,8 @@ int zfs_userspace_one(zfs_sb_t *zsb, zfs_userquota_prop_t type, const char *domain, uint64_t rid, uint64_t *valp) { - char buf[32]; + char buf[36] = "obj-"; + int offset = 0; int err; uint64_t obj; @@ -520,11 +543,19 @@ zfs_userspace_one(zfs_sb_t *zsb, zfs_userquota_prop_t type, if (!dmu_objset_userspace_present(zsb->z_os)) return (SET_ERROR(ENOTSUP)); + if ((type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED || + type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA) && + !dmu_objset_userobjspace_present(zsb->z_os)) + return (SET_ERROR(ENOTSUP)); + obj = zfs_userquota_prop_to_obj(zsb, type); if (obj == 0) return (0); - err = id_to_fuidstr(zsb, domain, rid, buf, B_FALSE); + if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED) + offset = strlen("obj-"); + + err = id_to_fuidstr(zsb, domain, rid, buf + offset, B_FALSE); if (err) return (err); @@ -545,14 +576,25 @@ zfs_set_userquota(zfs_sb_t *zsb, zfs_userquota_prop_t type, uint64_t *objp; boolean_t fuid_dirtied; - if (type != ZFS_PROP_USERQUOTA && type != ZFS_PROP_GROUPQUOTA) - return (SET_ERROR(EINVAL)); - if (zsb->z_version < ZPL_VERSION_USERSPACE) return (SET_ERROR(ENOTSUP)); - objp = (type == ZFS_PROP_USERQUOTA) ? &zsb->z_userquota_obj : - &zsb->z_groupquota_obj; + switch (type) { + case ZFS_PROP_USERQUOTA: + objp = &zsb->z_userquota_obj; + break; + case ZFS_PROP_GROUPQUOTA: + objp = &zsb->z_groupquota_obj; + break; + case ZFS_PROP_USEROBJQUOTA: + objp = &zsb->z_userobjquota_obj; + break; + case ZFS_PROP_GROUPOBJQUOTA: + objp = &zsb->z_groupobjquota_obj; + break; + default: + return (SET_ERROR(EINVAL)); + } err = id_to_fuidstr(zsb, domain, rid, buf, B_TRUE); if (err)