From f67950b21e185934ccabe311516f4dcbdb00ef79 Mon Sep 17 00:00:00 2001 From: Nasf-Fan Date: Mon, 19 Aug 2019 21:21:09 +0000 Subject: 11479 zfs project support Portions contributed by: Jerry Jelinek Portions contributed by: Jinshan Xiong Portions contributed by: Suman Chakravartula Portions contributed by: jxiong Reviewed by: Brian Behlendorf Reviewed by: Andreas Dilger Reviewed by Ned Bass Reviewed by: Matthew Ahrens Reviewed by: C Fraire Reviewed by: Kody Kantor Approved by: Richard Lowe --- usr/src/cmd/zdb/zdb.c | 20 +- usr/src/cmd/zfs/Makefile | 8 +- usr/src/cmd/zfs/zfs_main.c | 421 +++++++++++++++++++-- usr/src/cmd/zfs/zfs_project.c | 304 +++++++++++++++ usr/src/cmd/zfs/zfs_projectutil.h | 49 +++ usr/src/cmd/zhack/zhack.c | 2 +- usr/src/common/zfs/zfeature_common.c | 20 + usr/src/common/zfs/zfeature_common.h | 2 + usr/src/common/zfs/zfs_deleg.c | 8 + usr/src/common/zfs/zfs_deleg.h | 8 + usr/src/common/zfs/zfs_prop.c | 10 +- usr/src/lib/libzfs/common/libzfs_dataset.c | 159 ++++---- usr/src/man/man1m/zfs.1m | 270 ++++++++++++- usr/src/man/man5/zpool-features.5 | 65 +++- usr/src/pkg/manifests/system-test-zfstest.mf | 59 +++ usr/src/test/zfs-tests/cmd/mkfiles/mkfiles.c | 6 +- usr/src/test/zfs-tests/runfiles/delphix.run | 26 +- usr/src/test/zfs-tests/runfiles/omnios.run | 28 +- usr/src/test/zfs-tests/runfiles/openindiana.run | 28 +- usr/src/test/zfs-tests/runfiles/smartos.run | 22 ++ .../functional/cli_root/zpool_get/zpool_get.cfg | 2 + .../zfs-tests/tests/functional/privilege/setup.ksh | 4 + .../tests/functional/projectquota/Makefile | 21 + .../tests/functional/projectquota/cleanup.ksh | 37 ++ .../functional/projectquota/projectid_001_pos.ksh | 100 +++++ .../functional/projectquota/projectid_002_pos.ksh | 91 +++++ .../functional/projectquota/projectid_003_pos.ksh | 86 +++++ .../tests/functional/projectquota/projectquota.cfg | 46 +++ .../projectquota/projectquota_001_pos.ksh | 89 +++++ .../projectquota/projectquota_002_pos.ksh | 87 +++++ .../projectquota/projectquota_003_pos.ksh | 99 +++++ .../projectquota/projectquota_004_neg.ksh | 87 +++++ .../projectquota/projectquota_005_pos.ksh | 68 ++++ .../projectquota/projectquota_006_pos.ksh | 72 ++++ .../projectquota/projectquota_007_pos.ksh | 58 +++ .../projectquota/projectquota_008_pos.ksh | 91 +++++ .../projectquota/projectquota_009_pos.ksh | 131 +++++++ .../projectquota/projectquota_common.kshlib | 101 +++++ .../projectquota/projectspace_001_pos.ksh | 91 +++++ .../projectquota/projectspace_002_pos.ksh | 83 ++++ .../projectquota/projectspace_003_pos.ksh | 119 ++++++ .../projectquota/projectspace_004_pos.ksh | 76 ++++ .../projectquota/projecttree_001_pos.ksh | 108 ++++++ .../projectquota/projecttree_002_pos.ksh | 120 ++++++ .../projectquota/projecttree_003_neg.ksh | 103 +++++ .../tests/functional/projectquota/setup.ksh | 56 +++ .../zfs-tests/tests/functional/upgrade/Makefile | 21 + .../zfs-tests/tests/functional/upgrade/cleanup.ksh | 42 ++ .../zfs-tests/tests/functional/upgrade/setup.ksh | 43 +++ .../tests/functional/upgrade/upgrade_common.kshlib | 41 ++ .../upgrade/upgrade_projectquota_001_pos.ksh | 125 ++++++ .../functional/upgrade/upgrade_userobj_001_pos.ksh | 95 +++++ .../functional/userquota/groupspace_003_pos.ksh | 104 +++++ .../functional/userquota/userquota_001_pos.ksh | 4 +- .../functional/userquota/userquota_004_pos.ksh | 15 +- .../functional/userquota/userquota_010_pos.ksh | 2 +- .../functional/userquota/userquota_013_pos.ksh | 79 ++++ .../functional/userquota/userquota_common.kshlib | 6 +- .../functional/userquota/userspace_002_pos.ksh | 3 +- .../functional/userquota/userspace_003_pos.ksh | 117 ++++++ usr/src/uts/common/fs/zfs/dbuf.c | 2 +- usr/src/uts/common/fs/zfs/dmu.c | 4 +- usr/src/uts/common/fs/zfs/dmu_objset.c | 413 +++++++++++++++++--- usr/src/uts/common/fs/zfs/dmu_traverse.c | 26 +- usr/src/uts/common/fs/zfs/dnode.c | 22 +- usr/src/uts/common/fs/zfs/dnode_sync.c | 5 + usr/src/uts/common/fs/zfs/dsl_pool.c | 2 +- usr/src/uts/common/fs/zfs/dsl_scan.c | 6 +- usr/src/uts/common/fs/zfs/sa.c | 189 ++++++++- usr/src/uts/common/fs/zfs/spa.c | 12 + usr/src/uts/common/fs/zfs/sys/dmu.h | 9 +- usr/src/uts/common/fs/zfs/sys/dmu_objset.h | 39 +- usr/src/uts/common/fs/zfs/sys/dnode.h | 13 +- usr/src/uts/common/fs/zfs/sys/dsl_deleg.h | 8 + usr/src/uts/common/fs/zfs/sys/sa.h | 1 + usr/src/uts/common/fs/zfs/sys/spa_impl.h | 2 + usr/src/uts/common/fs/zfs/sys/zfs_acl.h | 3 +- usr/src/uts/common/fs/zfs/sys/zfs_context.h | 1 + usr/src/uts/common/fs/zfs/sys/zfs_project.h | 78 ++++ usr/src/uts/common/fs/zfs/sys/zfs_sa.h | 3 + usr/src/uts/common/fs/zfs/sys/zfs_vfsops.h | 14 +- usr/src/uts/common/fs/zfs/sys/zfs_znode.h | 23 +- usr/src/uts/common/fs/zfs/zfs_acl.c | 12 +- usr/src/uts/common/fs/zfs/zfs_dir.c | 2 +- usr/src/uts/common/fs/zfs/zfs_ioctl.c | 87 ++++- usr/src/uts/common/fs/zfs/zfs_log.c | 14 +- usr/src/uts/common/fs/zfs/zfs_replay.c | 13 +- usr/src/uts/common/fs/zfs/zfs_sa.c | 13 +- usr/src/uts/common/fs/zfs/zfs_vfsops.c | 417 ++++++++++++++++---- usr/src/uts/common/fs/zfs/zfs_vnops.c | 375 ++++++++++++++++-- usr/src/uts/common/fs/zfs/zfs_znode.c | 57 ++- usr/src/uts/common/fs/zfs/zio_crypt.c | 13 +- usr/src/uts/common/sys/fs/zfs.h | 8 + usr/src/uts/common/sys/vnode.h | 9 +- 94 files changed, 5775 insertions(+), 358 deletions(-) create mode 100644 usr/src/cmd/zfs/zfs_project.c create mode 100644 usr/src/cmd/zfs/zfs_projectutil.h create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/Makefile create mode 100755 usr/src/test/zfs-tests/tests/functional/projectquota/cleanup.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectid_001_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectid_002_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectid_003_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectquota.cfg create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_001_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_002_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_003_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_004_neg.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_005_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_006_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_007_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_008_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_009_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_001_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_002_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_003_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_004_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_001_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_002_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_003_neg.ksh create mode 100755 usr/src/test/zfs-tests/tests/functional/projectquota/setup.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/upgrade/Makefile create mode 100644 usr/src/test/zfs-tests/tests/functional/upgrade/cleanup.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/upgrade/setup.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_common.kshlib create mode 100644 usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_projectquota_001_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_userobj_001_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/userquota/groupspace_003_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/userquota/userquota_013_pos.ksh create mode 100644 usr/src/test/zfs-tests/tests/functional/userquota/userspace_003_pos.ksh create mode 100644 usr/src/uts/common/fs/zfs/sys/zfs_project.h diff --git a/usr/src/cmd/zdb/zdb.c b/usr/src/cmd/zdb/zdb.c index 26ed6dacf8..5022ca3035 100644 --- a/usr/src/cmd/zdb/zdb.c +++ b/usr/src/cmd/zdb/zdb.c @@ -2041,6 +2041,13 @@ dump_znode(objset_t *os, uint64_t object, void *data, size_t size) (void) printf("\tparent %llu\n", (u_longlong_t)parent); (void) printf("\tlinks %llu\n", (u_longlong_t)links); (void) printf("\tpflags %llx\n", (u_longlong_t)pflags); + if (dmu_objset_projectquota_enabled(os) && (pflags & ZFS_PROJID)) { + uint64_t projid; + + if (sa_lookup(hdl, sa_attr_table[ZPL_PROJID], &projid, + sizeof (uint64_t)) == 0) + (void) printf("\tprojid %llu\n", (u_longlong_t)projid); + } if (sa_lookup(hdl, sa_attr_table[ZPL_XATTR], &xattr, sizeof (uint64_t)) == 0) (void) printf("\txattr %llu\n", (u_longlong_t)xattr); @@ -2103,8 +2110,8 @@ static object_viewer_t *object_viewer[DMU_OT_NUMTYPES + 1] = { dump_packed_nvlist, /* FUID nvlist size */ dump_zap, /* DSL dataset next clones */ dump_zap, /* DSL scrub queue */ - dump_zap, /* ZFS user/group used */ - dump_zap, /* ZFS user/group quota */ + dump_zap, /* ZFS user/group/project used */ + dump_zap, /* ZFS user/group/project quota */ dump_zap, /* snapshot refcount tags */ dump_ddt_zap, /* DDT ZAP object */ dump_zap, /* DDT statistics */ @@ -2217,11 +2224,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", @@ -2411,6 +2420,11 @@ dump_dir(objset_t *os) NULL); } + if (DMU_PROJECTUSED_DNODE(os) != NULL && + DMU_PROJECTUSED_DNODE(os)->dn_type != 0) + dump_object(os, DMU_PROJECTUSED_OBJECT, verbosity, + &print_header, NULL); + object = 0; while ((error = dmu_object_next(os, &object, B_FALSE, 0)) == 0) { dump_object(os, object, verbosity, &print_header, &dnode_slots); diff --git a/usr/src/cmd/zfs/Makefile b/usr/src/cmd/zfs/Makefile index a3d61c7ee4..a65371609c 100644 --- a/usr/src/cmd/zfs/Makefile +++ b/usr/src/cmd/zfs/Makefile @@ -24,12 +24,13 @@ # Copyright 2010 Nexenta Systems, Inc. All rights reserved. # Copyright (c) 2012, 2015 by Delphix. All rights reserved. # Copyright 2016 Igor Kozhukhov . +# Copyright 2019 Joyent, Inc. # PROG= zfs -OBJS= zfs_main.o zfs_iter.o +OBJS= zfs_main.o zfs_iter.o zfs_project.o SRCS= $(OBJS:%.o=%.c) -POFILES= zfs_main.po zfs_iter.po +POFILES= zfs_main.po zfs_iter.po zfs_project.po POFILE= zfs.po include ../Makefile.cmd @@ -41,8 +42,11 @@ ROOTETCFSTYPE= $(ROOTETC)/fs/$(FSTYPE) USRLIBFSTYPE= $(ROOTLIB)/fs/$(FSTYPE) LDLIBS += -lzfs_core -lzfs -luutil -lumem -lnvpair -lsec -lidmap +# cmdutils has list(9F) functions used by the project code. +LDLIBS += -lcmdutils INCS += -I../../common/zfs +INCS += -I$(SRC)/uts/common/fs/zfs CSTD= $(CSTD_GNU99) C99LMODE= -Xc99=%all diff --git a/usr/src/cmd/zfs/zfs_main.c b/usr/src/cmd/zfs/zfs_main.c index c9424302bc..cfed8bc4f8 100644 --- a/usr/src/cmd/zfs/zfs_main.c +++ b/usr/src/cmd/zfs/zfs_main.c @@ -61,6 +61,7 @@ #include #include #include +#include #include #include @@ -76,6 +77,7 @@ #include "zfs_iter.h" #include "zfs_util.h" #include "zfs_comutil.h" +#include "zfs_projectutil.h" libzfs_handle_t *g_zfs; @@ -114,6 +116,7 @@ static int zfs_do_channel_program(int argc, char **argv); static int zfs_do_load_key(int argc, char **argv); static int zfs_do_unload_key(int argc, char **argv); static int zfs_do_change_key(int argc, char **argv); +static int zfs_do_project(int argc, char **argv); /* * Enable a reasonable set of defaults for libumem debugging on DEBUG builds. @@ -156,6 +159,8 @@ typedef enum { HELP_UNALLOW, HELP_USERSPACE, HELP_GROUPSPACE, + HELP_PROJECTSPACE, + HELP_PROJECT, HELP_HOLD, HELP_HOLDS, HELP_RELEASE, @@ -201,8 +206,12 @@ static zfs_command_t command_table[] = { { "get", zfs_do_get, HELP_GET }, { "inherit", zfs_do_inherit, HELP_INHERIT }, { "upgrade", zfs_do_upgrade, HELP_UPGRADE }, + { NULL }, { "userspace", zfs_do_userspace, HELP_USERSPACE }, { "groupspace", zfs_do_userspace, HELP_GROUPSPACE }, + { "projectspace", zfs_do_userspace, HELP_PROJECTSPACE }, + { NULL }, + { "project", zfs_do_project, HELP_PROJECT }, { NULL }, { "mount", zfs_do_mount, HELP_MOUNT }, { "unmount", zfs_do_unmount, HELP_UNMOUNT }, @@ -333,6 +342,15 @@ get_usage(zfs_help_t idx) "[-s field] ...\n" "\t [-S field] ... [-t type[,...]] " "\n")); + case HELP_PROJECTSPACE: + return (gettext("\tprojectspace [-Hp] [-o field[,...]] " + "[-s field] ... \n" + "\t [-S field] ... \n")); + case HELP_PROJECT: + return (gettext("\tproject [-d|-r] \n" + "\tproject -c [-0] [-d|-r] [-p id] \n" + "\tproject -C [-k] [-r] \n" + "\tproject [-p id] [-r] [-s] \n")); case HELP_HOLD: return (gettext("\thold [-r] ...\n")); case HELP_HOLDS: @@ -496,10 +514,26 @@ usage(boolean_t requested) (void) fprintf(fp, " NO NO \n"); (void) fprintf(fp, "\t%-15s ", "groupused@..."); (void) fprintf(fp, " NO NO \n"); + (void) fprintf(fp, "\t%-15s ", "projectused@..."); + (void) fprintf(fp, " NO NO \n"); + (void) fprintf(fp, "\t%-15s ", "userobjused@..."); + (void) fprintf(fp, " NO NO \n"); + (void) fprintf(fp, "\t%-15s ", "groupobjused@..."); + (void) fprintf(fp, " NO NO \n"); + (void) fprintf(fp, "\t%-15s ", "projectobjused@..."); + (void) fprintf(fp, " NO NO \n"); (void) fprintf(fp, "\t%-15s ", "userquota@..."); (void) fprintf(fp, "YES NO | none\n"); (void) fprintf(fp, "\t%-15s ", "groupquota@..."); (void) fprintf(fp, "YES NO | none\n"); + (void) fprintf(fp, "\t%-15s ", "projectquota@..."); + (void) fprintf(fp, "YES NO | none\n"); + (void) fprintf(fp, "\t%-15s ", "userobjquota@..."); + (void) fprintf(fp, "YES NO | none\n"); + (void) fprintf(fp, "\t%-15s ", "groupobjquota@..."); + (void) fprintf(fp, "YES NO | none\n"); + (void) fprintf(fp, "\t%-15s ", "projectobjquota@..."); + (void) fprintf(fp, "YES NO | none\n"); (void) fprintf(fp, "\t%-15s ", "written@"); (void) fprintf(fp, " NO NO \n"); @@ -507,9 +541,9 @@ usage(boolean_t requested) "with standard units such as K, M, G, etc.\n")); (void) fprintf(fp, gettext("\nUser-defined properties can " "be specified by using a name containing a colon (:).\n")); - (void) fprintf(fp, gettext("\nThe {user|group}{used|quota}@ " - "properties must be appended with\n" - "a user or group specifier of one of these forms:\n" + (void) fprintf(fp, gettext("\nThe {user|group|project}" + "[obj]{used|quota}@ properties must be appended with\n" + "a user|group|project specifier of one of these forms:\n" " POSIX name (eg: \"matt\")\n" " POSIX id (eg: \"126829\")\n" " SMB name@domain (eg: \"matt@sun\")\n" @@ -2267,6 +2301,8 @@ zfs_do_upgrade(int argc, char **argv) * [-S field [-S field]...] [-t type[,...]] filesystem | snapshot * zfs groupspace [-Hinp] [-o field[,...]] [-s field [-s field]...] * [-S field [-S field]...] [-t type[,...]] filesystem | snapshot + * zfs projectspace [-Hp] [-o field[,...]] [-s field [-s field]...] + * [-S field [-S field]...] filesystem | snapshot * * -H Scripted mode; elide headers and separate columns by tabs. * -i Translate SID to POSIX ID. @@ -2286,18 +2322,24 @@ enum us_field_types { USFIELD_TYPE, USFIELD_NAME, USFIELD_USED, - USFIELD_QUOTA + USFIELD_QUOTA, + USFIELD_OBJUSED, + USFIELD_OBJQUOTA }; -static char *us_field_hdr[] = { "TYPE", "NAME", "USED", "QUOTA" }; -static char *us_field_names[] = { "type", "name", "used", "quota" }; +static char *us_field_hdr[] = { "TYPE", "NAME", "USED", "QUOTA", + "OBJUSED", "OBJQUOTA" }; +static char *us_field_names[] = { "type", "name", "used", "quota", + "objused", "objquota" }; #define USFIELD_LAST (sizeof (us_field_names) / sizeof (char *)) #define USTYPE_PSX_GRP (1 << 0) #define USTYPE_PSX_USR (1 << 1) #define USTYPE_SMB_GRP (1 << 2) #define USTYPE_SMB_USR (1 << 3) +#define USTYPE_PROJ (1 << 4) #define USTYPE_ALL \ - (USTYPE_PSX_GRP | USTYPE_PSX_USR | USTYPE_SMB_GRP | USTYPE_SMB_USR) + (USTYPE_PSX_GRP | USTYPE_PSX_USR | USTYPE_SMB_GRP | USTYPE_SMB_USR | \ + USTYPE_PROJ) static int us_type_bits[] = { USTYPE_PSX_GRP, @@ -2435,6 +2477,27 @@ us_compare(const void *larg, const void *rarg, void *unused) return (0); } +static boolean_t +zfs_prop_is_user(unsigned p) +{ + return (p == ZFS_PROP_USERUSED || p == ZFS_PROP_USERQUOTA || + p == ZFS_PROP_USEROBJUSED || p == ZFS_PROP_USEROBJQUOTA); +} + +static boolean_t +zfs_prop_is_group(unsigned p) +{ + return (p == ZFS_PROP_GROUPUSED || p == ZFS_PROP_GROUPQUOTA || + p == ZFS_PROP_GROUPOBJUSED || p == ZFS_PROP_GROUPOBJQUOTA); +} + +static boolean_t +zfs_prop_is_project(unsigned p) +{ + return (p == ZFS_PROP_PROJECTUSED || p == ZFS_PROP_PROJECTQUOTA || + p == ZFS_PROP_PROJECTOBJUSED || p == ZFS_PROP_PROJECTOBJQUOTA); +} + static inline const char * us_type2str(unsigned field_type) { @@ -2447,6 +2510,8 @@ us_type2str(unsigned field_type) return ("SMB User"); case USTYPE_SMB_GRP: return ("SMB Group"); + case USTYPE_PROJ: + return ("Project"); default: return ("Undefined"); } @@ -2519,7 +2584,7 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) if (cb->cb_sid2posix || domain == NULL || domain[0] == '\0') { /* POSIX or -i */ - if (prop == ZFS_PROP_GROUPUSED || prop == ZFS_PROP_GROUPQUOTA) { + if (zfs_prop_is_group(prop)) { type = USTYPE_PSX_GRP; if (!cb->cb_numname) { struct group *g; @@ -2527,7 +2592,7 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) if ((g = getgrgid(rid)) != NULL) name = g->gr_name; } - } else { + } else if (zfs_prop_is_user(prop)) { type = USTYPE_PSX_USR; if (!cb->cb_numname) { struct passwd *p; @@ -2535,6 +2600,8 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) if ((p = getpwuid(rid)) != NULL) name = p->pw_name; } + } else { + type = USTYPE_PROJ; } } @@ -2584,19 +2651,42 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) } /* Calculate/update width of USED/QUOTA fields */ - if (cb->cb_nicenum) - zfs_nicenum(space, sizebuf, sizeof (sizebuf)); - else + if (cb->cb_nicenum) { + if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED || + prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA || + prop == ZFS_PROP_PROJECTUSED || + prop == ZFS_PROP_PROJECTQUOTA) { + zfs_nicenum(space, sizebuf, sizeof (sizebuf)); + } else { + zfs_nicenum(space, sizebuf, sizeof (sizebuf)); + } + } else { (void) snprintf(sizebuf, sizeof (sizebuf), "%llu", space); + } sizelen = strlen(sizebuf); - if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED) { + if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED || + prop == ZFS_PROP_PROJECTUSED) { propname = "used"; if (!nvlist_exists(props, "quota")) (void) nvlist_add_uint64(props, "quota", 0); - } else { + } else if (prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA || + prop == ZFS_PROP_PROJECTQUOTA) { propname = "quota"; if (!nvlist_exists(props, "used")) (void) nvlist_add_uint64(props, "used", 0); + } else if (prop == ZFS_PROP_USEROBJUSED || + prop == ZFS_PROP_GROUPOBJUSED || prop == ZFS_PROP_PROJECTOBJUSED) { + propname = "objused"; + if (!nvlist_exists(props, "objquota")) + (void) nvlist_add_uint64(props, "objquota", 0); + } else if (prop == ZFS_PROP_USEROBJQUOTA || + prop == ZFS_PROP_GROUPOBJQUOTA || + prop == ZFS_PROP_PROJECTOBJQUOTA) { + propname = "objquota"; + if (!nvlist_exists(props, "objused")) + (void) nvlist_add_uint64(props, "objused", 0); + } else { + return (-1); } sizeidx = us_field_index(propname); if (sizelen > cb->cb_width[sizeidx]) @@ -2629,7 +2719,7 @@ print_us_node(boolean_t scripted, boolean_t parsable, int *fields, int types, data_type_t type; uint32_t val32; uint64_t val64; - char *strval = NULL; + char *strval = "-"; while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) { if (strcmp(nvpair_name(nvp), @@ -2637,7 +2727,7 @@ print_us_node(boolean_t scripted, boolean_t parsable, int *fields, int types, break; } - type = nvpair_type(nvp); + type = nvp == NULL ? DATA_TYPE_UNKNOWN : nvpair_type(nvp); switch (type) { case DATA_TYPE_UINT32: (void) nvpair_value_uint32(nvp, &val32); @@ -2648,13 +2738,16 @@ print_us_node(boolean_t scripted, boolean_t parsable, int *fields, int types, case DATA_TYPE_STRING: (void) nvpair_value_string(nvp, &strval); break; + case DATA_TYPE_UNKNOWN: + break; default: (void) fprintf(stderr, "invalid data type\n"); } switch (field) { case USFIELD_TYPE: - strval = (char *)us_type2str(val32); + if (type == DATA_TYPE_UINT32) + strval = (char *)us_type2str(val32); break; case USFIELD_NAME: if (type == DATA_TYPE_UINT64) { @@ -2664,6 +2757,8 @@ print_us_node(boolean_t scripted, boolean_t parsable, int *fields, int types, break; case USFIELD_USED: case USFIELD_QUOTA: + case USFIELD_OBJUSED: + case USFIELD_OBJQUOTA: if (type == DATA_TYPE_UINT64) { if (parsable) { (void) sprintf(valstr, "%llu", val64); @@ -2671,7 +2766,8 @@ print_us_node(boolean_t scripted, boolean_t parsable, int *fields, int types, zfs_nicenum(val64, valstr, sizeof (valstr)); } - if (field == USFIELD_QUOTA && + if ((field == USFIELD_QUOTA || + field == USFIELD_OBJQUOTA) && strcmp(valstr, "0") == 0) strval = "none"; else @@ -2743,7 +2839,7 @@ zfs_do_userspace(int argc, char **argv) uu_avl_t *avl_tree; uu_avl_walk_t *walk; char *delim; - char deffields[] = "type,name,used,quota"; + char deffields[] = "type,name,used,quota,objused,objquota"; char *ofield = NULL; char *tfield = NULL; int cfield = 0; @@ -2768,13 +2864,22 @@ zfs_do_userspace(int argc, char **argv) if (argc < 2) usage(B_FALSE); - if (strcmp(argv[0], "groupspace") == 0) + if (strcmp(argv[0], "groupspace") == 0) { /* Toggle default group types */ types = USTYPE_PSX_GRP | USTYPE_SMB_GRP; + } else if (strcmp(argv[0], "projectspace") == 0) { + types = USTYPE_PROJ; + prtnum = B_TRUE; + } while ((c = getopt(argc, argv, "nHpo:s:S:t:i")) != -1) { switch (c) { case 'n': + if (types == USTYPE_PROJ) { + (void) fprintf(stderr, + gettext("invalid option 'n'\n")); + usage(B_FALSE); + } prtnum = B_TRUE; break; case 'H': @@ -2796,9 +2901,19 @@ zfs_do_userspace(int argc, char **argv) } break; case 't': + if (types == USTYPE_PROJ) { + (void) fprintf(stderr, + gettext("invalid option 't'\n")); + usage(B_FALSE); + } tfield = optarg; break; case 'i': + if (types == USTYPE_PROJ) { + (void) fprintf(stderr, + gettext("invalid option 'i'\n")); + usage(B_FALSE); + } sid2posix = B_TRUE; break; case ':': @@ -2892,11 +3007,13 @@ 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 ((zfs_prop_is_user(p) && !(types & (USTYPE_PSX_USR | USTYPE_SMB_USR))) || - ((p == ZFS_PROP_GROUPUSED || p == ZFS_PROP_GROUPQUOTA) && - !(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP)))) + (zfs_prop_is_group(p) && + !(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP))) || + (zfs_prop_is_project(p) && types != USTYPE_PROJ)) continue; + cb.cb_prop = p; if ((ret = zfs_userspace(zhp, p, userspace_cb, &cb)) != 0) return (ret); @@ -4205,6 +4322,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" @@ -4213,6 +4335,11 @@ zfs_do_receive(int argc, char **argv) #define ZFS_DELEG_PERM_LOAD_KEY "load-key" #define ZFS_DELEG_PERM_CHANGE_KEY "change-key" +#define ZFS_DELEG_PERM_PROJECTUSED "projectused" +#define ZFS_DELEG_PERM_PROJECTQUOTA "projectquota" +#define ZFS_DELEG_PERM_PROJECTOBJUSED "projectobjused" +#define ZFS_DELEG_PERM_PROJECTOBJQUOTA "projectobjquota" + #define ZFS_NUM_DELEG_NOTES ZFS_DELEG_NOTE_NONE static zfs_deleg_perm_tab_t zfs_deleg_perm_tbl[] = { @@ -4241,6 +4368,14 @@ 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 }, + { ZFS_DELEG_PERM_PROJECTUSED, ZFS_DELEG_NOTE_PROJECTUSED }, + { ZFS_DELEG_PERM_PROJECTQUOTA, ZFS_DELEG_NOTE_PROJECTQUOTA }, + { ZFS_DELEG_PERM_PROJECTOBJUSED, ZFS_DELEG_NOTE_PROJECTOBJUSED }, + { ZFS_DELEG_PERM_PROJECTOBJQUOTA, ZFS_DELEG_NOTE_PROJECTOBJQUOTA }, { NULL, ZFS_DELEG_NOTE_NONE } }; @@ -4318,6 +4453,14 @@ 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: + case ZFS_DELEG_NOTE_PROJECTUSED: + case ZFS_DELEG_NOTE_PROJECTQUOTA: + case ZFS_DELEG_NOTE_PROJECTOBJUSED: + case ZFS_DELEG_NOTE_PROJECTOBJQUOTA: /* other */ return (gettext("other")); default: @@ -4829,6 +4972,33 @@ 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; + case ZFS_DELEG_NOTE_PROJECTQUOTA: + str = gettext("Allows accessing any projectquota@... property"); + break; + case ZFS_DELEG_NOTE_PROJECTOBJQUOTA: + str = gettext("Allows accessing any \n\t\t\t\t" + "projectobjquota@... property"); + break; + case ZFS_DELEG_NOTE_PROJECTUSED: + str = gettext("Allows reading any projectused@... property"); + break; + case ZFS_DELEG_NOTE_PROJECTOBJUSED: + str = gettext("Allows accessing any \n\t\t\t\t" + "projectobjused@... property"); + break; /* other */ default: str = ""; @@ -7611,6 +7781,211 @@ zfs_do_change_key(int argc, char **argv) return (0); } +/* + * 1) zfs project [-d|-r] + * List project ID and inherit flag of file(s) or directories. + * -d: List the directory itself, not its children. + * -r: List subdirectories recursively. + * + * 2) zfs project -C [-k] [-r] + * Clear project inherit flag and/or ID on the file(s) or directories. + * -k: Keep the project ID unchanged. If not specified, the project ID + * will be reset as zero. + * -r: Clear on subdirectories recursively. + * + * 3) zfs project -c [-0] [-d|-r] [-p id] + * Check project ID and inherit flag on the file(s) or directories, + * report the outliers. + * -0: Print file name followed by a NUL instead of newline. + * -d: Check the directory itself, not its children. + * -p: Specify the referenced ID for comparing with the target file(s) + * or directories' project IDs. If not specified, the target (top) + * directory's project ID will be used as the referenced one. + * -r: Check subdirectories recursively. + * + * 4) zfs project [-p id] [-r] [-s] + * Set project ID and/or inherit flag on the file(s) or directories. + * -p: Set the project ID as the given id. + * -r: Set on subdirectory recursively. If not specify "-p" option, + * it will use top-level directory's project ID as the given id, + * then set both project ID and inherit flag on all descendants + * of the top-level directory. + * -s: Set project inherit flag. + */ +static int +zfs_do_project(int argc, char **argv) +{ + zfs_project_control_t zpc = { + .zpc_expected_projid = ZFS_INVALID_PROJID, + .zpc_op = ZFS_PROJECT_OP_DEFAULT, + .zpc_dironly = B_FALSE, + .zpc_keep_projid = B_FALSE, + .zpc_newline = B_TRUE, + .zpc_recursive = B_FALSE, + .zpc_set_flag = B_FALSE, + }; + int ret = 0, c; + + if (argc < 2) + usage(B_FALSE); + + while ((c = getopt(argc, argv, "0Ccdkp:rs")) != -1) { + switch (c) { + case '0': + zpc.zpc_newline = B_FALSE; + break; + case 'C': + if (zpc.zpc_op != ZFS_PROJECT_OP_DEFAULT) { + (void) fprintf(stderr, gettext("cannot " + "specify '-C' '-c' '-s' together\n")); + usage(B_FALSE); + } + + zpc.zpc_op = ZFS_PROJECT_OP_CLEAR; + break; + case 'c': + if (zpc.zpc_op != ZFS_PROJECT_OP_DEFAULT) { + (void) fprintf(stderr, gettext("cannot " + "specify '-C' '-c' '-s' together\n")); + usage(B_FALSE); + } + + zpc.zpc_op = ZFS_PROJECT_OP_CHECK; + break; + case 'd': + zpc.zpc_dironly = B_TRUE; + /* overwrite "-r" option */ + zpc.zpc_recursive = B_FALSE; + break; + case 'k': + zpc.zpc_keep_projid = B_TRUE; + break; + case 'p': { + char *endptr; + + errno = 0; + zpc.zpc_expected_projid = strtoull(optarg, &endptr, 0); + if (errno != 0 || *endptr != '\0') { + (void) fprintf(stderr, + gettext("project ID must be less than " + "%u\n"), UINT32_MAX); + usage(B_FALSE); + } + if (zpc.zpc_expected_projid >= UINT32_MAX) { + (void) fprintf(stderr, + gettext("invalid project ID\n")); + usage(B_FALSE); + } + break; + } + case 'r': + zpc.zpc_recursive = B_TRUE; + /* overwrite "-d" option */ + zpc.zpc_dironly = B_FALSE; + break; + case 's': + if (zpc.zpc_op != ZFS_PROJECT_OP_DEFAULT) { + (void) fprintf(stderr, gettext("cannot " + "specify '-C' '-c' '-s' together\n")); + usage(B_FALSE); + } + + zpc.zpc_set_flag = B_TRUE; + zpc.zpc_op = ZFS_PROJECT_OP_SET; + break; + default: + (void) fprintf(stderr, gettext("invalid option '%c'\n"), + optopt); + usage(B_FALSE); + } + } + + if (zpc.zpc_op == ZFS_PROJECT_OP_DEFAULT) { + if (zpc.zpc_expected_projid != ZFS_INVALID_PROJID) + zpc.zpc_op = ZFS_PROJECT_OP_SET; + else + zpc.zpc_op = ZFS_PROJECT_OP_LIST; + } + + switch (zpc.zpc_op) { + case ZFS_PROJECT_OP_LIST: + if (zpc.zpc_keep_projid) { + (void) fprintf(stderr, + gettext("'-k' is only valid together with '-C'\n")); + usage(B_FALSE); + } + if (!zpc.zpc_newline) { + (void) fprintf(stderr, + gettext("'-0' is only valid together with '-c'\n")); + usage(B_FALSE); + } + break; + case ZFS_PROJECT_OP_CHECK: + if (zpc.zpc_keep_projid) { + (void) fprintf(stderr, + gettext("'-k' is only valid together with '-C'\n")); + usage(B_FALSE); + } + break; + case ZFS_PROJECT_OP_CLEAR: + if (zpc.zpc_dironly) { + (void) fprintf(stderr, + gettext("'-d' is useless together with '-C'\n")); + usage(B_FALSE); + } + if (!zpc.zpc_newline) { + (void) fprintf(stderr, + gettext("'-0' is only valid together with '-c'\n")); + usage(B_FALSE); + } + if (zpc.zpc_expected_projid != ZFS_INVALID_PROJID) { + (void) fprintf(stderr, + gettext("'-p' is useless together with '-C'\n")); + usage(B_FALSE); + } + break; + case ZFS_PROJECT_OP_SET: + if (zpc.zpc_dironly) { + (void) fprintf(stderr, + gettext("'-d' is useless for set project ID and/or " + "inherit flag\n")); + usage(B_FALSE); + } + if (zpc.zpc_keep_projid) { + (void) fprintf(stderr, + gettext("'-k' is only valid together with '-C'\n")); + usage(B_FALSE); + } + if (!zpc.zpc_newline) { + (void) fprintf(stderr, + gettext("'-0' is only valid together with '-c'\n")); + usage(B_FALSE); + } + break; + default: + ASSERT(0); + break; + } + + argv += optind; + argc -= optind; + if (argc == 0) { + (void) fprintf(stderr, + gettext("missing file or directory target(s)\n")); + usage(B_FALSE); + } + + for (int i = 0; i < argc; i++) { + int err; + + err = zfs_project_handle(argv[i], &zpc); + if (err && !ret) + ret = err; + } + + return (ret); +} + int main(int argc, char **argv) { diff --git a/usr/src/cmd/zfs/zfs_project.c b/usr/src/cmd/zfs/zfs_project.c new file mode 100644 index 0000000000..7b7652f786 --- /dev/null +++ b/usr/src/cmd/zfs/zfs_project.c @@ -0,0 +1,304 @@ +/* + * 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) 2017, Intle Corporation. All rights reserved. + * Copyright 2019 Joyent, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zfs_util.h" +#include "zfs_projectutil.h" + +typedef struct zfs_project_item { + list_node_t zpi_list; + char zpi_name[0]; +} zfs_project_item_t; + +static void +zfs_project_item_alloc(list_t *head, const char *name) +{ + zfs_project_item_t *zpi; + + zpi = safe_malloc(sizeof (zfs_project_item_t) + strlen(name) + 1); + (void) strcpy(zpi->zpi_name, name); + list_insert_tail(head, zpi); +} + +static int +zfs_project_sanity_check(const char *name, zfs_project_control_t *zpc, + struct stat *st) +{ + int ret; + + ret = stat(name, st); + if (ret) { + (void) fprintf(stderr, gettext("failed to stat %s: %s\n"), + name, strerror(errno)); + return (ret); + } + + if (!S_ISREG(st->st_mode) && !S_ISDIR(st->st_mode)) { + (void) fprintf(stderr, gettext("only support project quota on " + "regular file or directory\n")); + return (-1); + } + + if (!S_ISDIR(st->st_mode)) { + if (zpc->zpc_dironly) { + (void) fprintf(stderr, gettext( + "'-d' option on non-dir target %s\n"), name); + return (-1); + } + + if (zpc->zpc_recursive) { + (void) fprintf(stderr, gettext( + "'-r' option on non-dir target %s\n"), name); + return (-1); + } + } + + return (0); +} + +static int +zfs_project_load_projid(const char *name, zfs_project_control_t *zpc) +{ + zfsxattr_t fsx; + int ret, fd; + + fd = open(name, O_RDONLY | O_NOCTTY); + if (fd < 0) { + (void) fprintf(stderr, gettext("failed to open %s: %s\n"), + name, strerror(errno)); + return (fd); + } + + ret = ioctl(fd, ZFS_IOC_FSGETXATTR, &fsx); + if (ret) + (void) fprintf(stderr, + gettext("failed to get xattr for %s: %s\n"), + name, strerror(errno)); + else + zpc->zpc_expected_projid = fsx.fsx_projid; + + (void) close(fd); + return (ret); +} + +static int +zfs_project_handle_one(const char *name, zfs_project_control_t *zpc) +{ + zfsxattr_t fsx; + int ret, fd; + + fd = open(name, O_RDONLY | O_NOCTTY); + if (fd < 0) { + if (errno == ENOENT && zpc->zpc_ignore_noent) + return (0); + + (void) fprintf(stderr, gettext("failed to open %s: %s\n"), + name, strerror(errno)); + return (fd); + } + + ret = ioctl(fd, ZFS_IOC_FSGETXATTR, &fsx); + if (ret) { + (void) fprintf(stderr, + gettext("failed to get xattr for %s: %s\n"), + name, strerror(errno)); + goto out; + } + + switch (zpc->zpc_op) { + case ZFS_PROJECT_OP_LIST: + (void) printf("%5u %c %s\n", fsx.fsx_projid, + (fsx.fsx_xflags & ZFS_PROJINHERIT_FL) ? 'P' : '-', name); + goto out; + case ZFS_PROJECT_OP_CHECK: + if (fsx.fsx_projid == zpc->zpc_expected_projid && + fsx.fsx_xflags & ZFS_PROJINHERIT_FL) + goto out; + + if (!zpc->zpc_newline) { + char c = '\0'; + + (void) printf("%s%c", name, c); + goto out; + } + + if (fsx.fsx_projid != zpc->zpc_expected_projid) + (void) printf("%s - project ID is not set properly " + "(%u/%u)\n", name, fsx.fsx_projid, + (uint32_t)zpc->zpc_expected_projid); + + if (!(fsx.fsx_xflags & ZFS_PROJINHERIT_FL)) + (void) printf("%s - project inherit flag is not set\n", + name); + + goto out; + case ZFS_PROJECT_OP_CLEAR: + if (!(fsx.fsx_xflags & ZFS_PROJINHERIT_FL) && + (zpc->zpc_keep_projid || + fsx.fsx_projid == ZFS_DEFAULT_PROJID)) + goto out; + + fsx.fsx_xflags &= ~ZFS_PROJINHERIT_FL; + if (!zpc->zpc_keep_projid) + fsx.fsx_projid = ZFS_DEFAULT_PROJID; + break; + case ZFS_PROJECT_OP_SET: + if (fsx.fsx_projid == zpc->zpc_expected_projid && + (!zpc->zpc_set_flag || fsx.fsx_xflags & ZFS_PROJINHERIT_FL)) + goto out; + + fsx.fsx_projid = zpc->zpc_expected_projid; + if (zpc->zpc_set_flag) + fsx.fsx_xflags |= ZFS_PROJINHERIT_FL; + break; + default: + ASSERT(0); + break; + } + + ret = ioctl(fd, ZFS_IOC_FSSETXATTR, &fsx); + if (ret) + (void) fprintf(stderr, + gettext("failed to set xattr for %s: %s\n"), + name, strerror(errno)); + +out: + (void) close(fd); + return (ret); +} + +static int +zfs_project_handle_dir(const char *name, zfs_project_control_t *zpc, + list_t *head) +{ + char fullname[PATH_MAX]; + struct dirent *ent; + DIR *dir; + int ret = 0; + + dir = opendir(name); + if (dir == NULL) { + if (errno == ENOENT && zpc->zpc_ignore_noent) + return (0); + + ret = -errno; + (void) fprintf(stderr, gettext("failed to opendir %s: %s\n"), + name, strerror(errno)); + return (ret); + } + + /* Non-top item, ignore the case of being removed or renamed by race. */ + zpc->zpc_ignore_noent = B_TRUE; + errno = 0; + while (!ret && (ent = readdir(dir)) != NULL) { + /* skip "." and ".." */ + if (strcmp(ent->d_name, ".") == 0 || + strcmp(ent->d_name, "..") == 0) + continue; + + if (strlen(ent->d_name) + strlen(name) >= + sizeof (fullname) + 1) { + errno = ENAMETOOLONG; + break; + } + + (void) sprintf(fullname, "%s/%s", name, ent->d_name); + ret = zfs_project_handle_one(fullname, zpc); + if (!ret && zpc->zpc_recursive) { + struct stat64 sb; + + if (stat64(fullname, &sb) == 0 && + (sb.st_mode & S_IFMT) == S_IFDIR) + zfs_project_item_alloc(head, fullname); + } + } + + if (errno && !ret) { + ret = -errno; + (void) fprintf(stderr, gettext("failed to readdir %s: %s\n"), + name, strerror(errno)); + } + + (void) closedir(dir); + return (ret); +} + +int +zfs_project_handle(const char *name, zfs_project_control_t *zpc) +{ + zfs_project_item_t *zpi; + struct stat st; + list_t head; + int ret; + + ret = zfs_project_sanity_check(name, zpc, &st); + if (ret) + return (ret); + + if ((zpc->zpc_op == ZFS_PROJECT_OP_SET || + zpc->zpc_op == ZFS_PROJECT_OP_CHECK) && + zpc->zpc_expected_projid == ZFS_INVALID_PROJID) { + ret = zfs_project_load_projid(name, zpc); + if (ret) + return (ret); + } + + zpc->zpc_ignore_noent = B_FALSE; + ret = zfs_project_handle_one(name, zpc); + if (ret || !S_ISDIR(st.st_mode) || zpc->zpc_dironly || + (!zpc->zpc_recursive && + zpc->zpc_op != ZFS_PROJECT_OP_LIST && + zpc->zpc_op != ZFS_PROJECT_OP_CHECK)) + return (ret); + + list_create(&head, sizeof (zfs_project_item_t), + offsetof(zfs_project_item_t, zpi_list)); + zfs_project_item_alloc(&head, name); + while ((zpi = list_remove_head(&head)) != NULL) { + if (!ret) + ret = zfs_project_handle_dir(zpi->zpi_name, zpc, &head); + free(zpi); + } + + return (ret); +} diff --git a/usr/src/cmd/zfs/zfs_projectutil.h b/usr/src/cmd/zfs/zfs_projectutil.h new file mode 100644 index 0000000000..1792a3383a --- /dev/null +++ b/usr/src/cmd/zfs/zfs_projectutil.h @@ -0,0 +1,49 @@ +/* + * 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) 2017, Intel Corporation. All rights reserved. + */ + +#ifndef _ZFS_PROJECTUTIL_H +#define _ZFS_PROJECTUTIL_H + +typedef enum { + ZFS_PROJECT_OP_DEFAULT = 0, + ZFS_PROJECT_OP_LIST = 1, + ZFS_PROJECT_OP_CHECK = 2, + ZFS_PROJECT_OP_CLEAR = 3, + ZFS_PROJECT_OP_SET = 4, +} zfs_project_ops_t; + +typedef struct zfs_project_control { + uint64_t zpc_expected_projid; + zfs_project_ops_t zpc_op; + boolean_t zpc_dironly; + boolean_t zpc_ignore_noent; + boolean_t zpc_keep_projid; + boolean_t zpc_newline; + boolean_t zpc_recursive; + boolean_t zpc_set_flag; +} zfs_project_control_t; + +int zfs_project_handle(const char *name, zfs_project_control_t *zpc); + +#endif /* _ZFS_PROJECTUTIL_H */ diff --git a/usr/src/cmd/zhack/zhack.c b/usr/src/cmd/zhack/zhack.c index 6b8e9dc47c..1f90f97bdf 100644 --- a/usr/src/cmd/zhack/zhack.c +++ b/usr/src/cmd/zhack/zhack.c @@ -105,7 +105,7 @@ fatal(spa_t *spa, void *tag, const char *fmt, ...) /* ARGSUSED */ static int 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) { /* * Is it a valid type of object to track? diff --git a/usr/src/common/zfs/zfeature_common.c b/usr/src/common/zfs/zfeature_common.c index 0eb681b723..78345bbd88 100644 --- a/usr/src/common/zfs/zfeature_common.c +++ b/usr/src/common/zfs/zfeature_common.c @@ -346,4 +346,24 @@ zpool_feature_init(void) "com.datto:encryption", "encryption", "Support for dataset level encryption", ZFEATURE_FLAG_PER_DATASET, encryption_deps); + + static const spa_feature_t userobj_accounting_deps[] = { + SPA_FEATURE_EXTENSIBLE_DATASET, + SPA_FEATURE_NONE + }; + zfeature_register(SPA_FEATURE_USEROBJ_ACCOUNTING, + "org.zfsonlinux:userobj_accounting", "userobj_accounting", + "User/Group object accounting.", + ZFEATURE_FLAG_READONLY_COMPAT | ZFEATURE_FLAG_PER_DATASET, + userobj_accounting_deps); + + static const spa_feature_t project_quota_deps[] = { + SPA_FEATURE_EXTENSIBLE_DATASET, + SPA_FEATURE_NONE + }; + zfeature_register(SPA_FEATURE_PROJECT_QUOTA, + "org.zfsonlinux:project_quota", "project_quota", + "space/object accounting based on project ID.", + ZFEATURE_FLAG_READONLY_COMPAT | ZFEATURE_FLAG_PER_DATASET, + project_quota_deps); } diff --git a/usr/src/common/zfs/zfeature_common.h b/usr/src/common/zfs/zfeature_common.h index adaa782f98..ab9ff50ff6 100644 --- a/usr/src/common/zfs/zfeature_common.h +++ b/usr/src/common/zfs/zfeature_common.h @@ -66,6 +66,8 @@ typedef enum spa_feature { SPA_FEATURE_RESILVER_DEFER, SPA_FEATURE_ENCRYPTION, SPA_FEATURE_BOOKMARK_V2, + SPA_FEATURE_USEROBJ_ACCOUNTING, + SPA_FEATURE_PROJECT_QUOTA, SPA_FEATURES } spa_feature_t; diff --git a/usr/src/common/zfs/zfs_deleg.c b/usr/src/common/zfs/zfs_deleg.c index 76248de87d..2e8b65d8b0 100644 --- a/usr/src/common/zfs/zfs_deleg.c +++ b/usr/src/common/zfs/zfs_deleg.c @@ -64,10 +64,18 @@ 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}, {ZFS_DELEG_PERM_LOAD_KEY}, {ZFS_DELEG_PERM_CHANGE_KEY}, + {ZFS_DELEG_PERM_PROJECTUSED}, + {ZFS_DELEG_PERM_PROJECTQUOTA}, + {ZFS_DELEG_PERM_PROJECTOBJUSED}, + {ZFS_DELEG_PERM_PROJECTOBJQUOTA}, {NULL} }; diff --git a/usr/src/common/zfs/zfs_deleg.h b/usr/src/common/zfs/zfs_deleg.h index e97b1dae22..1bc8f4d135 100644 --- a/usr/src/common/zfs/zfs_deleg.h +++ b/usr/src/common/zfs/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, @@ -70,6 +74,10 @@ typedef enum { ZFS_DELEG_NOTE_REMAP, ZFS_DELEG_NOTE_LOAD_KEY, ZFS_DELEG_NOTE_CHANGE_KEY, + ZFS_DELEG_NOTE_PROJECTUSED, + ZFS_DELEG_NOTE_PROJECTQUOTA, + ZFS_DELEG_NOTE_PROJECTOBJUSED, + ZFS_DELEG_NOTE_PROJECTOBJQUOTA, ZFS_DELEG_NOTE_NONE } zfs_deleg_note_t; diff --git a/usr/src/common/zfs/zfs_prop.c b/usr/src/common/zfs/zfs_prop.c index ffe94997ed..a4f02b18db 100644 --- a/usr/src/common/zfs/zfs_prop.c +++ b/usr/src/common/zfs/zfs_prop.c @@ -54,7 +54,15 @@ const char *zfs_userquota_prop_prefixes[] = { "userused@", "userquota@", "groupused@", - "groupquota@" + "groupquota@", + "userobjused@", + "userobjquota@", + "groupobjused@", + "groupobjquota@", + "projectused@", + "projectquota@", + "projectobjused@", + "projectobjquota@" }; zprop_desc_t * diff --git a/usr/src/lib/libzfs/common/libzfs_dataset.c b/usr/src/lib/libzfs/common/libzfs_dataset.c index 5b424d730b..b4aab0737a 100644 --- a/usr/src/lib/libzfs/common/libzfs_dataset.c +++ b/usr/src/lib/libzfs/common/libzfs_dataset.c @@ -1033,7 +1033,11 @@ 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 && + uqtype != ZFS_PROP_PROJECTQUOTA && + uqtype != ZFS_PROP_PROJECTOBJQUOTA) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' is readonly"), propname); @@ -1058,7 +1062,7 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, if (intval == 0) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "use 'none' to disable " - "userquota/groupquota")); + "{user|group|project}quota")); goto error; } } else { @@ -3012,19 +3016,26 @@ out: * convert the propname into parameters needed by kernel * Eg: userquota@ahrens -> ZFS_PROP_USERQUOTA, "", 126829 * Eg: userused@matt@domain -> ZFS_PROP_USERUSED, "S-1-123-456", 789 + * Eg: groupquota@staff -> ZFS_PROP_GROUPQUOTA, "", 1234 + * Eg: groupused@staff -> ZFS_PROP_GROUPUSED, "", 1234 + * Eg: projectquota@123 -> ZFS_PROP_PROJECTQUOTA, "", 123 + * Eg: projectused@789 -> ZFS_PROP_PROJECTUSED, "", 789 */ static int userquota_propname_decode(const char *propname, boolean_t zoned, zfs_userquota_prop_t *typep, char *domain, int domainlen, uint64_t *ridp) { zfs_userquota_prop_t type; - char *cp, *end; - char *numericsid = NULL; + char *cp; boolean_t isuser; + boolean_t isgroup; + boolean_t isproject; + struct passwd *pw; + struct group *gr; domain[0] = '\0'; - *ridp = 0; - /* Figure out the property type ({user|group}{quota|space}) */ + + /* Figure out the property type ({user|group|project}{quota|space}) */ for (type = 0; type < ZFS_NUM_USERQUOTA_PROPS; type++) { if (strncmp(propname, zfs_userquota_prop_prefixes[type], strlen(zfs_userquota_prop_prefixes[type])) == 0) @@ -3034,107 +3045,73 @@ userquota_propname_decode(const char *propname, boolean_t zoned, return (EINVAL); *typep = type; - isuser = (type == ZFS_PROP_USERQUOTA || - type == ZFS_PROP_USERUSED); + 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); + isproject = (type == ZFS_PROP_PROJECTQUOTA || + type == ZFS_PROP_PROJECTUSED || type == ZFS_PROP_PROJECTOBJQUOTA || + type == ZFS_PROP_PROJECTOBJUSED); cp = strchr(propname, '@') + 1; - if (strchr(cp, '@')) { + if (isuser && (pw = getpwnam(cp)) != NULL) { + if (zoned && getzoneid() == GLOBAL_ZONEID) + return (ENOENT); + *ridp = pw->pw_uid; + } else if (isgroup && (gr = getgrnam(cp)) != NULL) { + if (zoned && getzoneid() == GLOBAL_ZONEID) + return (ENOENT); + *ridp = gr->gr_gid; + } else if (!isproject && strchr(cp, '@')) { /* * It's a SID name (eg "user@domain") that needs to be * turned into S-1-domainID-RID. */ - int flag = 0; - idmap_stat stat, map_stat; - uid_t pid; - idmap_rid_t rid; - idmap_get_handle_t *gh = NULL; - - stat = idmap_get_create(&gh); - if (stat != IDMAP_SUCCESS) { - idmap_get_destroy(gh); - return (ENOMEM); - } + directory_error_t e; + char *numericsid = NULL; + char *end; + if (zoned && getzoneid() == GLOBAL_ZONEID) return (ENOENT); if (isuser) { - stat = idmap_getuidbywinname(cp, NULL, flag, &pid); - if (stat < 0) - return (ENOENT); - stat = idmap_get_sidbyuid(gh, pid, flag, &numericsid, - &rid, &map_stat); + e = directory_sid_from_user_name(NULL, + cp, &numericsid); } else { - stat = idmap_getgidbywinname(cp, NULL, flag, &pid); - if (stat < 0) - return (ENOENT); - stat = idmap_get_sidbygid(gh, pid, flag, &numericsid, - &rid, &map_stat); + e = directory_sid_from_group_name(NULL, + cp, &numericsid); } - if (stat < 0) { - idmap_get_destroy(gh); - return (ENOENT); - } - stat = idmap_get_mappings(gh); - idmap_get_destroy(gh); - - if (stat < 0) { + if (e != NULL) { + directory_error_free(e); return (ENOENT); } if (numericsid == NULL) return (ENOENT); cp = numericsid; - *ridp = rid; - /* will be further decoded below */ - } - - if (strncmp(cp, "S-1-", 4) == 0) { - /* It's a numeric SID (eg "S-1-234-567-89") */ (void) strlcpy(domain, cp, domainlen); + cp = strrchr(domain, '-'); + *cp = '\0'; + cp++; + errno = 0; - if (*ridp == 0) { - cp = strrchr(domain, '-'); - *cp = '\0'; - cp++; - *ridp = strtoull(cp, &end, 10); - } else { - end = ""; - } - if (numericsid) { - free(numericsid); - numericsid = NULL; - } + *ridp = strtoull(cp, &end, 10); + free(numericsid); + if (errno != 0 || *end != '\0') return (EINVAL); - } else if (!isdigit(*cp)) { - /* - * It's a user/group name (eg "user") that needs to be - * turned into a uid/gid - */ - if (zoned && getzoneid() == GLOBAL_ZONEID) - return (ENOENT); - if (isuser) { - struct passwd *pw; - pw = getpwnam(cp); - if (pw == NULL) - return (ENOENT); - *ridp = pw->pw_uid; - } else { - struct group *gr; - gr = getgrnam(cp); - if (gr == NULL) - return (ENOENT); - *ridp = gr->gr_gid; - } } else { - /* It's a user/group ID (eg "12345"). */ + /* It's a user/group/project ID (eg "12345"). */ + char *end; uid_t id = strtoul(cp, &end, 10); - idmap_rid_t rid; - char *mapdomain; - if (*end != '\0') return (EINVAL); - if (id > MAXUID) { + if (id > MAXUID && !isproject) { /* It's an ephemeral ID. */ + idmap_rid_t rid; + char *mapdomain; + if (idmap_id_to_numeric_domain_rid(id, isuser, &mapdomain, &rid) != 0) return (ENOENT); @@ -3145,7 +3122,6 @@ userquota_propname_decode(const char *propname, boolean_t zoned, } } - ASSERT3P(numericsid, ==, NULL); return (0); } @@ -3200,8 +3176,14 @@ zfs_prop_get_userquota(zfs_handle_t *zhp, const char *propname, if (literal) { (void) snprintf(propbuf, proplen, "%llu", 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 || + type == ZFS_PROP_PROJECTQUOTA || ZFS_PROP_PROJECTOBJQUOTA)) { (void) strlcpy(propbuf, "none", proplen); + } else if (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_GROUPQUOTA || + type == ZFS_PROP_USERUSED || type == ZFS_PROP_GROUPUSED || + type == ZFS_PROP_PROJECTUSED || type == ZFS_PROP_PROJECTQUOTA) { + zfs_nicenum(propvalue, propbuf, proplen); } else { zfs_nicenum(propvalue, propbuf, proplen); } @@ -4806,6 +4788,17 @@ zfs_userspace(zfs_handle_t *zhp, zfs_userquota_prop_t type, if (zfs_ioctl(hdl, ZFS_IOC_USERSPACE_MANY, &zc) != 0) { char errbuf[1024]; + if ((errno == ENOTSUP && + (type == ZFS_PROP_USEROBJUSED || + type == ZFS_PROP_GROUPOBJUSED || + type == ZFS_PROP_USEROBJQUOTA || + type == ZFS_PROP_GROUPOBJQUOTA || + type == ZFS_PROP_PROJECTOBJUSED || + type == ZFS_PROP_PROJECTOBJQUOTA || + type == ZFS_PROP_PROJECTUSED || + type == ZFS_PROP_PROJECTQUOTA))) + break; + (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN, "cannot get used/quota for %s"), zc.zc_name); diff --git a/usr/src/man/man1m/zfs.1m b/usr/src/man/man1m/zfs.1m index 6d841e6ba3..6cf069550e 100644 --- a/usr/src/man/man1m/zfs.1m +++ b/usr/src/man/man1m/zfs.1m @@ -148,6 +148,34 @@ .Oo Fl t Ar type Ns Oo , Ns Ar type Oc Ns ... Oc .Ar filesystem Ns | Ns Ar snapshot .Nm +.Cm projectspace +.Op Fl Hp +.Oo Fl o Ar field Ns Oo , Ns Ar field Oc Ns ... Oc +.Oo Fl s Ar field Oc Ns ... +.Oo Fl S Ar field Oc Ns ... +.Ar filesystem Ns | Ns Ar snapshot +.Nm +.Cm project +.Oo Fl d Ns | Ns Fl r Ns Oc +.Ar file Ns | Ns Ar directory Ns ... +.Nm +.Cm project +.Fl C +.Oo Fl kr Ns Oc +.Ar file Ns | Ns Ar directory Ns ... +.Nm +.Cm project +.Fl c +.Oo Fl 0 Ns Oc +.Oo Fl d Ns | Ns Fl r Ns Oc +.Op Fl p Ar id +.Ar file Ns | Ns Ar directory Ns ... +.Nm +.Cm project +.Op Fl p Ar id +.Oo Fl rs Ns Oc +.Ar file Ns | Ns Ar directory Ns ... +.Nm .Cm mount .Nm .Cm mount @@ -847,6 +875,24 @@ forms: .Sy S-1-123-456-789 .Pc .El +.It Sy userobjused Ns @ Ns Em user +The +.Sy userobjused +property is similar to +.Sy userused +but instead it counts the number of objects consumed by a user. +This property counts all objects allocated on behalf of the user, it may +differ from the results of system tools such as +.Nm df Fl i . +.Pp +When the property +.Sy xattr=on +is set on a file system additional objects will be created per-file to store +extended attributes. +These additional objects are reflected in the +.Sy userobjused +value and are counted against the user's +.Sy userobjquota . .It Sy userrefs This property is set to the number of user holds on this snapshot. User holds are set by using the @@ -866,6 +912,66 @@ The root user, or a user who has been granted the privilege with .Nm zfs Cm allow , can access all groups' usage. +.It Sy groupobjused Ns @ Ns Em group +The number of objects consumed by the specified group in this dataset. +Multiple objects may be charged to the group for each file when extended +attributes are in use. +See the +.Sy userobjused Ns @ Ns Em user +property for more information. +.Pp +Unprivileged users can only access their own groups' space usage. +The root user, or a user who has been granted the +.Sy groupobjused +privilege with +.Nm zfs Cm allow , +can access all groups' usage. +.It Sy projectused Ns @ Ns Em project +The amount of space consumed by the specified project in this dataset. +Project is identified via the project identifier (ID) that is object-based +numeral attribute. +An object can inherit the project ID from its parent object (if the +parent has the flag of inherit project ID that can be set and changed via +.Nm zfs project Fl s ) +when being created. +The privileged user can set and change object's project +ID via +.Nm zfs project Fl s +anytime. +Space is charged to the project of each file, as displayed by +.Nm zfs project . +See the +.Sy userused Ns @ Ns Em user +property for more information. +.Pp +The root user, or a user who has been granted the +.Sy projectused +privilege with +.Nm zfs allow , +can access all projects' usage. +.It Sy projectobjused Ns @ Ns Em project +The +.Sy projectobjused +is similar to +.Sy projectused +but instead it counts the number of objects consumed by project. +When the property +.Sy xattr=on +is set on a fileset, ZFS will create additional objects per-file to store +extended attributes. +These additional objects are reflected in the +.Sy projectobjused +value and are counted against the project's +.Sy projectobjquota . +See the +.Sy userobjused Ns @ Ns Em user +property for more information. +.Pp +The root user, or a user who has been granted the +.Sy projectobjused +privilege with +.Nm zfs allow , +can access all projects' objects usage. .It Sy volblocksize For volumes, specifies the block size of the volume. The @@ -1409,6 +1515,15 @@ symbol, using one of the following forms: .Sy S-1-123-456-789 .Pc .El +.It Sy userobjquota@ Ns Em user Ns = Ns Em size Ns | Ns Sy none +The +.Sy userobjquota +is similar to +.Sy userquota +but it limits the number of objects a user can create. +Please refer to +.Sy userobjused +for more information about how objects are counted. .It Sy groupquota@ Ns Em group Ns = Ns Em size Ns | Ns Sy none Limits the amount of space consumed by the specified group. Group space consumption is identified by the @@ -1421,6 +1536,38 @@ The root user, or a user who has been granted the privilege with .Nm zfs Cm allow , can get and set all groups' quotas. +.It Sy groupobjquota@ Ns Em group Ns = Ns Em size Ns | Ns Sy none +The +.Sy groupobjquota +is similar to +.Sy groupquota +but it limits the number of objects a group can consume. +Please refer to +.Sy userobjused +for more information about how objects are counted. +.It Sy projectquota@ Ns Em project Ns = Ns Em size Ns | Ns Sy none +Limits the amount of space consumed by the specified project. +Project space consumption is identified by the +.Sy projectused@ Ns Em project +property. +Please refer to +.Sy projectused +for more information about how project is identified and set or changed. +.Pp +The root user, or a user who has been granted the +.Sy projectquota +privilege with +.Nm zfs allow , +can access all projects' quotas. +.It Sy projectobjquota@ Ns Em project Ns = Ns Em size Ns | Ns Sy none +The +.Sy projectobjquota +is similar to +.Sy projectquota +but it limits the number of objects a project can consume. +Please refer to +.Sy userobjused +for more information about how objects are counted. .It Sy readonly Ns = Ns Sy on Ns | Ns Sy off Controls whether this dataset can be modified. The default value is @@ -2742,9 +2889,11 @@ Upgrade the specified file system and all descendent file systems. Displays space consumed by, and quotas on, each user in the specified filesystem or snapshot. This corresponds to the -.Sy userused@ Ns Em user +.Sy userused@ Ns Em user , +.Sy userobjused@ Ns Em user , +.Sy userquota@ Ns Em user, and -.Sy userquota@ Ns Em user +.Sy userobjquota@ Ns Em user properties. .Bl -tag -width "-H" .It Fl H @@ -2827,6 +2976,118 @@ except that the default types to display are .Fl t Sy posixgroup Ns \&, Ns Sy smbgroup . .It Xo .Nm +.Cm projectspace +.Op Fl Hp +.Oo Fl o Ar field Ns Oo , Ns Ar field Oc Ns ... Oc +.Oo Fl s Ar field Oc Ns ... +.Oo Fl S Ar field Oc Ns ... +.Ar filesystem Ns | Ns Ar snapshot +.Xc +Displays space consumed by, and quotas on, each project in the specified +filesystem or snapshot. +This subcommand is identical to +.Nm zfs Cm userspace , +except that the project identifier is numeral, not name. +So need neither the option +.Sy -i +for SID to POSIX ID nor +.Sy -n +for numeric ID, nor +.Sy -t +for types. +.It Xo +.Nm +.Cm project +.Oo Fl d Ns | Ns Fl r Ns Oc +.Ar file Ns | Ns Ar directory Ns ... +.Xc +List project identifier (ID) and inherit flag of files or directories. +.Bl -tag -width "-d" +.It Fl d +Show the directory project ID and inherit flag, not its children. +It will overwrite the former specified +.Fl r +option. +.It Fl r +Show on subdirectories recursively. +It will overwrite the former specified +.Fl d +option. +.El +.It Xo +.Nm +.Cm project +.Fl C +.Oo Fl kr Ns Oc +.Ar file Ns | Ns Ar directory Ns ... +.Xc +Clear project inherit flag and/or ID on the files or directories. +.Bl -tag -width "-k" +.It Fl k +Keep the project ID unchanged. +If not specified, the project ID will be reset as zero. +.It Fl r +Clear on subdirectories recursively. +.El +.It Xo +.Nm +.Cm project +.Fl c +.Oo Fl 0 Ns Oc +.Oo Fl d Ns | Ns Fl r Ns Oc +.Op Fl p Ar id +.Ar file Ns | Ns Ar directory Ns ... +.Xc +Check project ID and inherit flag on the files or directories, report the +entries without project inherit flag or with different project IDs from the +specified (via +.Fl p +option) value or the target directory's project ID. +.Bl -tag -width "-0" +.It Fl 0 +Print file name with a trailing NUL instead of newline (by default), like +"find -print0". +.It Fl d +Check the directory project ID and inherit flag, not its children. +It will overwrite the former specified +.Fl r +option. +.It Fl p +Specify the referenced ID for comparing with the target files or directories' +project IDs. +If not specified, the target (top) directory's project ID will be used as the +referenced one. +.It Fl r +Check on subdirectories recursively. +It will overwrite the former specified +.Fl d +option. +.El +.It Xo +.Nm +.Cm project +.Op Fl p Ar id +.Oo Fl rs Ns Oc +.Ar file Ns | Ns Ar directory Ns ... +.Xc +Set project ID and/or inherit flag on the files or directories. +.Bl -tag -width "-p" +.It Fl p +Set the files' or directories' project ID with the given value. +.It Fl r +Set on subdirectories recursively. +.It Fl s +Set project inherit flag on the given files or directories. +It is usually used for setup tree quota on the directory target with +.Fl r +option specified together. +When setup tree quota, by default the directory's project ID will be set to +all its descendants unless you specify the project ID via +.Fl p +option explicitly. +.El +.It Xo +.Nm .Cm mount .Xc Displays all ZFS file systems currently mounted. @@ -3711,6 +3972,11 @@ userprop other Allows changing any user property userquota other Allows accessing any userquota@... property userused other Allows reading any userused@... property +projectobjquota other Allows accessing any projectobjquota@... + property +projectquota other Allows accessing any projectquota@... property +projectobjused other Allows reading any projectobjused@... property +projectused other Allows reading any projectused@... property aclinherit property aclmode property diff --git a/usr/src/man/man5/zpool-features.5 b/usr/src/man/man5/zpool-features.5 index 88efb3d543..21a5369799 100644 --- a/usr/src/man/man5/zpool-features.5 +++ b/usr/src/man/man5/zpool-features.5 @@ -708,7 +708,7 @@ vdevs from an allocation class are removed. .sp .ne 2 .na -\fB\fBcom.datto:encryption\fR\fR +\fB\fBencryption\fR\fR .ad .RS 4n .TS @@ -744,6 +744,69 @@ running one to be immediately restarted from the beginning. This feature becomes \fBactive\fR once a resilver has been deferred, and returns to being \fBenabled\fR when the deferred resilver begins. +.RE + +.sp +.ne 2 +.na +\fBuserobj_accounting\fR +.ad +.RS 4n +.TS +l l . +GUID org.zfsonlinux:userobj_accounting +READ\-ONLY COMPATIBLE yes +DEPENDENCIES extensible_dataset +.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. +Each filesystem will be upgraded automatically when remounted, or when new +files are created under that filesystem. +The upgrade can also be started manually on filesystems by running +`zfs set version=current `. +The upgrade process runs in the background and may take a while to complete +for filesystems containing a large number of files. +.RE + +.sp +.ne 2 +.na +\fBproject_quota\fR +.ad +.RS 4n +.TS +l l . +GUID org.zfsonlinux:project_quota +READ\-ONLY COMPATIBLE yes +DEPENDENCIES extensible_dataset +.TE + +This feature allows administrators to account the space and object usage +information against the project identifier (ID). + +The project ID is a new object-based attribute. +When upgrading an existing filesystem, an object without a project ID +attribute will be assigned a zero project ID. +After this feature is enabled, a newly created object will inherit +its parent directory's project ID if the parent's inherit flag is set (via +\fBzfs project [-s|-C]\fR). +Otherwise, the new object's project ID will be set as zero. +An object's project ID can be changed at any time by the owner (or privileged +user) via \fBzfs project -p $prjid\fR. + +This feature will become \fBactive\fR as soon as it is enabled and will never +return to being \fBdisabled\fR. +Each filesystem will be upgraded automatically when remounted or when a new file +is created under that filesystem. +The upgrade can also be triggered on filesystems via `zfs set version=current +`. +The upgrade process runs in the background and may take a while to complete +for the filesystems containing a large number of files. +.RE .SH "SEE ALSO" \fBzfs\fR(1M), \fBzpool\fR(1M) diff --git a/usr/src/pkg/manifests/system-test-zfstest.mf b/usr/src/pkg/manifests/system-test-zfstest.mf index 5244b2f791..7b0574942e 100644 --- a/usr/src/pkg/manifests/system-test-zfstest.mf +++ b/usr/src/pkg/manifests/system-test-zfstest.mf @@ -137,6 +137,7 @@ dir path=opt/zfs-tests/tests/functional/pool_checkpoint dir path=opt/zfs-tests/tests/functional/pool_names dir path=opt/zfs-tests/tests/functional/poolversion dir path=opt/zfs-tests/tests/functional/privilege +dir path=opt/zfs-tests/tests/functional/projectquota dir path=opt/zfs-tests/tests/functional/quota dir path=opt/zfs-tests/tests/functional/redundancy dir path=opt/zfs-tests/tests/functional/refquota @@ -154,6 +155,7 @@ dir path=opt/zfs-tests/tests/functional/snapused dir path=opt/zfs-tests/tests/functional/sparse dir path=opt/zfs-tests/tests/functional/threadsappend dir path=opt/zfs-tests/tests/functional/truncate +dir path=opt/zfs-tests/tests/functional/upgrade dir path=opt/zfs-tests/tests/functional/userquota dir path=opt/zfs-tests/tests/functional/utils_test dir path=opt/zfs-tests/tests/functional/vdev_zaps @@ -2627,6 +2629,51 @@ file path=opt/zfs-tests/tests/functional/privilege/cleanup mode=0555 file path=opt/zfs-tests/tests/functional/privilege/privilege_001_pos mode=0555 file path=opt/zfs-tests/tests/functional/privilege/privilege_002_pos mode=0555 file path=opt/zfs-tests/tests/functional/privilege/setup mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/cleanup mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectid_001_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectid_002_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectid_003_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectquota.cfg \ + mode=0444 +file path=opt/zfs-tests/tests/functional/projectquota/projectquota_001_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectquota_002_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectquota_003_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectquota_004_neg \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectquota_005_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectquota_006_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectquota_007_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectquota_008_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectquota_009_pos \ + mode=0555 +file \ + path=opt/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib \ + mode=0444 +file path=opt/zfs-tests/tests/functional/projectquota/projectspace_001_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectspace_002_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectspace_003_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projectspace_004_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projecttree_001_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projecttree_002_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/projecttree_003_neg \ + mode=0555 +file path=opt/zfs-tests/tests/functional/projectquota/setup mode=0555 file path=opt/zfs-tests/tests/functional/quota/cleanup mode=0555 file path=opt/zfs-tests/tests/functional/quota/quota.cfg mode=0444 file path=opt/zfs-tests/tests/functional/quota/quota.kshlib mode=0444 @@ -2915,11 +2962,21 @@ file path=opt/zfs-tests/tests/functional/truncate/setup mode=0555 file path=opt/zfs-tests/tests/functional/truncate/truncate.cfg mode=0444 file path=opt/zfs-tests/tests/functional/truncate/truncate_001_pos mode=0555 file path=opt/zfs-tests/tests/functional/truncate/truncate_002_pos mode=0555 +file path=opt/zfs-tests/tests/functional/upgrade/cleanup mode=0555 +file path=opt/zfs-tests/tests/functional/upgrade/setup mode=0555 +file path=opt/zfs-tests/tests/functional/upgrade/upgrade_common.kshlib \ + mode=0444 +file path=opt/zfs-tests/tests/functional/upgrade/upgrade_projectquota_001_pos \ + mode=0555 +file path=opt/zfs-tests/tests/functional/upgrade/upgrade_userobj_001_pos \ + mode=0555 file path=opt/zfs-tests/tests/functional/userquota/cleanup mode=0555 file path=opt/zfs-tests/tests/functional/userquota/groupspace_001_pos \ mode=0555 file path=opt/zfs-tests/tests/functional/userquota/groupspace_002_pos \ mode=0555 +file path=opt/zfs-tests/tests/functional/userquota/groupspace_003_pos \ + mode=0555 file path=opt/zfs-tests/tests/functional/userquota/setup mode=0555 file path=opt/zfs-tests/tests/functional/userquota/userquota.cfg mode=0444 file path=opt/zfs-tests/tests/functional/userquota/userquota_001_pos mode=0555 @@ -2934,10 +2991,12 @@ file path=opt/zfs-tests/tests/functional/userquota/userquota_009_pos mode=0555 file path=opt/zfs-tests/tests/functional/userquota/userquota_010_pos mode=0555 file path=opt/zfs-tests/tests/functional/userquota/userquota_011_pos mode=0555 file path=opt/zfs-tests/tests/functional/userquota/userquota_012_neg mode=0555 +file path=opt/zfs-tests/tests/functional/userquota/userquota_013_pos mode=0555 file path=opt/zfs-tests/tests/functional/userquota/userquota_common.kshlib \ mode=0444 file path=opt/zfs-tests/tests/functional/userquota/userspace_001_pos mode=0555 file path=opt/zfs-tests/tests/functional/userquota/userspace_002_pos mode=0555 +file path=opt/zfs-tests/tests/functional/userquota/userspace_003_pos mode=0555 file path=opt/zfs-tests/tests/functional/utils_test/cleanup mode=0555 file path=opt/zfs-tests/tests/functional/utils_test/setup mode=0555 file path=opt/zfs-tests/tests/functional/utils_test/utils_test.cfg mode=0444 diff --git a/usr/src/test/zfs-tests/cmd/mkfiles/mkfiles.c b/usr/src/test/zfs-tests/cmd/mkfiles/mkfiles.c index 58c7d5f509..8fb3227d65 100644 --- a/usr/src/test/zfs-tests/cmd/mkfiles/mkfiles.c +++ b/usr/src/test/zfs-tests/cmd/mkfiles/mkfiles.c @@ -36,6 +36,9 @@ main(int argc, char **argv) { unsigned int numfiles = 0; unsigned int first_file = 0; + unsigned int i; + char buf[MAXPATHLEN]; + if (argc < 3 || argc > 4) usage("Invalid number of arguments", -1); @@ -48,8 +51,7 @@ main(int argc, char **argv) if (numfiles < first_file) usage("First file larger than last file", -3); - char buf[MAXPATHLEN]; - for (unsigned int i = first_file; i <= numfiles; i++) { + for (i = first_file; i < first_file + numfiles; i++) { int fd; (void) snprintf(buf, MAXPATHLEN, "%s%u", argv[1], i); if ((fd = open(buf, O_CREAT | O_EXCL, O_RDWR)) == -1) { diff --git a/usr/src/test/zfs-tests/runfiles/delphix.run b/usr/src/test/zfs-tests/runfiles/delphix.run index bdac5ce314..627539a490 100644 --- a/usr/src/test/zfs-tests/runfiles/delphix.run +++ b/usr/src/test/zfs-tests/runfiles/delphix.run @@ -544,6 +544,15 @@ tests = ['poolversion_001_pos', 'poolversion_002_pos'] [/opt/zfs-tests/tests/functional/privilege] tests = ['privilege_001_pos', 'privilege_002_pos'] +[/opt/zfs-tests/tests/functional/projectquota] +tests = ['projectid_001_pos', 'projectid_002_pos', 'projectid_003_pos', + 'projectquota_001_pos', 'projectquota_002_pos', 'projectquota_003_pos', + 'projectquota_004_neg', 'projectquota_005_pos', 'projectquota_006_pos', + 'projectquota_007_pos', 'projectquota_008_pos', 'projectquota_009_pos', + 'projectspace_001_pos', 'projectspace_002_pos', 'projectspace_003_pos', + 'projectspace_004_pos', + 'projecttree_002_pos', 'projecttree_003_neg' ] + [/opt/zfs-tests/tests/functional/quota] tests = ['quota_001_pos', 'quota_002_pos', 'quota_003_pos', 'quota_004_pos', 'quota_005_pos', 'quota_006_neg'] @@ -645,13 +654,18 @@ tests = ['threadsappend_001_pos'] [/opt/zfs-tests/tests/functional/truncate] tests = ['truncate_001_pos', 'truncate_002_pos'] +[/opt/zfs-tests/tests/functional/upgrade] +tests = ['upgrade_userobj_001_pos', 'upgrade_projectquota_001_pos'] + [/opt/zfs-tests/tests/functional/userquota] -tests = ['groupspace_001_pos', 'groupspace_002_pos', 'userquota_001_pos', - 'userquota_002_pos', 'userquota_003_pos', 'userquota_004_pos', - 'userquota_005_neg', 'userquota_006_pos', 'userquota_007_pos', - 'userquota_008_pos', 'userquota_009_pos', 'userquota_010_pos', - 'userquota_011_pos', 'userquota_012_neg', 'userspace_001_pos', - 'userspace_002_pos'] +tests = [ + 'userquota_001_pos', 'userquota_002_pos', 'userquota_003_pos', + 'userquota_004_pos', 'userquota_005_neg', 'userquota_006_pos', + 'userquota_007_pos', 'userquota_008_pos', 'userquota_009_pos', + 'userquota_010_pos', 'userquota_011_pos', 'userquota_012_neg', + 'userquota_013_pos', + 'userspace_001_pos', 'userspace_002_pos', 'userspace_003_pos', + 'groupspace_001_pos', 'groupspace_002_pos', 'groupspace_003_pos' ] [/opt/zfs-tests/tests/functional/utils_test] tests = ['utils_test_001_pos', 'utils_test_002_pos', 'utils_test_003_pos', diff --git a/usr/src/test/zfs-tests/runfiles/omnios.run b/usr/src/test/zfs-tests/runfiles/omnios.run index 968f8acfd8..a5cc0f67b6 100644 --- a/usr/src/test/zfs-tests/runfiles/omnios.run +++ b/usr/src/test/zfs-tests/runfiles/omnios.run @@ -161,7 +161,7 @@ tests = ['zfs_mount_001_pos', 'zfs_mount_002_pos', 'zfs_mount_003_pos', 'zfs_mount_004_pos', 'zfs_mount_005_pos', 'zfs_mount_006_pos', 'zfs_mount_007_pos', 'zfs_mount_008_pos', 'zfs_mount_009_neg', 'zfs_mount_010_neg', 'zfs_mount_011_neg', 'zfs_mount_012_neg', - 'zfs_mount_all_001_pos', 'zfs_mount_all_fail', 'zfs_mount_all_mountpoints' + 'zfs_mount_all_001_pos', 'zfs_mount_all_fail', 'zfs_mount_all_mountpoints', 'zfs_mount_encrypted'] [/opt/zfs-tests/tests/functional/cli_root/zfs_program] @@ -543,6 +543,15 @@ tests = ['poolversion_001_pos', 'poolversion_002_pos'] [/opt/zfs-tests/tests/functional/privilege] tests = ['privilege_001_pos', 'privilege_002_pos'] +[/opt/zfs-tests/tests/functional/projectquota] +tests = ['projectid_001_pos', 'projectid_002_pos', 'projectid_003_pos', + 'projectquota_001_pos', 'projectquota_002_pos', 'projectquota_003_pos', + 'projectquota_004_neg', 'projectquota_005_pos', 'projectquota_006_pos', + 'projectquota_007_pos', 'projectquota_008_pos', 'projectquota_009_pos', + 'projectspace_001_pos', 'projectspace_002_pos', 'projectspace_003_pos', + 'projectspace_004_pos', + 'projecttree_002_pos', 'projecttree_003_neg' ] + [/opt/zfs-tests/tests/functional/quota] tests = ['quota_001_pos', 'quota_002_pos', 'quota_003_pos', 'quota_004_pos', 'quota_005_pos', 'quota_006_neg'] @@ -644,13 +653,18 @@ tests = ['threadsappend_001_pos'] [/opt/zfs-tests/tests/functional/truncate] tests = ['truncate_001_pos', 'truncate_002_pos'] +[/opt/zfs-tests/tests/functional/upgrade] +tests = ['upgrade_userobj_001_pos', 'upgrade_projectquota_001_pos'] + [/opt/zfs-tests/tests/functional/userquota] -tests = ['groupspace_001_pos', 'groupspace_002_pos', 'userquota_001_pos', - 'userquota_002_pos', 'userquota_003_pos', 'userquota_004_pos', - 'userquota_005_neg', 'userquota_006_pos', 'userquota_007_pos', - 'userquota_008_pos', 'userquota_009_pos', 'userquota_010_pos', - 'userquota_011_pos', 'userquota_012_neg', 'userspace_001_pos', - 'userspace_002_pos'] +tests = [ + 'userquota_001_pos', 'userquota_002_pos', 'userquota_003_pos', + 'userquota_004_pos', 'userquota_005_neg', 'userquota_006_pos', + 'userquota_007_pos', 'userquota_008_pos', 'userquota_009_pos', + 'userquota_010_pos', 'userquota_011_pos', 'userquota_012_neg', + 'userquota_013_pos', + 'userspace_001_pos', 'userspace_002_pos', 'userspace_003_pos', + 'groupspace_001_pos', 'groupspace_002_pos', 'groupspace_003_pos' ] [/opt/zfs-tests/tests/functional/utils_test] tests = ['utils_test_001_pos', 'utils_test_002_pos', 'utils_test_003_pos', diff --git a/usr/src/test/zfs-tests/runfiles/openindiana.run b/usr/src/test/zfs-tests/runfiles/openindiana.run index f96a889d54..e7d8fb3c65 100644 --- a/usr/src/test/zfs-tests/runfiles/openindiana.run +++ b/usr/src/test/zfs-tests/runfiles/openindiana.run @@ -161,7 +161,7 @@ tests = ['zfs_mount_001_pos', 'zfs_mount_002_pos', 'zfs_mount_003_pos', 'zfs_mount_004_pos', 'zfs_mount_005_pos', 'zfs_mount_006_pos', 'zfs_mount_007_pos', 'zfs_mount_008_pos', 'zfs_mount_009_neg', 'zfs_mount_010_neg', 'zfs_mount_011_neg', 'zfs_mount_012_neg', - 'zfs_mount_all_001_pos', 'zfs_mount_all_fail', 'zfs_mount_all_mountpoints' + 'zfs_mount_all_001_pos', 'zfs_mount_all_fail', 'zfs_mount_all_mountpoints', 'zfs_mount_encrypted'] [/opt/zfs-tests/tests/functional/cli_root/zfs_program] @@ -543,6 +543,15 @@ tests = ['poolversion_001_pos', 'poolversion_002_pos'] [/opt/zfs-tests/tests/functional/privilege] tests = ['privilege_001_pos', 'privilege_002_pos'] +[/opt/zfs-tests/tests/functional/projectquota] +tests = ['projectid_001_pos', 'projectid_002_pos', 'projectid_003_pos', + 'projectquota_001_pos', 'projectquota_002_pos', 'projectquota_003_pos', + 'projectquota_004_neg', 'projectquota_005_pos', 'projectquota_006_pos', + 'projectquota_007_pos', 'projectquota_008_pos', 'projectquota_009_pos', + 'projectspace_001_pos', 'projectspace_002_pos', 'projectspace_003_pos', + 'projectspace_004_pos', + 'projecttree_002_pos', 'projecttree_003_neg' ] + [/opt/zfs-tests/tests/functional/quota] tests = ['quota_001_pos', 'quota_002_pos', 'quota_003_pos', 'quota_004_pos', 'quota_005_pos', 'quota_006_neg'] @@ -644,13 +653,18 @@ tests = ['threadsappend_001_pos'] [/opt/zfs-tests/tests/functional/truncate] tests = ['truncate_001_pos', 'truncate_002_pos'] +[/opt/zfs-tests/tests/functional/upgrade] +tests = ['upgrade_userobj_001_pos', 'upgrade_projectquota_001_pos'] + [/opt/zfs-tests/tests/functional/userquota] -tests = ['groupspace_001_pos', 'groupspace_002_pos', 'userquota_001_pos', - 'userquota_002_pos', 'userquota_003_pos', 'userquota_004_pos', - 'userquota_005_neg', 'userquota_006_pos', 'userquota_007_pos', - 'userquota_008_pos', 'userquota_009_pos', 'userquota_010_pos', - 'userquota_011_pos', 'userquota_012_neg', 'userspace_001_pos', - 'userspace_002_pos'] +tests = [ + 'userquota_001_pos', 'userquota_002_pos', 'userquota_003_pos', + 'userquota_004_pos', 'userquota_005_neg', 'userquota_006_pos', + 'userquota_007_pos', 'userquota_008_pos', 'userquota_009_pos', + 'userquota_010_pos', 'userquota_011_pos', 'userquota_012_neg', + 'userquota_013_pos', + 'userspace_001_pos', 'userspace_002_pos', 'userspace_003_pos', + 'groupspace_001_pos', 'groupspace_002_pos', 'groupspace_003_pos' ] [/opt/zfs-tests/tests/functional/utils_test] tests = ['utils_test_001_pos', 'utils_test_002_pos', 'utils_test_003_pos', diff --git a/usr/src/test/zfs-tests/runfiles/smartos.run b/usr/src/test/zfs-tests/runfiles/smartos.run index 2711a179f6..06da5639ff 100644 --- a/usr/src/test/zfs-tests/runfiles/smartos.run +++ b/usr/src/test/zfs-tests/runfiles/smartos.run @@ -471,6 +471,15 @@ post = [/opt/zfs-tests/tests/functional/poolversion] tests = ['poolversion_001_pos', 'poolversion_002_pos'] +[/opt/zfs-tests/tests/functional/projectquota] +tests = ['projectid_001_pos', 'projectid_002_pos', 'projectid_003_pos', + 'projectquota_001_pos', 'projectquota_002_pos', 'projectquota_003_pos', + 'projectquota_004_neg', 'projectquota_005_pos', 'projectquota_006_pos', + 'projectquota_007_pos', 'projectquota_008_pos', 'projectquota_009_pos', + 'projectspace_001_pos', 'projectspace_002_pos', 'projectspace_003_pos', + 'projectspace_004_pos', + 'projecttree_002_pos', 'projecttree_003_neg' ] + [/opt/zfs-tests/tests/functional/quota] tests = ['quota_001_pos', 'quota_002_pos', 'quota_003_pos', 'quota_004_pos', 'quota_005_pos', 'quota_006_neg'] @@ -553,6 +562,19 @@ tests = ['threadsappend_001_pos'] [/opt/zfs-tests/tests/functional/truncate] tests = ['truncate_001_pos', 'truncate_002_pos'] +[/opt/zfs-tests/tests/functional/upgrade] +tests = ['upgrade_userobj_001_pos', 'upgrade_projectquota_001_pos'] + +[/opt/zfs-tests/tests/functional/userquota] +tests = [ + 'userquota_001_pos', 'userquota_002_pos', 'userquota_003_pos', + 'userquota_004_pos', 'userquota_005_neg', 'userquota_006_pos', + 'userquota_007_pos', 'userquota_008_pos', 'userquota_009_pos', + 'userquota_010_pos', 'userquota_011_pos', 'userquota_012_neg', + 'userquota_013_pos', + 'userspace_001_pos', 'userspace_002_pos', 'userspace_003_pos', + 'groupspace_001_pos', 'groupspace_002_pos', 'groupspace_003_pos' ] + [/opt/zfs-tests/tests/functional/utils_test] tests = ['utils_test_001_pos', 'utils_test_002_pos', 'utils_test_003_pos', 'utils_test_004_pos', 'utils_test_005_pos', 'utils_test_006_pos', diff --git a/usr/src/test/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg b/usr/src/test/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg index 998fe0e2b3..0db15d67a7 100644 --- a/usr/src/test/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg +++ b/usr/src/test/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg @@ -81,4 +81,6 @@ typeset -a properties=( "feature@resilver_defer" "feature@encryption" "feature@bookmark_v2" + "feature@userobj_accounting" + "feature@project_quota" ) diff --git a/usr/src/test/zfs-tests/tests/functional/privilege/setup.ksh b/usr/src/test/zfs-tests/tests/functional/privilege/setup.ksh index aa5f0aeb73..af38b4dfdd 100644 --- a/usr/src/test/zfs-tests/tests/functional/privilege/setup.ksh +++ b/usr/src/test/zfs-tests/tests/functional/privilege/setup.ksh @@ -31,6 +31,10 @@ . $STF_SUITE/include/libtest.shlib +if is_linux; then + log_unsupported "Requires pfexec command" +fi + ZFS_USER=zfsrbac USES_NIS=false diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/Makefile b/usr/src/test/zfs-tests/tests/functional/projectquota/Makefile new file mode 100644 index 0000000000..18c90fca34 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/Makefile @@ -0,0 +1,21 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2013, 2016 by Delphix. All rights reserved. +# + +include $(SRC)/Makefile.master + +ROOTOPTPKG = $(ROOT)/opt/zfs-tests +TARGETDIR = $(ROOTOPTPKG)/tests/functional/projectquota + +include $(SRC)/test/zfs-tests/Makefile.com diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/cleanup.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/cleanup.ksh new file mode 100755 index 0000000000..0440e3d8af --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/cleanup.ksh @@ -0,0 +1,37 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +log_must cleanup_projectquota +log_must del_user $PUSER +log_must del_group $PGROUP +default_cleanup diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectid_001_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectid_001_pos.ksh new file mode 100644 index 0000000000..188892f7be --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectid_001_pos.ksh @@ -0,0 +1,100 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# +# DESCRIPTION: +# Check project ID/flags can be set/inherited properly +# +# +# STRATEGY: +# 1. Create a regular file and a directroy. +# 2. Set project ID on both directroy and regular file. +# 3. New created subdir or regular file should inherit its parent's +# project ID if its parent has project inherit flag. +# 4. New created subdir should inherit its parent project's inherit flag. +# + +function cleanup +{ + log_must rm -f $PRJFILE + log_must rm -rf $PRJDIR +} + +log_onexit cleanup + +log_assert "Check project ID/flags can be set/inherited properly" + +log_must touch $PRJFILE +log_must mkdir $PRJDIR + +# log_must chattr -p $PRJID1 $PRJFILE +log_must zfs project -s -p $PRJID1 $PRJFILE +# log_must eval "lsattr -p $PRJFILE | grep $PRJID1 | grep -v '\-P[- ]* '" +log_must eval "zfs project $PRJFILE | grep $PRJID1" +# log_must chattr -p $PRJID1 $PRJDIR +log_must zfs project -s -p $PRJID1 $PRJDIR +# log_must eval "lsattr -pd $PRJDIR | grep $PRJID1 | grep -v '\-P[- ]* '" +log_must eval "zfs project $PRJDIR | grep $PRJID1" + +# "-1" is invalid project ID, should be denied +# log_mustnot chattr -p -1 $PRJFILE +log_mustnot zfs project -s -p -1 $PRJFILE +# log_must eval "lsattr -p $PRJFILE | grep $PRJID1 | grep -v '\-P[- ]* '" +log_must eval "zfs project $PRJFILE | grep $PRJID1" + +log_must mkdir $PRJDIR/dchild +# log_must eval "lsattr -pd $PRJDIR/dchild | grep $PRJID1 | grep '\-P[- ]* '" +log_must eval "zfs project -d $PRJDIR/dchild | grep $PRJID1" +log_must touch $PRJDIR/fchild +# log_must eval "lsattr -p $PRJDIR/fchild | grep $PRJID1" +log_must eval "zfs project $PRJDIR/fchild | grep $PRJID1" + +log_must touch $PRJDIR/dchild/foo +# log_must eval "lsattr -p $PRJDIR/dchild/foo | grep $PRJID1" +log_must eval "zfs project $PRJDIR/dchild/foo | grep $PRJID1" + +# do not support project ID/flag on block special file +log_must mknod $PRJDIR/dchild/b_foo b 124 124 +# log_mustnot lsattr -p $PRJDIR/dchild/b_foo +# log_mustnot chattr -p 123 $PRJDIR/dchild/b_foo +log_mustnot zfs project -s -p 123 $PRJDIR/dchild/b_foo + +# do not support project ID/flag on character special file +log_must mknod $PRJDIR/dchild/c_foo c 125 125 +# log_mustnot lsattr -p $PRJDIR/dchild/c_foo +# log_mustnot chattr -p 123 $PRJDIR/dchild/c_foo +log_mustnot zfs project -s -p 123 $PRJDIR/dchild/c_foo + +log_pass "Check project ID/flags can be set/inherited properly" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectid_002_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectid_002_pos.ksh new file mode 100644 index 0000000000..b877f488ae --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectid_002_pos.ksh @@ -0,0 +1,91 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# +# DESCRIPTION: +# Project ID affects POSIX behavior +# +# +# STRATEGY: +# 1. Create three directories +# 2. Set tdir1 and tdir3 project ID as PRJID1, +# set tdir2 project ID as PRJID2. +# 3. Create regular file under tdir1. It inherits tdir1 proejct ID. +# 4. Hardlink from tdir1's child to tdir2 should be denied, +# move tdir1's child to tdir2 will be object recreated. +# 5. Hardlink from tdir1's child to tdir3 should succeed. +# + +function cleanup +{ + log_must rm -rf $PRJDIR1 + log_must rm -rf $PRJDIR2 + log_must rm -rf $PRJDIR3 +} + +log_onexit cleanup + +log_assert "Project ID affects POSIX behavior" + +log_must mkdir $PRJDIR1 +log_must mkdir $PRJDIR2 +log_must mkdir $PRJDIR3 +log_must mkdir $PRJDIR3/dir + +# log_must chattr +P -p $PRJID1 $PRJDIR1 +log_must zfs project -s -p $PRJID1 $PRJDIR1 +# log_must chattr +P -p $PRJID2 $PRJDIR2 +log_must zfs project -s -p $PRJID2 $PRJDIR2 + +log_must touch $PRJDIR1/tfile1 +log_must touch $PRJDIR1/tfile2 +# log_must eval "lsattr -p $PRJDIR1/tfile1 | grep $PRJID1" +log_must eval "zfs project $PRJDIR1/tfile1 | grep $PRJID1" + +log_mustnot ln $PRJDIR1/tfile1 $PRJDIR2/tfile2 + +log_must mv $PRJDIR1/tfile1 $PRJDIR2/tfile2 +# log_must eval "lsattr -p $PRJDIR2/tfile2 | grep $PRJID2" +log_must eval "zfs project $PRJDIR2/tfile2 | grep $PRJID2" + +log_must mv $PRJDIR3/dir $PRJDIR2/ +# log_must eval "lsattr -dp $PRJDIR2/dir | grep $PRJID2" +log_must eval "zfs project -d $PRJDIR2/dir | grep $PRJID2" + +# log_must chattr +P -p $PRJID1 $PRJDIR3 +log_must zfs project -s -p $PRJID1 $PRJDIR3 +log_must ln $PRJDIR1/tfile2 $PRJDIR3/tfile3 + +log_pass "Project ID affects POSIX behavior" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectid_003_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectid_003_pos.ksh new file mode 100644 index 0000000000..8a1cd0ca3e --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectid_003_pos.ksh @@ -0,0 +1,86 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. Fan rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check changing project ID for the file with directory-based +# extended attributes. +# +# +# STRATEGY: +# 1. create new file with default project ID +# 2. set non-ACL extended attributes on the file +# 3. use zfs projectspace to check the object usage +# 4. change the file's project ID +# 5. use zfs projectspace to check the object usage again +# + +function cleanup +{ + log_must rm -f $PRJGUARD + log_must rm -f $PRJFILE +} + +log_onexit cleanup + +log_assert "Check changing project ID with directory-based extended attributes" + +log_must zfs set xattr=on $QFS + +log_must touch $PRJGUARD +# log_must chattr -p $PRJID1 $PRJGUARD +log_must zfs project -s -p $PRJID1 $PRJGUARD +log_must touch $PRJFILE + +# log_must setfattr -n trusted.ea1 -v val1 $PRJFILE +# log_must setfattr -n trusted.ea2 -v val2 $PRJFILE +# log_must setfattr -n trusted.ea3 -v val3 $PRJFILE +echo "dummy attribute data" >/tmp/attr.$$ +log_must runat $PRJFILE cp /tmp/attr.$$ trusted.ea1 +log_must runat $PRJFILE cp /tmp/attr.$$ trusted.ea2 +log_must runat $PRJFILE cp /tmp/attr.$$ trusted.ea3 +rm /tmp/attr.$$ + +sync_pool +typeset prj_bef=$(project_obj_count $QFS $PRJID1) + +# log_must chattr -p $PRJID1 $PRJFILE +log_must zfs project -s -p $PRJID1 $PRJFILE +sync_pool +typeset prj_aft=$(project_obj_count $QFS $PRJID1) + +[[ $prj_aft -ge $((prj_bef + 5)) ]] || + log_fail "new value ($prj_aft) is NOT 5 largr than old one ($prj_bef)" + +log_pass "Changing project ID with directory-based extended attributes pass" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota.cfg b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota.cfg new file mode 100644 index 0000000000..564ab3ef96 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota.cfg @@ -0,0 +1,46 @@ +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +export PUSER=puser +export PGROUP=pgroup + +export PRJID1=1001 +export PRJID2=1002 + +export QFS=$TESTPOOL/$TESTFS +export PRJFILE=$TESTDIR/tfile +export PRJGUARD=$TESTDIR/guard +export PRJDIR=$TESTDIR/tdir +export PRJDIR1=$TESTDIR/tdir1 +export PRJDIR2=$TESTDIR/tdir2 +export PRJDIR3=$TESTDIR/tdir3 + +export PQUOTA_LIMIT=1000000 +export PQUOTA_OBJLIMIT=1000 diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_001_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_001_pos.ksh new file mode 100644 index 0000000000..3c19d16194 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_001_pos.ksh @@ -0,0 +1,89 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# +# DESCRIPTION: +# Check the basic function of the project{obj}quota +# +# +# STRATEGY: +# 1. Set projectquota and overwrite the quota size. +# 2. The write operation should fail with Disc quota exceeded +# 3. Set projectobjquota and create up to the quota size. +# 4. More create should fail with Disc quota exceeded +# 5. More chattr to such project should fail with Disc quota exceeded +# + +function cleanup +{ + cleanup_projectquota +} + +log_onexit cleanup + +log_assert "If operation exceeds project{obj}quota size, it will fail" + +mkmount_writable $QFS + +log_note "Check the projectquota@$PRJID1" +log_must user_run $PUSER mkdir $PRJDIR +# log_must chattr +P -p $PRJID1 $PRJDIR +log_must zfs project -s -p $PRJID1 $PRJDIR + +log_must zfs set projectquota@$PRJID1=$PQUOTA_LIMIT $QFS +log_must user_run $PUSER mkfile $PQUOTA_LIMIT $PRJDIR/qf +sync_pool +log_mustnot user_run $PUSER mkfile 1 $PRJDIR/of + +log_must rm -rf $PRJDIR + +log_note "Check the projectobjquota@$PRJID2" +log_must zfs set xattr=on $QFS +log_must user_run $PUSER mkdir $PRJDIR +# log_must chattr +P -p $PRJID2 $PRJDIR +log_must zfs project -s -p $PRJID2 $PRJDIR + +log_must zfs set projectobjquota@$PRJID2=$PQUOTA_OBJLIMIT $QFS +log_must user_run $PUSER mkfiles $PRJDIR/qf_ $((PQUOTA_OBJLIMIT - 1)) +sync_pool +log_mustnot user_run $PUSER mkfile 1 $PRJDIR/of + +log_must user_run $PUSER touch $PRJFILE +# log_must user_run $PUSER chattr -p 123 $PRJFILE +log_must user_run $PUSER zfs project -s -p 123 $PRJFILE +# log_mustnot user_run $PUSER chattr -p $PRJID2 $PRJFILE +log_mustnot user_run $PUSER zfs project -s -p $PRJID2 $PRJFILE + +log_pass "Operation exceeds project{obj}quota size failed as expect" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_002_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_002_pos.ksh new file mode 100644 index 0000000000..75d1bf2858 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_002_pos.ksh @@ -0,0 +1,87 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# The project{obj}quota can be set during zpool or zfs creation +# +# +# STRATEGY: +# 1. Set project{obj}quota via "zpool -O or zfs create -o" +# + +verify_runnable "global" + +function cleanup +{ + if poolexists $TESTPOOL1; then + log_must zpool destroy $TESTPOOL1 + fi + + if [[ -f $pool_vdev ]]; then + rm -f $pool_vdev + fi +} + +log_onexit cleanup + +log_assert "The project{obj}quota can be set during zpool,zfs creation" + +typeset pool_vdev=$TEST_BASE_DIR/pool_dev.$$ + +log_must mkfile 500m $pool_vdev + +if poolexists $TESTPOOL1; then + zpool destroy $TESTPOOL1 +fi + +log_must zpool create -O projectquota@$PRJID1=$PQUOTA_LIMIT \ + -O projectobjquota@$PRJID2=$PQUOTA_OBJLIMIT $TESTPOOL1 $pool_vdev + +log_must zfs create -o projectquota@$PRJID1=$PQUOTA_LIMIT \ + -o projectobjquota@$PRJID2=$PQUOTA_OBJLIMIT $TESTPOOL1/fs + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL1 > /dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL1 "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL1 "$PQUOTA_OBJLIMIT" + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL1 > /dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL1/fs "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL1/fs "$PQUOTA_OBJLIMIT" + +log_pass "The project{obj}quota can be set during zpool,zfs creation" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_003_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_003_pos.ksh new file mode 100644 index 0000000000..a7f89c19c2 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_003_pos.ksh @@ -0,0 +1,99 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the basic function project{obj}used +# +# +# STRATEGY: +# 1. Write data to fs with some project then check the project{obj}used +# + +function cleanup +{ + cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check the basic function of project{obj}used" + +sync_pool +typeset project_used=$(get_value "projectused@$PRJID1" $QFS) +typeset file_size='10m' + +if [[ $project_used -ge 8192 ]]; then + log_fail "FAIL: projectused is $project_used, should be less than 8k" +fi + +mkmount_writable $QFS +log_must user_run $PUSER mkdir $PRJDIR +# log_must chattr +P -p $PRJID1 $PRJDIR +log_must zfs project -s -p $PRJID1 $PRJDIR +log_must user_run $PUSER mkfile $file_size $PRJDIR/qf +sync_pool +project_used=$(get_value "projectused@$PRJID1" $QFS) +# get_value() reads the exact byte value which is slightly more than 10m +if [[ "$(($project_used/1024/1024))m" != "$file_size" ]]; then + log_note "project $PRJID1 used is $project_used" + log_fail "projectused for project $PRJID1 expected to be $file_size, " \ + "not $project_used" +fi + +log_must rm -rf $PRJDIR +typeset project_obj_used=$(get_value "projectobjused@$PRJID2" $QFS) +typeset file_count=100 + +if [[ $project_obj_used -ge 2 ]]; then + log_fail "FAIL: projectobjused is $project_obj_used, should be " \ + "less than 2" +fi + +log_must zfs set xattr=on $QFS +log_must user_run $PUSER mkdir $PRJDIR +# log_must chattr +P -p $PRJID2 $PRJDIR +log_must zfs project -s -p $PRJID2 $PRJDIR +# $PRJDIR has already used one object with the $PRJID2 +log_must user_run $PUSER mkfiles $PRJDIR/qf_ $((file_count - 1)) +sync_pool +project_obj_used=$(get_value "projectobjused@$PRJID2" $QFS) +exp_count=$((file_count + 1)) +if [[ $project_obj_used -gt $exp_count ]]; then + log_note "project $PRJID2 used is $project_obj_used" + log_fail "projectobjused for project $PRJID2 expected to be less than" \ + "$exp_count, not $project_obj_used" +fi + +log_pass "Check the basic function of project{obj}used pass as expected" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_004_neg.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_004_neg.ksh new file mode 100644 index 0000000000..da3b8c1826 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_004_neg.ksh @@ -0,0 +1,87 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the invalid parameter of zfs set project{obj}quota +# +# +# STRATEGY: +# 1. check the invalid zfs set project{obj}quota to fs +# 2. check the valid zfs set project{obj}quota to snapshots +# + +function cleanup +{ + if datasetexists $snap_fs; then + log_must zfs destroy $snap_fs + fi + + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check the invalid parameter of zfs set project{obj}quota" +typeset snap_fs=$QFS@snap + +log_must zfs snapshot $snap_fs + +set -A no_prjs "mms1234" "ss@#" "root-122" "-1" +for prj in "${no_prjs[@]}"; do + log_mustnot zfs set projectquota@$prj=100m $QFS +done + +log_note "can set all numeric id even that id does not exist" +log_must zfs set projectquota@12345678=100m $QFS + +set -A sizes "100mfsd" "m0.12m" "GGM" "-1234-m" "123m-m" +for size in "${sizes[@]}"; do + log_note "can not set projectquota with invalid size parameter" + log_mustnot zfs set projectquota@$PRJID1=$size $QFS +done + +log_note "can not set projectquota to snapshot $snap_fs" +log_mustnot zfs set projectquota@$PRJID1=100m $snap_fs + +for prj in "${no_prjs[@]}"; do + log_mustnot zfs set projectobjquota@$prj=100 $QFS +done + +log_note "can not set projectobjquota with invalid size parameter" +log_mustnot zfs set projectobjquota@$PRJID2=100msfsd $QFS + +log_note "can not set projectobjquota to snapshot $snap_fs" +log_mustnot zfs set projectobjquota@$PRJID2=100m $snap_fs + +log_pass "Check the invalid parameter of zfs set project{obj}quota" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_005_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_005_pos.ksh new file mode 100644 index 0000000000..b52f302f78 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_005_pos.ksh @@ -0,0 +1,68 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the invalid parameter of zfs get project{obj}quota +# +# +# STRATEGY: +# 1. check the invalid zfs get project{obj}quota to fs +# 2. check the valid zfs get project{obj}quota to snapshots +# + +function cleanup +{ + if datasetexists $snap_fs; then + log_must zfs destroy $snap_fs + fi + + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check the invalid parameter of zfs get project{obj}quota" +typeset snap_fs=$QFS@snap + +log_must zfs snapshot $snap_fs + +set -A no_prjs "mms1234" "ss@#" "root-122" +for prj in "${no_prjs[@]}"; do + log_must eval "zfs get projectquota@$prj $QFS >/dev/null 2>&1" + log_must eval "zfs get projectquota@$prj $snap_fs >/dev/null 2>&1" + log_must eval "zfs get projectobjquota@$prj $QFS >/dev/null 2>&1" + log_must eval "zfs get projectobjquota@$prj $snap_fs >/dev/null 2>&1" +done + +log_pass "Check the invalid parameter of zfs get project{obj}quota" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_006_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_006_pos.ksh new file mode 100644 index 0000000000..0b5488bfe1 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_006_pos.ksh @@ -0,0 +1,72 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Projectquota can be set beyond the fs quota. +# Pprojectquota can be set at a smaller size than its current usage. +# +# STRATEGY: +# 1. set quota to a fs and set a larger size of projectquota +# 2. write some data to the fs and set a smaller projectquota +# + +function cleanup +{ + log_must cleanup_projectquota + log_must zfs set quota=none $QFS +} + +log_onexit cleanup + +log_assert "Check set projectquota to larger than the quota size of a fs" + +log_must zfs set quota=200m $QFS +log_must zfs set projectquota@$PRJID1=500m $QFS + +log_must zfs get projectquota@$PRJID1 $QFS + +log_note "write some data to the $QFS" +mkmount_writable $QFS +log_must user_run $PUSER mkdir $PRJDIR +# log_must chattr +P -p $PRJID1 $PRJDIR +log_must zfs project -s -p $PRJID1 $PRJDIR +log_must user_run $PUSER mkfile 100m $PRJDIR/qf +sync + +log_note "set projectquota at a smaller size than its current usage" +log_must zfs set projectquota@$PRJID1=90m $QFS + +log_must zfs get projectquota@$PRJID1 $QFS + +log_pass "set projectquota to larger than quota size of a fs" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_007_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_007_pos.ksh new file mode 100644 index 0000000000..3572e0118f --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_007_pos.ksh @@ -0,0 +1,58 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# zfs get all does not print out project{obj}quota +# +# STRATEGY: +# 1. set project{obj}quota to a fs +# 2. check zfs get all fs +# + +function cleanup +{ + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check zfs get all will not print out project{obj}quota" + +log_must zfs set projectquota@$PRJID1=50m $QFS +log_must zfs set projectobjquota@$PRJID2=100 $QFS + +log_mustnot eval "zfs get all $QFS | grep projectquota" +log_mustnot eval "zfs get all $QFS | grep projectobjquota" + +log_pass "zfs get all will not print out project{obj}quota" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_008_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_008_pos.ksh new file mode 100644 index 0000000000..365b5627e8 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_008_pos.ksh @@ -0,0 +1,91 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check project{obj}quota to snapshot that: +# 1) can not set project{obj}quota to snapshot directly +# 2) snapshot can inherit the parent fs's project{obj}quota +# 3) the project{obj}quota will not change even the parent quota changed. +# +# +# STRATEGY: +# 1. create a snapshot of a fs +# 2. set the project{obj}quota to snapshot and expect fail +# 3. set project{obj}quota to fs and check the snapshot +# 4. re-set project{obj}quota to fs and check the snapshot's value +# + +function cleanup +{ + if datasetexists $snap_fs; then + log_must zfs destroy $snap_fs + fi + + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check the snapshot's project{obj}quota" +typeset snap_fs=$QFS@snap + + +log_must zfs set projectquota@$PRJID1=$PQUOTA_LIMIT $QFS +log_must check_quota "projectquota@$PRJID1" $QFS "$PQUOTA_LIMIT" + +log_must zfs set projectobjquota@$PRJID2=$PQUOTA_OBJLIMIT $QFS +log_must check_quota "projectobjquota@$PRJID2" $QFS "$PQUOTA_OBJLIMIT" + +log_must zfs snapshot $snap_fs + +log_note "check the snapshot $snap_fs project{obj}quota" +log_must check_quota "projectquota@$PRJID1" $snap_fs "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $snap_fs "$PQUOTA_OBJLIMIT" + +log_note "set project{obj}quota to $snap_fs which will fail" +log_mustnot zfs set projectquota@$PRJID1=100m $snap_fs +log_mustnot zfs set projectobjquota@$PRJID2=100 $snap_fs + +log_note "change the parent's project{obj}quota" +log_must zfs set projectquota@$PRJID1=$((PQUOTA_LIMIT * 2)) $QFS +log_must zfs set projectobjquota@$PRJID2=50 $QFS + +log_must check_quota "projectquota@$PRJID1" $QFS $((PQUOTA_LIMIT * 2)) +log_must check_quota "projectobjquota@$PRJID2" $QFS 50 + +log_note "check the snapshot $snap_fs project{obj}quota" +log_must check_quota "projectquota@$PRJID1" $snap_fs "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $snap_fs "$PQUOTA_OBJLIMIT" + +log_pass "Check the snapshot's project{obj}quota" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_009_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_009_pos.ksh new file mode 100644 index 0000000000..a867b538c1 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_009_pos.ksh @@ -0,0 +1,131 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# The project{obj}quota will not change during zfs actions, such as +# snapshot,clone,rename,upgrade,send,receive. +# +# +# STRATEGY: +# 1. Create a pool, and create fs with preset project{obj}quota +# 2. Check set project{obj}quota via zfs snapshot|clone|list -o +# 3. Check the project{obj}quota can not change during zfs +# rename|upgrade|promote +# 4. Check the project{obj}quota can not change during zfs clone +# 5. Check the project{obj}quota can not change during zfs send/receive +# + +function cleanup +{ + for ds in $TESTPOOL/fs $TESTPOOL/fs-rename $TESTPOOL/fs-clone; do + if datasetexists $ds; then + log_must zfs destroy -rRf $ds + fi + done +} + +log_onexit cleanup + +log_assert "the project{obj}quota can't change during zfs actions" + +cleanup + +log_must zfs create -o projectquota@$PRJID1=$PQUOTA_LIMIT \ + -o projectobjquota@$PRJID2=$PQUOTA_OBJLIMIT $TESTPOOL/fs + +log_must zfs snapshot $TESTPOOL/fs@snap +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL >/dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL/fs@snap "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL/fs@snap \ + "$PQUOTA_OBJLIMIT" + + +log_note "clone fs gets its parent's project{obj}quota initially" +log_must zfs clone -o projectquota@$PRJID1=$PQUOTA_LIMIT \ + -o projectobjquota@$PRJID2=$PQUOTA_OBJLIMIT \ + $TESTPOOL/fs@snap $TESTPOOL/fs-clone + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL >/dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL/fs-clone "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL/fs-clone \ + "$PQUOTA_OBJLIMIT" + +log_must eval "zfs list -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL/fs-clone >/dev/null 2>&1" + +log_note "zfs promote can not change the previously set project{obj}quota" +log_must zfs promote $TESTPOOL/fs-clone + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL >/dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL/fs-clone "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL/fs-clone \ + "$PQUOTA_OBJLIMIT" + +log_note "zfs send receive can not change the previously set project{obj}quota" +log_must zfs send $TESTPOOL/fs-clone@snap | zfs receive $TESTPOOL/fs-rev + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL >/dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL/fs-rev "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL/fs-rev \ + "$PQUOTA_OBJLIMIT" + +log_note "zfs rename can not change the previously set project{obj}quota" +log_must zfs rename $TESTPOOL/fs-rev $TESTPOOL/fs-rename + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL >/dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL/fs-rename "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL/fs-rename \ + "$PQUOTA_OBJLIMIT" + +log_note "zfs upgrade can not change the previously set project{obj}quota" +log_must zfs upgrade $TESTPOOL/fs-rename + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL >/dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL/fs-rename "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL/fs-rename \ + "$PQUOTA_OBJLIMIT" + +log_pass "the project{obj}quota can't change during zfs actions" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib new file mode 100644 index 0000000000..23f7c2a506 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib @@ -0,0 +1,101 @@ +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/projectquota/projectquota.cfg + +# +# reset the projectquota and delete temporary files +# +function cleanup_projectquota +{ + if datasetexists $QFS; then + typeset mntp=$(get_prop mountpoint $QFS) + + log_must zfs set projectquota@$PRJID1=none $QFS + log_must zfs set projectobjquota@$PRJID1=none $QFS + log_must zfs set projectquota@$PRJID2=none $QFS + log_must zfs set projectobjquota@$PRJID2=none $QFS + log_must chmod 0755 $mntp + fi + + [[ -f $PRJFILE ]] && log_must rm -f $PRJFILE + [[ -d $PRJDIR ]] && log_must rm -rf $PRJDIR + [[ -d $PRJDIR1 ]] && log_must rm -rf $PRJDIR1 + [[ -d $PRJDIR2 ]] && log_must rm -rf $PRJDIR2 + [[ -d $PRJDIR3 ]] && log_must rm -rf $PRJDIR3 + sync + + return 0 +} + +function mkmount_writable +{ + typeset fs=$1 + typeset mntp=$(get_prop mountpoint $fs) + log_must chmod 0777 $mntp +} + +function check_quota +{ + typeset fs=$2 + typeset prop=$1 + typeset expected=$3 + typeset value=$(get_prop $prop $fs) + + if (($value != $expected)); then + return 1 + fi +} + +function get_value +{ + typeset prop_val + typeset prop=$1 + typeset dataset=$2 + + prop_val=$(zfs get -H -p -o value $prop $dataset 2>/dev/null) + if [[ $? -ne 0 ]]; then + log_note "Unable to get $prop property for dataset $dataset" + return 1 + fi + + echo $prop_val +} + +function project_obj_count +{ + typeset fs=$1 + typeset prj=$2 + typeset cnt=$(zfs projectspace -oname,objused $fs | + awk /$prj/'{print $2}') + [[ "$cnt" == "-" ]] && cnt=0 || true + echo $cnt +} diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_001_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_001_pos.ksh new file mode 100644 index 0000000000..fdbf5b61b4 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_001_pos.ksh @@ -0,0 +1,91 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. Fan rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the zfs projectspace with kinds of parameters +# +# +# STRATEGY: +# 1. set zfs projectspace to a fs +# 2. write some data to the fs with specified project ID +# 3. use zfs projectspace with all possible parameters to check the result +# 4. use zfs projectspace with some bad parameters to check the result +# + +function cleanup +{ + if datasetexists $snap_fs; then + log_must zfs destroy $snap_fs + fi + + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check the zfs projectspace with all possible parameters" + +set -A good_params -- "-H" "-p" "-o type,name,used,quota" "-o name,used,quota" \ + "-o used,quota" "-o objused" "-o quota" "-s type" "-s name" "-s used" \ + "-s quota" "-S type" "-S name" "-S used" "-S quota" + +typeset snap_fs=$QFS@snap + +log_must zfs set projectquota@$PRJID1=100m $QFS +log_must zfs set projectobjquota@$PRJID1=100 $QFS +mkmount_writable $QFS +log_must user_run $PUSER mkdir $PRJDIR +# log_must chattr +P -p $PRJID1 $PRJDIR +log_must zfs project -s -p $PRJID1 $PRJDIR +log_must user_run $PUSER mkfile 50m $PRJDIR/qf +sync + +log_must zfs snapshot $snap_fs + +for param in "${good_params[@]}"; do + log_must eval "zfs projectspace $param $QFS >/dev/null 2>&1" + log_must eval "zfs projectspace $param $snap_fs >/dev/null 2>&1" +done + +log_assert "Check the zfs projectspace with some bad parameters" + +set -A bad_params -- "-i" "-n" "-P" "-t posixuser" + +for param in "${bad_params[@]}"; do + log_mustnot eval "zfs projectspace $param $QFS >/dev/null 2>&1" + log_mustnot eval "zfs projectspace $param $snap_fs >/dev/null 2>&1" +done + +log_pass "zfs projectspace with kinds of parameters pass" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_002_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_002_pos.ksh new file mode 100644 index 0000000000..719740c018 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_002_pos.ksh @@ -0,0 +1,83 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the project used size and quota in zfs projectspace +# +# +# STRATEGY: +# 1. set zfs projectquota to a fs +# 2. write some data to the fs with specified project and size +# 3. use zfs projectspace to check the used size and quota size +# + +function cleanup +{ + if datasetexists $snapfs; then + log_must zfs destroy $snapfs + fi + + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check the zfs projectspace used and quota" + +log_must zfs set projectquota@$PRJID1=100m $QFS + +mkmount_writable $QFS +log_must user_run $PUSER mkdir $PRJDIR +# log_must chattr +P -p $PRJID1 $PRJDIR +log_must zfs project -s -p $PRJID1 $PRJDIR +log_must user_run $PUSER mkfile 50m $PRJDIR/qf +sync + +typeset snapfs=$QFS@snap + +log_must zfs snapshot $snapfs + +log_must eval "zfs projectspace $QFS >/dev/null 2>&1" +log_must eval "zfs projectspace $snapfs >/dev/null 2>&1" + +for fs in "$QFS" "$snapfs"; do + log_note "check the quota size in zfs projectspace $fs" + log_must eval "zfs projectspace $fs | grep $PRJID1 | grep 100M" + + log_note "check the project used size in zfs projectspace $fs" + log_must eval "zfs projectspace $fs | grep $PRJID1 | grep 50\\.\*M" +done + +log_pass "Check the zfs projectspace used and quota" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_003_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_003_pos.ksh new file mode 100644 index 0000000000..c653e6b922 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_003_pos.ksh @@ -0,0 +1,119 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the project used object accounting in zfs projectspace +# +# +# STRATEGY: +# 1. create a bunch of files by specific project +# 2. use zfs projectspace to check the used objects +# 3. change the project ID of test files and verify object count +# 4. delete files and verify object count +# + +function cleanup +{ + if datasetexists $snapfs; then + log_must zfs destroy $snapfs + fi + + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check the zfs projectspace object used" + +mkmount_writable $QFS +log_must zfs set xattr=on $QFS +log_must user_run $PUSER mkdir $PRJDIR1 +log_must user_run $PUSER mkdir $PRJDIR2 +# log_must chattr +P -p $PRJID1 $PRJDIR1 +# log_must chattr +P -p $PRJID2 $PRJDIR2 +log_must zfs project -s -p $PRJID1 $PRJDIR1 +log_must zfs project -s -p $PRJID2 $PRJDIR2 + +((prj_cnt1 = RANDOM % 100 + 2)) +((prj_cnt2 = RANDOM % 100 + 2)) + +log_must user_run $PUSER mkfiles $PRJDIR1/qf $((prj_cnt1 - 1)) +log_must user_run $PUSER mkfiles $PRJDIR2/qf $((prj_cnt2 - 1)) +sync_pool + +typeset snapfs=$QFS@snap + +log_must zfs snapshot $snapfs + +log_must eval "zfs projectspace $QFS >/dev/null 2>&1" +log_must eval "zfs projectspace $snapfs >/dev/null 2>&1" + +for fs in "$QFS" "$snapfs"; do + log_note "check the project used objects in zfs projectspace $fs" + prjused=$(project_obj_count $fs $PRJID1) + [[ $prjused -ge $prj_cnt1 ]] || + log_fail "($PRJID1) expected $prj_cnt1, got $prjused" + prjused=$(project_obj_count $fs $PRJID2) + [[ $prjused -ge $prj_cnt2 ]] || + log_fail "($PRJID2) expected $prj_cnt2, got $prjused" +done + +log_note "change the project of files" +# log_must chattr -p $PRJID2 $PRJDIR1/qf* +log_must zfs project -s -p $PRJID2 $PRJDIR1/qf* +sync_pool + +prjused=$(project_obj_count $QFS $PRJID1) +[[ $prjused -lt 10 ]] || + log_fail "expected less than 10 for project $PRJID1, got $prjused" + +prjused=$(project_obj_count $snapfs $PRJID1) +[[ $prjused -ge $prj_cnt1 ]] || + log_fail "expected $prj_cnt1 for $PRJID1 in snapfs, got $prjused" + +prjused=$(project_obj_count $QFS $PRJID2) +[[ $prjused -ge $((prj_cnt1 + prj_cnt2 - 1)) ]] || + log_fail "($PRJID2) expected $((prj_cnt1 + prj_cnt2 - 1)), got $prjused" + +log_note "file removal" +log_must rm -rf $PRJDIR1 +sync_pool + +prjused=$(project_obj_count $QFS $PRJID1) +[[ $prjused -lt 10 ]] || log_fail "expected less than 10 for $PRJID1, " \ + "got $prjused" + +cleanup +log_pass "Check the zfs projectspace object used" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_004_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_004_pos.ksh new file mode 100644 index 0000000000..9c4d37f0f7 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_004_pos.ksh @@ -0,0 +1,76 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. Fan rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check 'df' command on the directory with INHERIT (project ID) flag +# +# +# STRATEGY: +# 1. set project [obj]quota on the directory +# 2. set project ID and inherit flag on the directoty +# 3. run 'df [-i]' on the directory and check the result +# + +function cleanup +{ + if datasetexists $snap_fs; then + log_must zfs destroy $snap_fs + fi + + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check 'df' on dir with inherit project shows the project quota/used" + +log_must zfs set projectquota@$PRJID1=100m $QFS +log_must zfs set projectobjquota@$PRJID1=100 $QFS +mkmount_writable $QFS +log_must user_run $PUSER mkdir $PRJDIR +# log_must chattr +P -p $PRJID1 $PRJDIR +log_must zfs project -s -p $PRJID1 $PRJDIR +log_must user_run $PUSER mkfile 50m $PRJDIR/qf +sync_pool + +total=$(df -b $PRJDIR | tail -n 1 | awk '{ print $2 }') +[[ $total -ge 9590000 && $total -le 9598900 ]] || \ + log_fail "expect '9590000-9598900' resource, but got '$total'" + +# -i invalid on illumos +# used=$(df -i $PRJDIR | tail -n 1 | awk '{ print $5 }') +# [[ "$used" == "2%" ]] || log_fail "expect '2%' used, but got '$used'" + +log_pass "'df' on the directory with inherit project ID flag pass as expect" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_001_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_001_pos.ksh new file mode 100644 index 0000000000..0402e345df --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_001_pos.ksh @@ -0,0 +1,108 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# +# DESCRIPTION: +# Check 'zfs project' is compatible with chattr/lsattr +# +# +# STRATEGY: +# Verify the following: +# 1. "zfs project -p" behaviours the same as "chattr -p" +# 2. "zfs project" behaviours the same as "lsattr -p" +# 3. "zfs project -d" behaviours the same as "lsattr -p -d" +# 4. "zfs project -s" behaviours the same as "chattr +P" +# 5. "zfs project -s -p" behaviours the same as "chattr +P -p" +# 6. "zfs project -C" behaviours the same as "chattr -P" +# + +function cleanup +{ + log_must rm -rf $PRJDIR +} + +if ! lsattr -pd > /dev/null 2>&1; then + log_unsupported "Current e2fsprogs does not support set/show project ID" +fi + +# +# e2fsprogs-1.44.4 incorrectly reports verity 'V' bit when the project 'P' +# bit is set. Skip this test when 1.44.4 is installed to prevent failures. +# +# https://github.com/tytso/e2fsprogs/commit/7e5a95e3d +# +if lsattr -V 2>&1 | grep "lsattr 1.44.4"; then + log_unsupported "Current e2fsprogs incorrectly reports 'V' verity bit" +fi + +log_onexit cleanup + +log_assert "Check 'zfs project' is compatible with chattr/lsattr" + +log_must mkdir $PRJDIR +log_must mkdir $PRJDIR/a1 +log_must mkdir $PRJDIR/a2 +log_must touch $PRJDIR/a3 + +log_must chattr -p $PRJID1 $PRJDIR/a3 +log_must eval "zfs project $PRJDIR/a3 | grep '$PRJID1 \-'" + +log_must zfs project -p $PRJID2 $PRJDIR/a3 +log_must eval "lsattr -p $PRJDIR/a3 | grep $PRJID2 | grep -v '\-P[- ]* '" + +log_must chattr -p $PRJID1 $PRJDIR/a1 +log_must eval "zfs project -d $PRJDIR/a1 | grep '$PRJID1 \-'" + +log_must zfs project -p $PRJID2 $PRJDIR/a1 +log_must eval "lsattr -pd $PRJDIR/a1 | grep $PRJID2 | grep -v '\-P[- ]* '" + +log_must chattr +P $PRJDIR/a2 +log_must eval "zfs project -d $PRJDIR/a2 | grep '0 P'" + +log_must zfs project -s $PRJDIR/a2 +log_must eval "lsattr -pd $PRJDIR/a2 | grep 0 | grep '\-P[- ]* '" + +log_must chattr +P -p $PRJID1 $PRJDIR/a1 +log_must eval "zfs project -d $PRJDIR/a1 | grep '$PRJID1 P'" + +log_must zfs project -s -p $PRJID2 $PRJDIR/a2 +log_must eval "lsattr -pd $PRJDIR/a2 | grep $PRJID2 | grep '\-P[- ]* '" + +log_must chattr -P $PRJDIR/a1 +log_must eval "zfs project -d $PRJDIR/a1 | grep '$PRJID1 \-'" + +log_must zfs project -C -k $PRJDIR/a2 +log_must eval "lsattr -pd $PRJDIR/a2 | grep $PRJID2 | grep -v '\-P[- ]* '" + +log_pass "Check 'zfs project' is compatible with chattr/lsattr" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_002_pos.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_002_pos.ksh new file mode 100644 index 0000000000..d610192427 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_002_pos.ksh @@ -0,0 +1,120 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# +# DESCRIPTION: +# Check project ID/flag can be operated via "zfs project" +# +# +# STRATEGY: +# 1. Create a tree with 4 level directories. +# 2. Set project ID on both directory and regular file via +# "zfs project -p". +# 3. Check the project ID via "zfs project". +# 4. Set project inherit flag on kinds of level directories (and its +# descendants for some)) via "zfs project -s [-r]". +# 5. Check the project ID and inherit flag via "zfs project -r". +# 6. Clear the project inherit flag from some directories (and its +# descendants for some) via "zfs project -C [-r]". +# 7. Check the project ID and inherit flag via "zfs project -r". +# + +function cleanup +{ + log_must rm -rf $PRJDIR +} + +log_onexit cleanup + +log_assert "Check project ID/flag can be operated via 'zfs project'" + +log_must mkdir $PRJDIR + +log_must mkdir $PRJDIR/a1 +log_must mkdir $PRJDIR/b1 +log_must touch $PRJDIR/c1 + +log_must mkdir $PRJDIR/a1/a2 +log_must mkdir $PRJDIR/a1/b2 +log_must touch $PRJDIR/a1/c2 + +log_must mkdir $PRJDIR/b1/a2 +log_must mkdir $PRJDIR/b1/b2 +log_must touch $PRJDIR/b1/c2 + +log_must mkdir $PRJDIR/a1/a2/a3 +log_must mkdir $PRJDIR/a1/a2/b3 +log_must touch $PRJDIR/a1/a2/c3 + +log_must mkdir $PRJDIR/b1/a2/a3 + +log_must touch $PRJDIR/a1/a2/a3/c4 +log_must touch $PRJDIR/a1/a2/a3/d4 + +log_must zfs project -p $PRJID1 $PRJDIR/a1/c2 +log_must eval "zfs project $PRJDIR/a1/c2 | grep $PRJID1" + +log_must zfs project -p $PRJID2 $PRJDIR/a1/a2/a3 +log_must eval "zfs project -d $PRJDIR/a1/a2/a3 | grep $PRJID2" + +log_must zfs project -s $PRJDIR/b1/a2 +log_must eval "zfs project -d $PRJDIR/b1/a2 | grep ' P '" +log_must eval "zfs project -d $PRJDIR/b1/a2/a3 | grep ' \- '" + +log_must zfs project -s -r -p $PRJID2 $PRJDIR/a1/a2 +log_must zfs project -c -r $PRJDIR/a1/a2 +log_must eval "zfs project -d $PRJDIR/a1/a2/a3 | grep ' P '" +log_must eval "zfs project $PRJDIR/a1/a2/a3/c4 | grep $PRJID2" + +log_must zfs project -C $PRJDIR/a1/a2/a3 +log_must eval "zfs project -cr $PRJDIR/a1/a2 | grep 'inherit flag is not set'" +log_must eval "zfs project $PRJDIR/a1/a2/a3/c4 | grep $PRJID2 | grep -v not" +log_must zfs project -p 123 $PRJDIR/a1/a2/a3/c4 +log_must eval "zfs project -c -r $PRJDIR/a1/a2 | grep 123 | grep 'not set'" +log_mustnot eval "zfs project -cr -p 123 $PRJDIR/a1/a2 | grep c4 | grep -v not" + +log_must zfs project -C -r $PRJDIR/a1/a2/a3 +log_must eval "zfs project -cr $PRJDIR/a1/a2 | grep a3 | grep 'not set'" +log_must eval "zfs project -cr $PRJDIR/a1/a2 | grep d4 | grep 'not set'" +log_must eval "zfs project $PRJDIR/a1/a2/a3/d4 | grep '0 \-'" + +log_must eval \ + "zfs project -cr -0 $PRJDIR/a1/a2 | xargs -0 zfs project -s -p $PRJID2" +log_mustnot eval "zfs project -cr $PRJDIR/a1/a2 | grep a3 | grep 'not set'" +log_mustnot eval "zfs project -cr $PRJDIR/a1/a2 | grep d4 | grep 'not set'" + +log_must zfs project -C -r -k $PRJDIR/a1/a2 +log_must eval "zfs project -d $PRJDIR/a1/a2/b3 | grep '$PRJID2 \- '" + +log_pass "Check project ID/flag can be operated via 'zfs project'" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_003_neg.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_003_neg.ksh new file mode 100644 index 0000000000..33382fdbe9 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_003_neg.ksh @@ -0,0 +1,103 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# +# DESCRIPTION: +# Check 'zfs project' invalid options combinations +# +# +# STRATEGY: +# Verify the following: +# 1. "-c" only supports "-d", "-p", "-r" and "-0". +# 2. "-C" only supports "-r" and "-k". +# 3. "-s" only supports "-r" and "-p". +# 4. "-c", "-C" and "-s" can NOT be specified together. +# 5. "-d" can overwirte former "-r". +# 6. "-r" can overwirte former "-d". +# 7. "-0" must be together with "-c". +# 8. "-d" must be on directory. +# 9. "-r" must be on directory. +# 10. "-p" must be together with "-c -r" or "-s". +# + +function cleanup +{ + log_must rm -rf $PRJDIR +} + +log_onexit cleanup + +log_assert "Check 'zfs project' invalid options combinations" + +log_must mkdir $PRJDIR +log_must mkdir $PRJDIR/a1 +log_must touch $PRJDIR/a2 + +log_mustnot zfs project -c +log_mustnot zfs project -c -k $PRJDIR/a1 +log_mustnot zfs project -c -C $PRJDIR/a1 +log_mustnot zfs project -c -s $PRJDIR/a1 +log_must zfs project -c -d -r $PRJDIR/a1 +log_must zfs project -c -r -d $PRJDIR/a1 +log_mustnot zfs project -c -d $PRJDIR/a2 +log_mustnot zfs project -c -r $PRJDIR/a2 + +log_mustnot zfs project -C +log_mustnot zfs project -C -c $PRJDIR/a1 +log_mustnot zfs project -C -d $PRJDIR/a1 +log_mustnot zfs project -C -p 100 $PRJDIR/a1 +log_mustnot zfs project -C -s $PRJDIR/a1 +log_mustnot zfs project -C -r -0 $PRJDIR/a1 +log_mustnot zfs project -C -0 $PRJDIR/a1 + +log_mustnot zfs project -s +log_mustnot zfs project -s -d $PRJDIR/a1 +log_mustnot zfs project -s -k $PRJDIR/a1 +log_mustnot zfs project -s -r -0 $PRJDIR/a1 +log_mustnot zfs project -s -0 $PRJDIR/a1 +log_mustnot zfs project -s -r $PRJDIR/a2 + +log_mustnot zfs project -p 100 +log_mustnot zfs project -p -1 $PRJDIR/a2 +log_mustnot zfs project -p 100 -d $PRJDIR/a1 +log_mustnot zfs project -p 100 -k $PRJDIR/a1 +log_mustnot zfs project -p 100 -0 $PRJDIR/a1 +log_mustnot zfs project -p 100 -r -0 $PRJDIR/a1 + +log_mustnot zfs project +log_mustnot zfs project -0 $PRJDIR/a2 +log_mustnot zfs project -k $PRJDIR/a2 +log_mustnot zfs project -S $PRJDIR/a1 + +log_pass "Check 'zfs project' invalid options combinations" diff --git a/usr/src/test/zfs-tests/tests/functional/projectquota/setup.ksh b/usr/src/test/zfs-tests/tests/functional/projectquota/setup.ksh new file mode 100755 index 0000000000..d16d27fb51 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/projectquota/setup.ksh @@ -0,0 +1,56 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +verify_runnable "both" + +del_user $PUSER +del_group $PGROUP +log_must add_group $PGROUP +log_must add_user $PGROUP $PUSER + +# +# Verify the test user can execute the zfs utilities. This may not +# be possible due to default permissions on the user home directory. +# This can be resolved by granting group read access. +# +# chmod 0750 $HOME +# +user_run $PUSER zfs list +if [ $? -ne 0 ]; then + log_unsupported "Test user $PUSER cannot execute zfs utilities" +fi + +DISK=${DISKS%% *} +default_setup_noexit $DISK + +log_pass diff --git a/usr/src/test/zfs-tests/tests/functional/upgrade/Makefile b/usr/src/test/zfs-tests/tests/functional/upgrade/Makefile new file mode 100644 index 0000000000..5af265d505 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/upgrade/Makefile @@ -0,0 +1,21 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2013, 2016 by Delphix. All rights reserved. +# + +include $(SRC)/Makefile.master + +ROOTOPTPKG = $(ROOT)/opt/zfs-tests +TARGETDIR = $(ROOTOPTPKG)/tests/functional/upgrade + +include $(SRC)/test/zfs-tests/Makefile.com diff --git a/usr/src/test/zfs-tests/tests/functional/upgrade/cleanup.ksh b/usr/src/test/zfs-tests/tests/functional/upgrade/cleanup.ksh new file mode 100644 index 0000000000..1f0c9b63d9 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/upgrade/cleanup.ksh @@ -0,0 +1,42 @@ +#!/bin/ksh -p +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2013 by Delphix. All rights reserved. +# + +# +# Copyright (c) 2016 by Jinshan Xiong. No rights reserved. +# + +. $STF_SUITE/tests/functional/upgrade/upgrade_common.kshlib + +verify_runnable "global" + +log_must rm -f $TMPDEV + +default_cleanup diff --git a/usr/src/test/zfs-tests/tests/functional/upgrade/setup.ksh b/usr/src/test/zfs-tests/tests/functional/upgrade/setup.ksh new file mode 100644 index 0000000000..c25d25df6b --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/upgrade/setup.ksh @@ -0,0 +1,43 @@ +#!/bin/ksh -p +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2013 by Delphix. All rights reserved. +# + +# +# Copyright (c) 2016 by Jinshan Xiong. No rights reserved. +# + +. $STF_SUITE/tests/functional/upgrade/upgrade_common.kshlib + +verify_runnable "global" + +# create a pool without any features +log_must mkfile 128m $TMPDEV + +log_pass diff --git a/usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_common.kshlib b/usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_common.kshlib new file mode 100644 index 0000000000..679ff30492 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_common.kshlib @@ -0,0 +1,41 @@ +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib + +export TMPDEV=$TEST_BASE_DIR/zpool_upgrade_test.dat + +function cleanup_upgrade +{ + datasetexists $TESTPOOL/fs1 && log_must zfs destroy $TESTPOOL/fs1 + datasetexists $TESTPOOL/fs2 && log_must zfs destroy $TESTPOOL/fs2 + datasetexists $TESTPOOL/fs3 && log_must zfs destroy $TESTPOOL/fs3 + datasetexists $TESTPOOL && log_must zpool destroy $TESTPOOL +} diff --git a/usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_projectquota_001_pos.ksh b/usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_projectquota_001_pos.ksh new file mode 100644 index 0000000000..1dc5003c58 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_projectquota_001_pos.ksh @@ -0,0 +1,125 @@ +#!/bin/ksh -p +# +# 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) 2017 by Fan Yong. All rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/tests/functional/upgrade/upgrade_common.kshlib + +# +# DESCRIPTION: +# +# Check whether zfs upgrade for project quota works or not. +# The project quota is per dataset based feature. This test +# will create multiple datasets and try different upgrade methods. +# +# STRATEGY: +# 1. Create a pool with all features disabled +# 2. Create a few dataset for testing +# 3. Make sure automatic upgrade work +# 4. Make sure manual upgrade work +# + +verify_runnable "global" + +log_assert "pool upgrade for projectquota should work" +log_onexit cleanup_upgrade + +log_must zpool create -d -m $TESTDIR $TESTPOOL $TMPDEV + +log_must mkfiles $TESTDIR/tf $((RANDOM % 100 + 1)) +log_must zfs create $TESTPOOL/fs1 +log_must mkfiles $TESTDIR/fs1/tf $((RANDOM % 100 + 1)) +log_must zfs umount $TESTPOOL/fs1 + +log_must zfs create $TESTPOOL/fs2 +log_must mkdir $TESTDIR/fs2/dir +log_must mkfiles $TESTDIR/fs2/tf $((RANDOM % 100 + 1)) + +log_must zfs create $TESTPOOL/fs3 +log_must mkdir $TESTDIR/fs3/dir +log_must mkfiles $TESTDIR/fs3/tf $((RANDOM % 100 + 1)) + +# Make sure project quota is disabled +zfs projectspace -o used $TESTPOOL | grep -q "USED" && + log_fail "project quota should be disabled initially" + +# set projectquota before upgrade will fail +log_mustnot zfs set projectquota@100=100m $TESTDIR/fs3 + +# set projectobjquota before upgrade will fail +log_mustnot zfs set projectobjquota@100=1000 $TESTDIR/fs3 + +# setting a project should fail before upgrade +# log_mustnot chattr -p 100 $TESTDIR/fs3/dir +log_mustnot zfs project -s -p 100 $TESTDIR/fs3/dir + +# Upgrade zpool to support all features +log_must zpool upgrade $TESTPOOL + +# Double check project quota is disabled +zfs projectspace -o used $TESTPOOL | grep -q "USED" && + log_fail "project quota should be disabled after pool upgrade" + +# Mount dataset should trigger upgrade +log_must zfs mount $TESTPOOL/fs1 +log_must sleep 3 # upgrade done in the background so let's wait for a while +zfs projectspace -o used $TESTPOOL/fs1 | grep -q "USED" || + log_fail "project quota should be enabled for $TESTPOOL/fs1" + +# Create file should trigger dataset upgrade +log_must mkfile 1m $TESTDIR/fs2/dir/tf +log_must sleep 3 # upgrade done in the background so let's wait for a while +zfs projectspace -o used $TESTPOOL/fs2 | grep -q "USED" || + log_fail "project quota should be enabled for $TESTPOOL/fs2" + +# reading projects should NOT trigger upgrade +# log_must lsattr -p -d $TESTDIR/fs3/dir +log_must eval "zfs project $TESTDIR/fs3/dir" +zfs projectspace -o used $TESTPOOL/fs3 | grep -q "USED" && + log_fail "project quota should not active for $TESTPOOL/fs3" + +# setting a project should trigger dataset upgrade +# log_must chattr -p 100 $TESTDIR/fs3/dir +log_must zfs project -s -p 100 $TESTDIR/fs3/dir +log_must sleep 5 # upgrade done in the background so let's wait for a while +zfs projectspace -o used $TESTPOOL/fs3 | grep -q "USED" || + log_fail "project quota should be enabled for $TESTPOOL/fs3" +cnt=$(zfs get -H projectobjused@100 $TESTPOOL/fs3 | awk '{print $3}') +# if 'xattr=on', then 'cnt = 2' +[[ $cnt -ne 1 ]] && [[ $cnt -ne 2 ]] && + log_fail "projectquota accounting failed $cnt" + +# All in all, after having been through this, the dataset for testpool +# still shouldn't be upgraded +zfs projectspace -o used $TESTPOOL | grep -q "USED" && + log_fail "project quota should be disabled for $TESTPOOL" + +# Manual upgrade root dataset +# uses an ioctl which will wait for the upgrade to be done before returning +log_must zfs set version=current $TESTPOOL +zfs projectspace -o used $TESTPOOL | grep -q "USED" || + log_fail "project quota should be enabled for $TESTPOOL" + +log_pass "Project Quota upgrade done" diff --git a/usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_userobj_001_pos.ksh b/usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_userobj_001_pos.ksh new file mode 100644 index 0000000000..250ed95893 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_userobj_001_pos.ksh @@ -0,0 +1,95 @@ +#!/bin/ksh -p +# +# 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) 2013 by Jinshan Xiong. No rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/tests/functional/upgrade/upgrade_common.kshlib + +# +# DESCRIPTION: +# +# Check that zfs upgrade for object count accounting works. +# Since userobjaccounting is a per dataset feature, this test case +# will create multiple dataset and try different upgrade method. +# +# STRATEGY: +# 1. Create a pool with all features disabled +# 2. Create a few dataset for testing +# 3. Make sure automatic upgrade work +# 4. Make sure manual upgrade work +# + +verify_runnable "global" + +log_assert "pool upgrade for userobj accounting should work" +log_onexit cleanup_upgrade + +log_must zpool create -d -m $TESTDIR $TESTPOOL $TMPDEV + +log_must mkfiles $TESTDIR/tf $((RANDOM % 1000 + 1)) +log_must zfs create $TESTPOOL/fs1 +log_must mkfiles $TESTDIR/fs1/tf $((RANDOM % 1000 + 1)) +log_must zfs create $TESTPOOL/fs2 +log_must mkfiles $TESTDIR/fs2/tf $((RANDOM % 1000 + 1)) +log_must zfs umount $TESTPOOL/fs2 + +# Make sure userobj accounting is disabled +zfs userspace -o objused -H $TESTPOOL | head -n 1 | grep -q "-" || + log_fail "userobj accounting should be disabled initially" + +# Upgrade zpool to support all features +log_must zpool upgrade $TESTPOOL + +# Make sure userobj accounting is disabled again +zfs userspace -o objused -H $TESTPOOL | head -n 1 | grep -q "-" || + log_fail "userobj accounting should be disabled after pool upgrade" + +# Create a file in fs1 should trigger dataset upgrade +log_must mkfile 1m $TESTDIR/fs1/tf +sync_pool + +# Make sure userobj accounting is working for fs1 +zfs userspace -o objused -H $TESTPOOL/fs1 | head -n 1 | grep -q "-" && + log_fail "userobj accounting should be enabled for $TESTPOOL/fs1" + +# Mount a dataset should trigger upgrade +log_must zfs mount $TESTPOOL/fs2 +sync_pool + +# Make sure userobj accounting is working for fs2 +zfs userspace -o objused -H $TESTPOOL/fs2 | head -n 1 | grep -q "-" && + log_fail "userobj accounting should be enabled for $TESTPOOL/fs2" + +# All in all, after having been through this, the dataset for testpool +# still shouldn't be upgraded +zfs userspace -o objused -H $TESTPOOL | head -n 1 | grep -q "-" || + log_fail "userobj accounting should be disabled for $TESTPOOL" + +# Manual upgrade root dataset +log_must zfs set version=current $TESTPOOL +zfs userspace -o objused -H $TESTPOOL | head -n 1 | grep -q "-" && + log_fail "userobj accounting should be enabled for $TESTPOOL" + +log_pass "all tests passed - what a lucky day!" diff --git a/usr/src/test/zfs-tests/tests/functional/userquota/groupspace_003_pos.ksh b/usr/src/test/zfs-tests/tests/functional/userquota/groupspace_003_pos.ksh new file mode 100644 index 0000000000..f4c5002675 --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/userquota/groupspace_003_pos.ksh @@ -0,0 +1,104 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2013 by Delphix. All rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib + +# +# DESCRIPTION: +# Check the user used and groupspace object counts in zfs groupspace +# +# +# STRATEGY: +# 1. set zfs groupquota to a fs +# 2. create objects for different users in the same group +# 3. use zfs groupspace to check the object count +# + +function cleanup +{ + if datasetexists $snapfs; then + log_must zfs destroy $snapfs + fi + + log_must rm -f ${QFILE}_* + log_must cleanup_quota +} + +function group_object_count +{ + typeset fs=$1 + typeset user=$2 + typeset cnt=$(zfs groupspace -oname,objused $fs | grep $user | + awk '{print $2}') + echo $cnt +} + +log_onexit cleanup + +log_assert "Check the zfs groupspace object used" + +mkmount_writable $QFS +log_must zfs set xattr=on $QFS + +((user1_cnt = RANDOM % 100 + 1)) +((user2_cnt = RANDOM % 100 + 1)) +log_must user_run $QUSER1 mkfiles ${QFILE}_1 $user1_cnt +log_must user_run $QUSER2 mkfiles ${QFILE}_2 $user2_cnt +((grp_cnt = user1_cnt + user2_cnt)) +sync_pool + +typeset snapfs=$QFS@snap + +log_must zfs snapshot $snapfs + +log_must eval "zfs groupspace $QFS >/dev/null 2>&1" +log_must eval "zfs groupspace $snapfs >/dev/null 2>&1" + +for fs in "$QFS" "$snapfs"; do + log_note "check the object count in zfs groupspace $fs" + [[ $(group_object_count $fs $QGROUP) -eq $grp_cnt ]] || + log_fail "expected $grp_cnt" +done + +log_note "file removal" +log_must rm ${QFILE}_* +sync_pool + +[[ $(group_object_count $QFS $QGROUP) -eq 0 ]] || + log_fail "expected 0 files for $QGROUP" + +[[ $(group_object_count $snapfs $QGROUP) -eq $grp_cnt ]] || + log_fail "expected $grp_cnt files for $QGROUP" + +cleanup +log_pass "Check the zfs groupspace object used pass as expected" diff --git a/usr/src/test/zfs-tests/tests/functional/userquota/userquota_001_pos.ksh b/usr/src/test/zfs-tests/tests/functional/userquota/userquota_001_pos.ksh index 65ba2e0e62..acc78d23be 100644 --- a/usr/src/test/zfs-tests/tests/functional/userquota/userquota_001_pos.ksh +++ b/usr/src/test/zfs-tests/tests/functional/userquota/userquota_001_pos.ksh @@ -58,7 +58,7 @@ mkmount_writable $QFS log_note "Check the userquota@$QUSER1" log_must zfs set userquota@$QUSER1=$UQUOTA_SIZE $QFS log_must user_run $QUSER1 mkfile $UQUOTA_SIZE $QFILE -sync +sync_pool log_mustnot user_run $QUSER1 mkfile 1 $OFILE cleanup_quota @@ -66,7 +66,7 @@ log_note "Check the groupquota@$QGROUP" log_must zfs set groupquota@$QGROUP=$GQUOTA_SIZE $QFS mkmount_writable $QFS log_must user_run $QUSER1 mkfile $GQUOTA_SIZE $QFILE -sync +sync_pool log_mustnot user_run $QUSER1 mkfile 1 $OFILE cleanup_quota diff --git a/usr/src/test/zfs-tests/tests/functional/userquota/userquota_004_pos.ksh b/usr/src/test/zfs-tests/tests/functional/userquota/userquota_004_pos.ksh index ce5ed720cc..a7b2ab17f1 100644 --- a/usr/src/test/zfs-tests/tests/functional/userquota/userquota_004_pos.ksh +++ b/usr/src/test/zfs-tests/tests/functional/userquota/userquota_004_pos.ksh @@ -27,6 +27,7 @@ # # Copyright (c) 2013, 2016 by Delphix. All rights reserved. +$ Copyright 2019 Joyent, Inc. # . $STF_SUITE/include/libtest.shlib @@ -50,26 +51,28 @@ log_onexit cleanup log_assert "Check the basic function of {user|group} used" +sync_pool typeset user_used=$(get_value "userused@$QUSER1" $QFS) typeset group_used=$(get_value "groupused@$QGROUP" $QFS) -if [[ $user_used != 0 ]]; then - log_fail "FAIL: userused is $user_used, should be 0" +if [[ $user_used != "none" ]]; then + log_fail "FAIL: userused is $user_used, should be none" fi -if [[ $group_used != 0 ]]; then - log_fail "FAIL: groupused is $group_used, should be 0" +if [[ $group_used != "none" ]]; then + log_fail "FAIL: groupused is $group_used, should be none" fi mkmount_writable $QFS log_must user_run $QUSER1 mkfile 100m $QFILE -sync +sync_pool user_used=$(get_value "userused@$QUSER1" $QFS) group_used=$(get_value "groupused@$QGROUP" $QFS) if [[ $user_used != "100M" ]]; then log_note "user $QUSER1 used is $user_used" - log_fail "userused for user $QUSER1 expected to be 50.0M, not $user_used" + log_fail "userused for user $QUSER1 expected to be 50.0M, " \ + "not $user_used" fi if [[ $user_used != $group_used ]]; then diff --git a/usr/src/test/zfs-tests/tests/functional/userquota/userquota_010_pos.ksh b/usr/src/test/zfs-tests/tests/functional/userquota/userquota_010_pos.ksh index 09cb833551..9968ed945f 100644 --- a/usr/src/test/zfs-tests/tests/functional/userquota/userquota_010_pos.ksh +++ b/usr/src/test/zfs-tests/tests/functional/userquota/userquota_010_pos.ksh @@ -57,7 +57,7 @@ log_must zfs set groupquota@$QGROUP=$GQUOTA_SIZE $QFS mkmount_writable $QFS log_must user_run $QUSER1 mkfile $UQUOTA_SIZE $QFILE -sync +sync_pool log_must eval "zfs get -p userused@$QUSER1 $QFS >/dev/null 2>&1" log_must eval "zfs get -p groupused@$GROUPUSED $QFS >/dev/null 2>&1" diff --git a/usr/src/test/zfs-tests/tests/functional/userquota/userquota_013_pos.ksh b/usr/src/test/zfs-tests/tests/functional/userquota/userquota_013_pos.ksh new file mode 100644 index 0000000000..bdb0bafaab --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/userquota/userquota_013_pos.ksh @@ -0,0 +1,79 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2016 by Jinshan Xiong. All rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib + +# +# +# DESCRIPTION: +# Check the basic function of the userobjquota and groupobjquota +# +# +# STRATEGY: +# 1. Set userobjquota and overwrite the quota size +# 2. Creating new object should fail with Disc quota exceeded +# 3. Set groupobjquota and overwrite the quota size +# 4. Creating new object should fail with Disc quota exceeded +# +# + +function cleanup +{ + log_must rm -f ${QFILE}_* + cleanup_quota +} + +log_onexit cleanup + +log_assert "If creating object exceeds {user|group}objquota count, it will fail" + +mkmount_writable $QFS +log_must zfs set xattr=on $QFS + +log_note "Check the userobjquota@$QUSER1" +log_must zfs set userobjquota@$QUSER1=100 $QFS +log_must user_run $QUSER1 mkfiles ${QFILE}_1 100 +sync_pool +log_mustnot user_run $QUSER1 mkfile 1 $OFILE +cleanup_quota + +log_note "Check the groupobjquota@$QGROUP" +log_must zfs set groupobjquota@$QGROUP=200 $QFS +mkmount_writable $QFS +log_must user_run $QUSER1 mkfiles ${QFILE}_2 100 +sync_pool +log_mustnot user_run $QUSER2 mkfile 1 $OFILE + +cleanup +log_pass "Creating objects exceeds {user|group}objquota count, passes as" \ + "expected" diff --git a/usr/src/test/zfs-tests/tests/functional/userquota/userquota_common.kshlib b/usr/src/test/zfs-tests/tests/functional/userquota/userquota_common.kshlib index c38308c4d1..931fb61004 100644 --- a/usr/src/test/zfs-tests/tests/functional/userquota/userquota_common.kshlib +++ b/usr/src/test/zfs-tests/tests/functional/userquota/userquota_common.kshlib @@ -26,6 +26,7 @@ # # Copyright (c) 2013, 2016 by Delphix. All rights reserved. +# Copyright 2019 Joyent, Inc. # . $STF_SUITE/include/libtest.shlib @@ -38,8 +39,11 @@ function cleanup_quota { if datasetexists $QFS; then log_must zfs set userquota@$QUSER1=none $QFS + log_must zfs set userobjquota@$QUSER1=none $QFS log_must zfs set userquota@$QUSER2=none $QFS + log_must zfs set userobjquota@$QUSER2=none $QFS log_must zfs set groupquota@$QGROUP=none $QFS + log_must zfs set groupobjquota@$QGROUP=none $QFS recovery_writable $QFS fi @@ -47,7 +51,7 @@ function cleanup_quota [[ -f $OFILE ]] && log_must rm -f $OFILE sync - return 0 + return 0 } # diff --git a/usr/src/test/zfs-tests/tests/functional/userquota/userspace_002_pos.ksh b/usr/src/test/zfs-tests/tests/functional/userquota/userspace_002_pos.ksh index b6f8425179..032115feff 100644 --- a/usr/src/test/zfs-tests/tests/functional/userquota/userspace_002_pos.ksh +++ b/usr/src/test/zfs-tests/tests/functional/userquota/userspace_002_pos.ksh @@ -27,6 +27,7 @@ # # Copyright (c) 2013, 2016 by Delphix. All rights reserved. +# Copyright 2019 Joyent, Inc. # . $STF_SUITE/include/libtest.shlib @@ -75,7 +76,7 @@ for fs in "$QFS" "$snapfs"; do log_must eval "zfs userspace $fs | grep $QUSER1 | grep 100M" log_note "check the user used size in zfs userspace $fs" - log_must eval "zfs userspace $fs | grep $QUSER1 | grep 50.0M" + log_must eval "zfs userspace $fs | grep $QUSER1 | grep 50\\.\*M" done log_pass "Check the zfs userspace used and quota" diff --git a/usr/src/test/zfs-tests/tests/functional/userquota/userspace_003_pos.ksh b/usr/src/test/zfs-tests/tests/functional/userquota/userspace_003_pos.ksh new file mode 100644 index 0000000000..5c4454642d --- /dev/null +++ b/usr/src/test/zfs-tests/tests/functional/userquota/userspace_003_pos.ksh @@ -0,0 +1,117 @@ +#!/bin/ksh -p +# +# 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 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2016 by Jinshan Xiong. All rights reserved. +# Copyright 2019 Joyent, Inc. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib + +# +# DESCRIPTION: +# Check the user used object accounting in zfs userspace +# +# +# STRATEGY: +# 1. create a bunch of files by specific users +# 2. use zfs userspace to check the used objects +# 3. change the owner of test files and verify object count +# 4. delete files and verify object count +# + +function cleanup +{ + if datasetexists $snapfs; then + log_must zfs destroy $snapfs + fi + + log_must rm -f ${QFILE}_* + log_must cleanup_quota +} + +function user_object_count +{ + typeset fs=$1 + typeset user=$2 + typeset cnt=$(zfs userspace -oname,objused $fs | + awk /$user/'{print $2}') + echo $cnt +} + +log_onexit cleanup + +log_assert "Check the zfs userspace object used" + +mkmount_writable $QFS +log_must zfs set xattr=on $QFS + +((user1_cnt = RANDOM % 100 + 1)) +((user2_cnt = RANDOM % 100 + 1)) + +log_must user_run $QUSER1 mkfiles ${QFILE}_1 $user1_cnt +log_must user_run $QUSER2 mkfiles ${QFILE}_2 $user2_cnt +sync_pool + +typeset snapfs=$QFS@snap + +log_must zfs snapshot $snapfs + +log_must eval "zfs userspace $QFS >/dev/null 2>&1" +log_must eval "zfs userspace $snapfs >/dev/null 2>&1" + +for fs in "$QFS" "$snapfs"; do + log_note "check the user used objects in zfs userspace $fs" + [[ $(user_object_count $fs $QUSER1) -eq $user1_cnt ]] || + log_fail "expected $user1_cnt" + [[ $(user_object_count $fs $QUSER2) -eq $user2_cnt ]] || + log_fail "expected $user2_cnt" +done + +log_note "change the owner of files" +log_must chown $QUSER2 ${QFILE}_1* +sync_pool + +[[ $(user_object_count $QFS $QUSER1) -eq 0 ]] || + log_fail "expected 0 files for $QUSER1" + +[[ $(user_object_count $snapfs $QUSER1) -eq $user1_cnt ]] || + log_fail "expected $user_cnt files for $QUSER1 in snapfs" + +[[ $(user_object_count $QFS $QUSER2) -eq $((user1_cnt+user2_cnt)) ]] || + log_fail "expected $((user1_cnt+user2_cnt)) files for $QUSER2" + +log_note "file removal" +log_must rm ${QFILE}_* +sync_pool + +[[ $(user_object_count $QFS $QUSER2) -eq 0 ]] || + log_fail "expected 0 files for $QUSER2" + +cleanup +log_pass "Check the zfs userspace object used" diff --git a/usr/src/uts/common/fs/zfs/dbuf.c b/usr/src/uts/common/fs/zfs/dbuf.c index 2b37b177d7..cd629d830f 100644 --- a/usr/src/uts/common/fs/zfs/dbuf.c +++ b/usr/src/uts/common/fs/zfs/dbuf.c @@ -2369,7 +2369,7 @@ dbuf_destroy(dmu_buf_impl_t *db) /* * Note: While bpp will always be updated if the function returns success, * parentp will not be updated if the dnode does not have dn_dbuf filled in; - * this happens when the dnode is the meta-dnode, or a userused or groupused + * this happens when the dnode is the meta-dnode, or {user|group|project}used * object. */ static int diff --git a/usr/src/uts/common/fs/zfs/dmu.c b/usr/src/uts/common/fs/zfs/dmu.c index 5b4b78dd1d..63b5ef8671 100644 --- a/usr/src/uts/common/fs/zfs/dmu.c +++ b/usr/src/uts/common/fs/zfs/dmu.c @@ -135,8 +135,8 @@ const dmu_object_type_info_t dmu_ot[DMU_OT_NUMTYPES] = { { DMU_BSWAP_UINT64, TRUE, FALSE, FALSE, "FUID table size" }, { DMU_BSWAP_ZAP, TRUE, TRUE, FALSE, "DSL dataset next clones" }, { DMU_BSWAP_ZAP, TRUE, FALSE, FALSE, "scan work queue" }, - { DMU_BSWAP_ZAP, TRUE, FALSE, TRUE, "ZFS user/group used" }, - { DMU_BSWAP_ZAP, TRUE, FALSE, TRUE, "ZFS user/group quota" }, + { DMU_BSWAP_ZAP, TRUE, FALSE, TRUE, "ZFS user/group/project used"}, + { DMU_BSWAP_ZAP, TRUE, FALSE, TRUE, "ZFS user/group/proj quota"}, { DMU_BSWAP_ZAP, TRUE, TRUE, FALSE, "snapshot refcount tags" }, { DMU_BSWAP_ZAP, TRUE, FALSE, FALSE, "DDT ZAP algorithm" }, { DMU_BSWAP_ZAP, TRUE, FALSE, FALSE, "DDT statistics" }, diff --git a/usr/src/uts/common/fs/zfs/dmu_objset.c b/usr/src/uts/common/fs/zfs/dmu_objset.c index 8174222826..3e11d73cad 100644 --- a/usr/src/uts/common/fs/zfs/dmu_objset.c +++ b/usr/src/uts/common/fs/zfs/dmu_objset.c @@ -23,7 +23,7 @@ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2017 by Delphix. All rights reserved. * Copyright (c) 2013 by Saso Kiselkov. All rights reserved. - * Copyright (c) 2013, Joyent, Inc. All rights reserved. + * Copyright 2019 Joyent, Inc. * Copyright (c) 2014 Spectra Logic Corporation, All rights reserved. * Copyright (c) 2015, STRATO AG, Inc. All rights reserved. * Copyright (c) 2014 Integros [integros.com] @@ -32,6 +32,7 @@ /* Portions Copyright 2010 Robert Milkowski */ +#include #include #include #include @@ -54,7 +55,9 @@ #include #include #include +#include #include +#include #include "zfs_namecheck.h" /* @@ -80,6 +83,9 @@ int dmu_rescan_dnode_threshold = 131072; static void dmu_objset_find_dp_cb(void *arg); +static void dmu_objset_upgrade(objset_t *os, dmu_objset_upgrade_cb_t cb); +static void dmu_objset_upgrade_stop(objset_t *os); + void dmu_objset_init(void) { @@ -343,14 +349,17 @@ dmu_objset_byteswap(void *buf, size_t size) { objset_phys_t *osp = buf; - ASSERT(size == OBJSET_OLD_PHYS_SIZE || size == sizeof (objset_phys_t)); + ASSERT(size == OBJSET_PHYS_SIZE_V1 || size == OBJSET_PHYS_SIZE_V2 || + size == sizeof (objset_phys_t)); dnode_byteswap(&osp->os_meta_dnode); byteswap_uint64_array(&osp->os_zil_header, sizeof (zil_header_t)); osp->os_type = BSWAP_64(osp->os_type); osp->os_flags = BSWAP_64(osp->os_flags); - if (size == sizeof (objset_phys_t)) { + if (size >= OBJSET_PHYS_SIZE_V2) { dnode_byteswap(&osp->os_userused_dnode); dnode_byteswap(&osp->os_groupused_dnode); + if (size >= sizeof (objset_phys_t)) + dnode_byteswap(&osp->os_projectused_dnode); } } @@ -419,6 +428,7 @@ dmu_objset_open_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, if (!BP_IS_HOLE(os->os_rootbp)) { arc_flags_t aflags = ARC_FLAG_WAIT; zbookmark_phys_t zb; + int size; enum zio_flag zio_flags = ZIO_FLAG_CANFAIL; SET_BOOKMARK(&zb, ds ? ds->ds_object : DMU_META_OBJSET, ZB_ROOT_OBJECT, ZB_ROOT_LEVEL, ZB_ROOT_BLKID); @@ -444,12 +454,19 @@ dmu_objset_open_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, return (err); } + if (spa_version(spa) < SPA_VERSION_USERSPACE) + size = OBJSET_PHYS_SIZE_V1; + else if (!spa_feature_is_enabled(spa, + SPA_FEATURE_PROJECT_QUOTA)) + size = OBJSET_PHYS_SIZE_V2; + else + size = sizeof (objset_phys_t); + /* Increase the blocksize if we are permitted. */ - if (spa_version(spa) >= SPA_VERSION_USERSPACE && - arc_buf_size(os->os_phys_buf) < sizeof (objset_phys_t)) { + if (arc_buf_size(os->os_phys_buf) < size) { arc_buf_t *buf = arc_alloc_buf(spa, &os->os_phys_buf, - ARC_BUFC_METADATA, sizeof (objset_phys_t)); - bzero(buf->b_data, sizeof (objset_phys_t)); + ARC_BUFC_METADATA, size); + bzero(buf->b_data, size); bcopy(os->os_phys_buf->b_data, buf->b_data, arc_buf_size(os->os_phys_buf)); arc_buf_destroy(os->os_phys_buf, &os->os_phys_buf); @@ -460,7 +477,7 @@ dmu_objset_open_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, os->os_flags = os->os_phys->os_flags; } else { int size = spa_version(spa) >= SPA_VERSION_USERSPACE ? - sizeof (objset_phys_t) : OBJSET_OLD_PHYS_SIZE; + sizeof (objset_phys_t) : OBJSET_PHYS_SIZE_V1; os->os_phys_buf = arc_alloc_buf(spa, &os->os_phys_buf, ARC_BUFC_METADATA, size); os->os_phys = os->os_phys_buf->b_data; @@ -604,13 +621,19 @@ dmu_objset_open_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, dnode_special_open(os, &os->os_phys->os_meta_dnode, DMU_META_DNODE_OBJECT, &os->os_meta_dnode); - if (arc_buf_size(os->os_phys_buf) >= sizeof (objset_phys_t)) { + if (OBJSET_BUF_HAS_USERUSED(os->os_phys_buf)) { dnode_special_open(os, &os->os_phys->os_userused_dnode, DMU_USERUSED_OBJECT, &os->os_userused_dnode); dnode_special_open(os, &os->os_phys->os_groupused_dnode, DMU_GROUPUSED_OBJECT, &os->os_groupused_dnode); + if (OBJSET_BUF_HAS_PROJECTUSED(os->os_phys_buf)) + dnode_special_open(os, + &os->os_phys->os_projectused_dnode, + DMU_PROJECTUSED_OBJECT, &os->os_projectused_dnode); } + mutex_init(&os->os_upgrade_lock, NULL, MUTEX_DEFAULT, NULL); + *osp = os; return (0); } @@ -749,8 +772,19 @@ dmu_objset_own(const char *name, dmu_objset_type_t type, return (err); } - dsl_pool_rele(dp, FTAG); + /* + * User accounting requires the dataset to be decrypted and rw. + * We also don't begin user accounting during claiming to help + * speed up pool import times and to keep this txg reserved + * completely for recovery work. + */ + if ((dmu_objset_userobjspace_upgradable(*osp) || + dmu_objset_projectquota_upgradable(*osp)) && + !readonly && !dp->dp_spa->spa_claiming && + (ds->ds_dir->dd_crypto_obj == 0 || decrypt)) + dmu_objset_id_quota_upgrade(*osp); + dsl_pool_rele(dp, FTAG); return (0); } @@ -826,6 +860,10 @@ dmu_objset_refresh_ownership(dsl_dataset_t *ds, dsl_dataset_t **newds, void dmu_objset_disown(objset_t *os, boolean_t decrypt, void *tag) { + /* + * Stop upgrading thread + */ + dmu_objset_upgrade_stop(os); dsl_dataset_disown(os->os_dsl_dataset, (decrypt) ? DS_HOLD_FLAG_DECRYPT : 0, tag); } @@ -861,6 +899,8 @@ dmu_objset_evict_dbufs(objset_t *os) mutex_exit(&os->os_lock); if (DMU_USERUSED_DNODE(os) != NULL) { + if (DMU_PROJECTUSED_DNODE(os) != NULL) + dnode_evict_dbufs(DMU_PROJECTUSED_DNODE(os)); dnode_evict_dbufs(DMU_GROUPUSED_DNODE(os)); dnode_evict_dbufs(DMU_USERUSED_DNODE(os)); } @@ -915,6 +955,8 @@ dmu_objset_evict_done(objset_t *os) dnode_special_close(&os->os_meta_dnode); if (DMU_USERUSED_DNODE(os)) { + if (DMU_PROJECTUSED_DNODE(os)) + dnode_special_close(&os->os_projectused_dnode); dnode_special_close(&os->os_userused_dnode); dnode_special_close(&os->os_groupused_dnode); } @@ -1024,6 +1066,18 @@ dmu_objset_create_impl_dnstats(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, if (dmu_objset_userused_enabled(os) && (!os->os_encrypted || !dmu_objset_is_receiving(os))) { os->os_phys->os_flags |= OBJSET_FLAG_USERACCOUNTING_COMPLETE; + if (dmu_objset_userobjused_enabled(os)) { + ds->ds_feature_activation_needed[ + SPA_FEATURE_USEROBJ_ACCOUNTING] = B_TRUE; + os->os_phys->os_flags |= + OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE; + } + if (dmu_objset_projectquota_enabled(os)) { + ds->ds_feature_activation_needed[ + SPA_FEATURE_PROJECT_QUOTA] = B_TRUE; + os->os_phys->os_flags |= + OBJSET_FLAG_PROJECTQUOTA_COMPLETE; + } os->os_flags = os->os_phys->os_flags; } @@ -1414,6 +1468,58 @@ dmu_objset_snapshot_one(const char *fsname, const char *snapname) return (err); } +static void +dmu_objset_upgrade_task_cb(void *data) +{ + objset_t *os = data; + + mutex_enter(&os->os_upgrade_lock); + os->os_upgrade_status = EINTR; + if (!os->os_upgrade_exit) { + mutex_exit(&os->os_upgrade_lock); + + os->os_upgrade_status = os->os_upgrade_cb(os); + mutex_enter(&os->os_upgrade_lock); + } + os->os_upgrade_exit = B_TRUE; + os->os_upgrade_id = 0; + mutex_exit(&os->os_upgrade_lock); +} + +static void +dmu_objset_upgrade(objset_t *os, dmu_objset_upgrade_cb_t cb) +{ + if (os->os_upgrade_id != 0) + return; + + mutex_enter(&os->os_upgrade_lock); + if (os->os_upgrade_id == 0 && os->os_upgrade_status == 0) { + os->os_upgrade_exit = B_FALSE; + os->os_upgrade_cb = cb; + os->os_upgrade_id = taskq_dispatch( + os->os_spa->spa_upgrade_taskq, + dmu_objset_upgrade_task_cb, os, TQ_SLEEP); + if (os->os_upgrade_id == 0) + os->os_upgrade_status = ENOMEM; + } + mutex_exit(&os->os_upgrade_lock); +} + +static void +dmu_objset_upgrade_stop(objset_t *os) +{ + mutex_enter(&os->os_upgrade_lock); + os->os_upgrade_exit = B_TRUE; + if (os->os_upgrade_id != 0) { + os->os_upgrade_id = 0; + mutex_exit(&os->os_upgrade_lock); + + taskq_wait(os->os_spa->spa_upgrade_taskq); + } else { + mutex_exit(&os->os_upgrade_lock); + } +} + static void dmu_objset_sync_dnodes(multilist_sublist_t *list, dmu_tx_t *tx) { @@ -1471,7 +1577,7 @@ dmu_objset_write_ready(zio_t *zio, arc_buf_t *abuf, void *arg) * Update rootbp fill count: it should be the number of objects * allocated in the object set (not counting the "special" * objects that are stored in the objset_phys_t -- the meta - * dnode and user/group accounting objects). + * dnode and user/group/project accounting objects). */ for (int i = 0; i < dnp->dn_nblkptr; i++) fill += BP_GET_FILL(&dnp->dn_blkptr[i]); @@ -1600,6 +1706,12 @@ dmu_objset_sync(objset_t *os, zio_t *pio, dmu_tx_t *tx) dnode_sync(DMU_GROUPUSED_DNODE(os), tx); } + if (DMU_PROJECTUSED_DNODE(os) && + DMU_PROJECTUSED_DNODE(os)->dn_type != DMU_OT_NONE) { + DMU_PROJECTUSED_DNODE(os)->dn_zio = zio; + dnode_sync(DMU_PROJECTUSED_DNODE(os), tx); + } + txgoff = tx->tx_txg & TXG_MASK; if (dmu_objset_userused_enabled(os) && @@ -1676,15 +1788,32 @@ 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)); +} + +boolean_t +dmu_objset_projectquota_enabled(objset_t *os) +{ + return (used_cbs[os->os_phys->os_type] != NULL && + DMU_PROJECTUSED_DNODE(os) != NULL && + spa_feature_is_enabled(os->os_spa, SPA_FEATURE_PROJECT_QUOTA)); +} + typedef struct userquota_node { - uint64_t uqn_id; - int64_t uqn_delta; - avl_node_t uqn_node; + /* must be in the first field, see userquota_update_cache() */ + char uqn_id[20 + DMU_OBJACCT_PREFIX_LEN]; + int64_t uqn_delta; + avl_node_t uqn_node; } userquota_node_t; typedef struct userquota_cache { avl_tree_t uqc_user_deltas; avl_tree_t uqc_group_deltas; + avl_tree_t uqc_project_deltas; } userquota_cache_t; static int @@ -1692,12 +1821,15 @@ userquota_compare(const void *l, const void *r) { const userquota_node_t *luqn = l; const userquota_node_t *ruqn = r; + int rv; - if (luqn->uqn_id < ruqn->uqn_id) - return (-1); - if (luqn->uqn_id > ruqn->uqn_id) - return (1); - return (0); + /* + * NB: can only access uqn_id because userquota_update_cache() doesn't + * pass in an entire userquota_node_t. + */ + rv = strcmp(luqn->uqn_id, ruqn->uqn_id); + + return (AVL_ISIGN(rv)); } static void @@ -1717,7 +1849,7 @@ do_userquota_cacheflush(objset_t *os, userquota_cache_t *cache, dmu_tx_t *tx) * is not thread-safe (i.e. not atomic). */ mutex_enter(&os->os_userused_lock); - VERIFY0(zap_increment_int(os, DMU_USERUSED_OBJECT, + VERIFY0(zap_increment(os, DMU_USERUSED_OBJECT, uqn->uqn_id, uqn->uqn_delta, tx)); mutex_exit(&os->os_userused_lock); kmem_free(uqn, sizeof (*uqn)); @@ -1728,40 +1860,96 @@ do_userquota_cacheflush(objset_t *os, userquota_cache_t *cache, dmu_tx_t *tx) while ((uqn = avl_destroy_nodes(&cache->uqc_group_deltas, &cookie)) != NULL) { mutex_enter(&os->os_userused_lock); - VERIFY0(zap_increment_int(os, DMU_GROUPUSED_OBJECT, + VERIFY0(zap_increment(os, DMU_GROUPUSED_OBJECT, uqn->uqn_id, uqn->uqn_delta, tx)); mutex_exit(&os->os_userused_lock); kmem_free(uqn, sizeof (*uqn)); } avl_destroy(&cache->uqc_group_deltas); + + if (dmu_objset_projectquota_enabled(os)) { + cookie = NULL; + while ((uqn = avl_destroy_nodes(&cache->uqc_project_deltas, + &cookie)) != NULL) { + mutex_enter(&os->os_userused_lock); + VERIFY0(zap_increment(os, DMU_PROJECTUSED_OBJECT, + uqn->uqn_id, uqn->uqn_delta, tx)); + mutex_exit(&os->os_userused_lock); + kmem_free(uqn, sizeof (*uqn)); + } + avl_destroy(&cache->uqc_project_deltas); + } } static void -userquota_update_cache(avl_tree_t *avl, uint64_t id, int64_t delta) +userquota_update_cache(avl_tree_t *avl, const char *id, int64_t delta) { - userquota_node_t search = { .uqn_id = id }; + userquota_node_t *uqn; avl_index_t idx; - userquota_node_t *uqn = avl_find(avl, &search, &idx); + ASSERT(strlen(id) < sizeof (uqn->uqn_id)); + /* + * Use id directly for searching because uqn_id is the first field of + * userquota_node_t and fields after uqn_id won't be accessed in + * avl_find(). + */ + uqn = avl_find(avl, (const void *)id, &idx); if (uqn == NULL) { uqn = kmem_zalloc(sizeof (*uqn), KM_SLEEP); - uqn->uqn_id = id; + (void) strlcpy(uqn->uqn_id, id, sizeof (uqn->uqn_id)); avl_insert(avl, uqn, idx); } uqn->uqn_delta += delta; } static void -do_userquota_update(userquota_cache_t *cache, uint64_t used, uint64_t flags, - uint64_t user, uint64_t group, boolean_t subtract) +do_userquota_update(objset_t *os, userquota_cache_t *cache, uint64_t used, + uint64_t flags, uint64_t user, uint64_t group, uint64_t project, + boolean_t subtract) { if ((flags & DNODE_FLAG_USERUSED_ACCOUNTED)) { int64_t delta = DNODE_MIN_SIZE + used; + char name[20]; + if (subtract) delta = -delta; - userquota_update_cache(&cache->uqc_user_deltas, user, delta); - userquota_update_cache(&cache->uqc_group_deltas, group, delta); + (void) sprintf(name, "%llx", (longlong_t)user); + userquota_update_cache(&cache->uqc_user_deltas, name, delta); + + (void) sprintf(name, "%llx", (longlong_t)group); + userquota_update_cache(&cache->uqc_group_deltas, name, delta); + + if (dmu_objset_projectquota_enabled(os)) { + (void) sprintf(name, "%llx", (longlong_t)project); + userquota_update_cache(&cache->uqc_project_deltas, + name, delta); + } + } +} + +static void +do_userobjquota_update(objset_t *os, userquota_cache_t *cache, uint64_t flags, + uint64_t user, uint64_t group, uint64_t project, boolean_t subtract) +{ + if (flags & DNODE_FLAG_USEROBJUSED_ACCOUNTED) { + char name[20 + DMU_OBJACCT_PREFIX_LEN]; + int delta = subtract ? -1 : 1; + + (void) snprintf(name, sizeof (name), DMU_OBJACCT_PREFIX "%llx", + (longlong_t)user); + userquota_update_cache(&cache->uqc_user_deltas, name, delta); + + (void) snprintf(name, sizeof (name), DMU_OBJACCT_PREFIX "%llx", + (longlong_t)group); + userquota_update_cache(&cache->uqc_group_deltas, name, delta); + + if (dmu_objset_projectquota_enabled(os)) { + (void) snprintf(name, sizeof (name), + DMU_OBJACCT_PREFIX "%llx", (longlong_t)project); + userquota_update_cache(&cache->uqc_project_deltas, + name, delta); + } } } @@ -1789,6 +1977,10 @@ userquota_updates_task(void *arg) sizeof (userquota_node_t), offsetof(userquota_node_t, uqn_node)); avl_create(&cache.uqc_group_deltas, userquota_compare, sizeof (userquota_node_t), offsetof(userquota_node_t, uqn_node)); + if (dmu_objset_projectquota_enabled(os)) + avl_create(&cache.uqc_project_deltas, userquota_compare, + sizeof (userquota_node_t), offsetof(userquota_node_t, + uqn_node)); while ((dn = multilist_sublist_head(list)) != NULL) { int flags; @@ -1800,15 +1992,21 @@ userquota_updates_task(void *arg) flags = dn->dn_id_flags; ASSERT(flags); if (flags & DN_ID_OLD_EXIST) { - do_userquota_update(&cache, - dn->dn_oldused, dn->dn_oldflags, - dn->dn_olduid, dn->dn_oldgid, B_TRUE); + do_userquota_update(os, &cache, dn->dn_oldused, + dn->dn_oldflags, dn->dn_olduid, dn->dn_oldgid, + dn->dn_oldprojid, B_TRUE); + do_userobjquota_update(os, &cache, dn->dn_oldflags, + dn->dn_olduid, dn->dn_oldgid, + dn->dn_oldprojid, B_TRUE); } if (flags & DN_ID_NEW_EXIST) { - do_userquota_update(&cache, - DN_USED_BYTES(dn->dn_phys), - dn->dn_phys->dn_flags, dn->dn_newuid, - dn->dn_newgid, B_FALSE); + do_userquota_update(os, &cache, + DN_USED_BYTES(dn->dn_phys), dn->dn_phys->dn_flags, + dn->dn_newuid, dn->dn_newgid, + dn->dn_newprojid, B_FALSE); + do_userobjquota_update(os, &cache, + dn->dn_phys->dn_flags, dn->dn_newuid, dn->dn_newgid, + dn->dn_newprojid, B_FALSE); } mutex_enter(&dn->dn_mtx); @@ -1817,6 +2015,7 @@ userquota_updates_task(void *arg) if (dn->dn_id_flags & DN_ID_NEW_EXIST) { dn->dn_olduid = dn->dn_newuid; dn->dn_oldgid = dn->dn_newgid; + dn->dn_oldprojid = dn->dn_newprojid; dn->dn_id_flags |= DN_ID_OLD_EXIST; if (dn->dn_bonuslen == 0) dn->dn_id_flags |= DN_ID_CHKED_SPILL; @@ -1842,11 +2041,17 @@ dmu_objset_do_userquota_updates(objset_t *os, dmu_tx_t *tx) if (!dmu_objset_userused_enabled(os)) return; - /* if this is a raw receive just return and handle accounting later */ + /* + * If this is a raw receive just return and handle accounting + * later when we have the keys loaded. We also don't do user + * accounting during claiming since the datasets are not owned + * for the duration of claiming and this txg should only be + * used for recovery. + */ if (os->os_encrypted && dmu_objset_is_receiving(os)) return; - /* Allocate the user/groupused objects if necessary. */ + /* Allocate the user/group/project used objects if necessary. */ if (DMU_USERUSED_DNODE(os)->dn_type == DMU_OT_NONE) { VERIFY0(zap_create_claim(os, DMU_USERUSED_OBJECT, @@ -1856,6 +2061,12 @@ dmu_objset_do_userquota_updates(objset_t *os, dmu_tx_t *tx) DMU_OT_USERGROUP_USED, DMU_OT_NONE, 0, tx)); } + if (dmu_objset_projectquota_enabled(os) && + DMU_PROJECTUSED_DNODE(os)->dn_type == DMU_OT_NONE) { + VERIFY0(zap_create_claim(os, DMU_PROJECTUSED_OBJECT, + DMU_OT_USERGROUP_USED, DMU_OT_NONE, 0, tx)); + } + for (int i = 0; i < multilist_get_num_sublists(os->os_synced_dnodes); i++) { userquota_updates_arg_t *uua = @@ -1918,6 +2129,7 @@ dmu_objset_userquota_get_ids(dnode_t *dn, boolean_t before, dmu_tx_t *tx) dmu_buf_impl_t *db = NULL; uint64_t *user = NULL; uint64_t *group = NULL; + uint64_t *project = NULL; int flags = dn->dn_id_flags; int error; boolean_t have_spill = B_FALSE; @@ -1975,9 +2187,11 @@ dmu_objset_userquota_get_ids(dnode_t *dn, boolean_t before, dmu_tx_t *tx) ASSERT(data); user = &dn->dn_olduid; group = &dn->dn_oldgid; + project = &dn->dn_oldprojid; } else if (data) { user = &dn->dn_newuid; group = &dn->dn_newgid; + project = &dn->dn_newprojid; } /* @@ -1985,7 +2199,7 @@ dmu_objset_userquota_get_ids(dnode_t *dn, boolean_t before, dmu_tx_t *tx) * type has changed and that type isn't an object type to track */ error = used_cbs[os->os_phys->os_type](dn->dn_bonustype, data, - user, group); + user, group, project); /* * Preserve existing uid/gid when the callback can't determine @@ -1998,9 +2212,11 @@ dmu_objset_userquota_get_ids(dnode_t *dn, boolean_t before, dmu_tx_t *tx) if (flags & DN_ID_OLD_EXIST) { dn->dn_newuid = dn->dn_olduid; dn->dn_newgid = dn->dn_oldgid; + dn->dn_newgid = dn->dn_oldprojid; } else { dn->dn_newuid = 0; dn->dn_newgid = 0; + dn->dn_newprojid = ZFS_DEFAULT_PROJID; } error = 0; } @@ -2031,19 +2247,26 @@ 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); +} + +boolean_t +dmu_objset_projectquota_present(objset_t *os) +{ + return (os->os_phys->os_flags & + OBJSET_FLAG_PROJECTQUOTA_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 @@ -2057,8 +2280,21 @@ dmu_objset_userspace_upgrade(objset_t *os) dmu_buf_t *db; int objerr; - if (issig(JUSTLOOKING) && issig(FORREAL)) - return (SET_ERROR(EINTR)); + mutex_enter(&os->os_upgrade_lock); + if (os->os_upgrade_exit) + err = SET_ERROR(EINTR); + mutex_exit(&os->os_upgrade_lock); + if (err != 0) + return (err); + + /* + * The following is only valid on Linux since we cannot send + * a signal to a kernel thread on illumos (because we have no + * lwp and never return to user-land). + * + * if (issig(JUSTLOOKING) && issig(FORREAL)) + * return (SET_ERROR(EINTR)); + */ objerr = dmu_bonus_hold(os, obj, FTAG, &db); if (objerr != 0) @@ -2074,12 +2310,89 @@ 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_is_snapshot(os)) + return (SET_ERROR(EINVAL)); + if (!dmu_objset_userused_enabled(os)) + return (SET_ERROR(ENOTSUP)); + + 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); } +static int +dmu_objset_id_quota_upgrade_cb(objset_t *os) +{ + int err = 0; + + if (dmu_objset_userobjspace_present(os) && + dmu_objset_projectquota_present(os)) + return (0); + if (dmu_objset_is_snapshot(os)) + return (SET_ERROR(EINVAL)); + if (!dmu_objset_userobjused_enabled(os)) + return (SET_ERROR(ENOTSUP)); + if (!dmu_objset_projectquota_enabled(os) && + dmu_objset_userobjspace_present(os)) + return (SET_ERROR(ENOTSUP)); + + dmu_objset_ds(os)->ds_feature_activation_needed[ + SPA_FEATURE_USEROBJ_ACCOUNTING] = B_TRUE; + if (dmu_objset_projectquota_enabled(os)) + dmu_objset_ds(os)->ds_feature_activation_needed[ + SPA_FEATURE_PROJECT_QUOTA] = B_TRUE; + + err = dmu_objset_space_upgrade(os); + if (err) + return (err); + + os->os_flags |= OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE; + if (dmu_objset_projectquota_enabled(os)) + os->os_flags |= OBJSET_FLAG_PROJECTQUOTA_COMPLETE; + + txg_wait_synced(dmu_objset_pool(os), 0); + return (0); +} + +void +dmu_objset_id_quota_upgrade(objset_t *os) +{ + dmu_objset_upgrade(os, dmu_objset_id_quota_upgrade_cb); +} + +boolean_t +dmu_objset_userobjspace_upgradable(objset_t *os) +{ + return (dmu_objset_type(os) == DMU_OST_ZFS && + !dmu_objset_is_snapshot(os) && + dmu_objset_userobjused_enabled(os) && + !dmu_objset_userobjspace_present(os) && + spa_writeable(dmu_objset_spa(os))); +} + +boolean_t +dmu_objset_projectquota_upgradable(objset_t *os) +{ + return (dmu_objset_type(os) == DMU_OST_ZFS && + !dmu_objset_is_snapshot(os) && + dmu_objset_projectquota_enabled(os) && + !dmu_objset_projectquota_present(os)); +} + void dmu_objset_space(objset_t *os, uint64_t *refdbytesp, uint64_t *availbytesp, uint64_t *usedobjsp, uint64_t *availobjsp) diff --git a/usr/src/uts/common/fs/zfs/dmu_traverse.c b/usr/src/uts/common/fs/zfs/dmu_traverse.c index 0547a09498..c8e4899d07 100644 --- a/usr/src/uts/common/fs/zfs/dmu_traverse.c +++ b/usr/src/uts/common/fs/zfs/dmu_traverse.c @@ -376,7 +376,11 @@ traverse_visitbp(traverse_data_t *td, const dnode_phys_t *dnp, if (osp->os_meta_dnode.dn_maxblkid == 0) td->td_realloc_possible = B_FALSE; - if (arc_buf_size(buf) >= sizeof (objset_phys_t)) { + if (OBJSET_BUF_HAS_USERUSED(buf)) { + if (OBJSET_BUF_HAS_PROJECTUSED(buf)) + prefetch_dnode_metadata(td, + &osp->os_projectused_dnode, + zb->zb_objset, DMU_PROJECTUSED_OBJECT); prefetch_dnode_metadata(td, &osp->os_groupused_dnode, zb->zb_objset, DMU_GROUPUSED_OBJECT); prefetch_dnode_metadata(td, &osp->os_userused_dnode, @@ -385,13 +389,19 @@ traverse_visitbp(traverse_data_t *td, const dnode_phys_t *dnp, err = traverse_dnode(td, &osp->os_meta_dnode, zb->zb_objset, DMU_META_DNODE_OBJECT); - if (err == 0 && arc_buf_size(buf) >= sizeof (objset_phys_t)) { - err = traverse_dnode(td, &osp->os_groupused_dnode, - zb->zb_objset, DMU_GROUPUSED_OBJECT); - } - if (err == 0 && arc_buf_size(buf) >= sizeof (objset_phys_t)) { - err = traverse_dnode(td, &osp->os_userused_dnode, - zb->zb_objset, DMU_USERUSED_OBJECT); + if (err == 0 && OBJSET_BUF_HAS_USERUSED(buf)) { + if (OBJSET_BUF_HAS_PROJECTUSED(buf)) + err = traverse_dnode(td, + &osp->os_projectused_dnode, zb->zb_objset, + DMU_PROJECTUSED_OBJECT); + if (err == 0) + err = traverse_dnode(td, + &osp->os_groupused_dnode, zb->zb_objset, + DMU_GROUPUSED_OBJECT); + if (err == 0) + err = traverse_dnode(td, + &osp->os_userused_dnode, zb->zb_objset, + DMU_USERUSED_OBJECT); } } diff --git a/usr/src/uts/common/fs/zfs/dnode.c b/usr/src/uts/common/fs/zfs/dnode.c index 5a86650d28..90f425a800 100644 --- a/usr/src/uts/common/fs/zfs/dnode.c +++ b/usr/src/uts/common/fs/zfs/dnode.c @@ -39,6 +39,7 @@ #include #include #include +#include dnode_stats_t dnode_stats = { { "dnode_hold_dbuf_hold", KSTAT_DATA_UINT64 }, @@ -159,8 +160,10 @@ dnode_cons(void *arg, void *unused, int kmflag) dn->dn_oldflags = 0; dn->dn_olduid = 0; dn->dn_oldgid = 0; + dn->dn_oldprojid = ZFS_DEFAULT_PROJID; dn->dn_newuid = 0; dn->dn_newgid = 0; + dn->dn_newprojid = ZFS_DEFAULT_PROJID; dn->dn_id_flags = 0; dn->dn_dbufs_count = 0; @@ -213,8 +216,10 @@ dnode_dest(void *arg, void *unused) ASSERT0(dn->dn_oldflags); ASSERT0(dn->dn_olduid); ASSERT0(dn->dn_oldgid); + ASSERT0(dn->dn_oldprojid); ASSERT0(dn->dn_newuid); ASSERT0(dn->dn_newgid); + ASSERT0(dn->dn_newprojid); ASSERT0(dn->dn_id_flags); ASSERT0(dn->dn_dbufs_count); @@ -554,8 +559,10 @@ dnode_destroy(dnode_t *dn) dn->dn_oldflags = 0; dn->dn_olduid = 0; dn->dn_oldgid = 0; + dn->dn_oldprojid = ZFS_DEFAULT_PROJID; dn->dn_newuid = 0; dn->dn_newgid = 0; + dn->dn_newprojid = ZFS_DEFAULT_PROJID; dn->dn_id_flags = 0; dmu_zfetch_fini(&dn->dn_zfetch); @@ -815,8 +822,10 @@ dnode_move_impl(dnode_t *odn, dnode_t *ndn) ndn->dn_oldflags = odn->dn_oldflags; ndn->dn_olduid = odn->dn_olduid; ndn->dn_oldgid = odn->dn_oldgid; + ndn->dn_oldprojid = odn->dn_oldprojid; ndn->dn_newuid = odn->dn_newuid; ndn->dn_newgid = odn->dn_newgid; + ndn->dn_newprojid = odn->dn_newprojid; ndn->dn_id_flags = odn->dn_id_flags; dmu_zfetch_init(&ndn->dn_zfetch, NULL); list_move_tail(&ndn->dn_zfetch.zf_stream, &odn->dn_zfetch.zf_stream); @@ -876,8 +885,10 @@ dnode_move_impl(dnode_t *odn, dnode_t *ndn) odn->dn_oldflags = 0; odn->dn_olduid = 0; odn->dn_oldgid = 0; + odn->dn_oldprojid = ZFS_DEFAULT_PROJID; odn->dn_newuid = 0; odn->dn_newgid = 0; + odn->dn_newprojid = ZFS_DEFAULT_PROJID; odn->dn_id_flags = 0; /* @@ -1289,9 +1300,14 @@ dnode_hold_impl(objset_t *os, uint64_t object, int flag, int slots, ASSERT((flag & DNODE_MUST_BE_ALLOCATED) || (flag & DNODE_MUST_BE_FREE)); - if (object == DMU_USERUSED_OBJECT || object == DMU_GROUPUSED_OBJECT) { - dn = (object == DMU_USERUSED_OBJECT) ? - DMU_USERUSED_DNODE(os) : DMU_GROUPUSED_DNODE(os); + if (object == DMU_USERUSED_OBJECT || object == DMU_GROUPUSED_OBJECT || + object == DMU_PROJECTUSED_OBJECT) { + if (object == DMU_USERUSED_OBJECT) + dn = DMU_USERUSED_DNODE(os); + else if (object == DMU_GROUPUSED_OBJECT) + dn = DMU_GROUPUSED_DNODE(os); + else + dn = DMU_PROJECTUSED_DNODE(os); if (dn == NULL) return (SET_ERROR(ENOENT)); type = dn->dn_type; diff --git a/usr/src/uts/common/fs/zfs/dnode_sync.c b/usr/src/uts/common/fs/zfs/dnode_sync.c index f5ee8a290d..dc7317b411 100644 --- a/usr/src/uts/common/fs/zfs/dnode_sync.c +++ b/usr/src/uts/common/fs/zfs/dnode_sync.c @@ -622,12 +622,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/usr/src/uts/common/fs/zfs/dsl_pool.c b/usr/src/uts/common/fs/zfs/dsl_pool.c index 76bae90e68..1f1d2477ff 100644 --- a/usr/src/uts/common/fs/zfs/dsl_pool.c +++ b/usr/src/uts/common/fs/zfs/dsl_pool.c @@ -659,7 +659,7 @@ dsl_pool_sync(dsl_pool_t *dp, uint64_t txg) /* * After the data blocks have been written (ensured by the zio_wait() - * above), update the user/group space accounting. This happens + * above), update the user/group/project space accounting. This happens * in tasks dispatched to dp_sync_taskq, so wait for them before * continuing. */ diff --git a/usr/src/uts/common/fs/zfs/dsl_scan.c b/usr/src/uts/common/fs/zfs/dsl_scan.c index 73634e33e2..22e808dfd9 100644 --- a/usr/src/uts/common/fs/zfs/dsl_scan.c +++ b/usr/src/uts/common/fs/zfs/dsl_scan.c @@ -1826,11 +1826,15 @@ dsl_scan_recurse(dsl_scan_t *scn, dsl_dataset_t *ds, dmu_objset_type_t ostype, if (OBJSET_BUF_HAS_USERUSED(buf)) { /* - * We also always visit user/group accounting + * We also always visit user/group/project accounting * objects, and never skip them, even if we are * suspending. This is necessary so that the space * deltas from this txg get integrated. */ + if (OBJSET_BUF_HAS_PROJECTUSED(buf)) + dsl_scan_visitdnode(scn, ds, osp->os_type, + &osp->os_projectused_dnode, + DMU_PROJECTUSED_OBJECT, tx); dsl_scan_visitdnode(scn, ds, osp->os_type, &osp->os_groupused_dnode, DMU_GROUPUSED_OBJECT, tx); diff --git a/usr/src/uts/common/fs/zfs/sa.c b/usr/src/uts/common/fs/zfs/sa.c index 5a4bc705aa..6328d3fac3 100644 --- a/usr/src/uts/common/fs/zfs/sa.c +++ b/usr/src/uts/common/fs/zfs/sa.c @@ -25,6 +25,7 @@ * Copyright (c) 2013, 2017 by Delphix. All rights reserved. * Copyright (c) 2014 Spectra Logic Corporation, All rights reserved. * Copyright (c) 2014 Integros [integros.com] + * Copyright 2019 Joyent, Inc. */ #include @@ -46,6 +47,10 @@ #include #include +#ifdef _KERNEL +#include +#endif + /* * ZFS System attributes: * @@ -1437,8 +1442,9 @@ sa_lookup_impl(sa_handle_t *hdl, sa_bulk_attr_t *bulk, int count) return (sa_attr_op(hdl, bulk, count, SA_LOOKUP, NULL)); } -int -sa_lookup(sa_handle_t *hdl, sa_attr_type_t attr, void *buf, uint32_t buflen) +static int +sa_lookup_locked(sa_handle_t *hdl, sa_attr_type_t attr, void *buf, + uint32_t buflen) { int error; sa_bulk_attr_t bulk; @@ -1449,9 +1455,19 @@ sa_lookup(sa_handle_t *hdl, sa_attr_type_t attr, void *buf, uint32_t buflen) bulk.sa_data_func = NULL; ASSERT(hdl); - mutex_enter(&hdl->sa_lock); error = sa_lookup_impl(hdl, &bulk, 1); + return (error); +} + +int +sa_lookup(sa_handle_t *hdl, sa_attr_type_t attr, void *buf, uint32_t buflen) +{ + int error; + + mutex_enter(&hdl->sa_lock); + error = sa_lookup_locked(hdl, attr, buf, buflen); mutex_exit(&hdl->sa_lock); + return (error); } @@ -1477,6 +1493,173 @@ sa_lookup_uio(sa_handle_t *hdl, sa_attr_type_t attr, uio_t *uio) return (error); } + +/* + * For the existing object that is upgraded from old system, its ondisk layout + * has no slot for the project ID attribute. But quota accounting logic needs + * to access related slots by offset directly. So we need to adjust these old + * objects' layout to make the project ID to some unified and fixed offset. + */ +int +sa_add_projid(sa_handle_t *hdl, dmu_tx_t *tx, uint64_t projid) +{ + znode_t *zp = sa_get_userdata(hdl); + dmu_buf_t *db = sa_get_db(hdl); + zfsvfs_t *zfsvfs = zp->z_zfsvfs; + int count = 0, err = 0; + sa_bulk_attr_t *bulk, *attrs; + zfs_acl_locator_cb_t locate = { 0 }; + uint64_t uid, gid, mode, rdev, xattr = 0, parent, gen, links; + uint64_t crtime[2], mtime[2], ctime[2], atime[2]; + zfs_acl_phys_t znode_acl = { 0 }; + char scanstamp[AV_SCANSTAMP_SZ]; + + if (zp->z_acl_cached == NULL) { + zfs_acl_t *aclp; + + mutex_enter(&zp->z_acl_lock); + err = zfs_acl_node_read(zp, B_FALSE, &aclp, B_FALSE); + mutex_exit(&zp->z_acl_lock); + if (err != 0 && err != ENOENT) + return (err); + } + + bulk = kmem_zalloc(sizeof (sa_bulk_attr_t) * ZPL_END, KM_SLEEP); + attrs = kmem_zalloc(sizeof (sa_bulk_attr_t) * ZPL_END, KM_SLEEP); + mutex_enter(&hdl->sa_lock); + mutex_enter(&zp->z_lock); + + err = sa_lookup_locked(hdl, SA_ZPL_PROJID(zfsvfs), &projid, + sizeof (uint64_t)); + if (unlikely(err == 0)) + /* Someone has added project ID attr by race. */ + err = EEXIST; + if (err != ENOENT) + goto out; + + /* First do a bulk query of the attributes that aren't cached */ + if (zp->z_is_sa) { + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MODE(zfsvfs), NULL, + &mode, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GEN(zfsvfs), NULL, + &gen, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_UID(zfsvfs), NULL, + &uid, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GID(zfsvfs), NULL, + &gid, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_PARENT(zfsvfs), NULL, + &parent, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ATIME(zfsvfs), NULL, + &atime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, + &mtime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, + &ctime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CRTIME(zfsvfs), NULL, + &crtime, 16); + if (S_ISBLK(zp->z_mode) || S_ISCHR(zp->z_mode)) + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_RDEV(zfsvfs), NULL, + &rdev, 8); + } else { + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ATIME(zfsvfs), NULL, + &atime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, + &mtime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, + &ctime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CRTIME(zfsvfs), NULL, + &crtime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GEN(zfsvfs), NULL, + &gen, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MODE(zfsvfs), NULL, + &mode, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_PARENT(zfsvfs), NULL, + &parent, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_XATTR(zfsvfs), NULL, + &xattr, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_RDEV(zfsvfs), NULL, + &rdev, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_UID(zfsvfs), NULL, + &uid, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GID(zfsvfs), NULL, + &gid, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ZNODE_ACL(zfsvfs), NULL, + &znode_acl, 88); + } + err = sa_bulk_lookup_locked(hdl, bulk, count); + if (err != 0) + goto out; + + err = sa_lookup_locked(hdl, SA_ZPL_XATTR(zfsvfs), &xattr, 8); + if (err != 0 && err != ENOENT) + goto out; + + zp->z_projid = projid; + zp->z_pflags |= ZFS_PROJID; + links = zp->z_links; + count = 0; + err = 0; + + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_MODE(zfsvfs), NULL, &mode, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_SIZE(zfsvfs), NULL, + &zp->z_size, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_GEN(zfsvfs), NULL, &gen, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_UID(zfsvfs), NULL, &uid, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_GID(zfsvfs), NULL, &gid, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_PARENT(zfsvfs), NULL, &parent, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_FLAGS(zfsvfs), NULL, + &zp->z_pflags, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_ATIME(zfsvfs), NULL, &atime, 16); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_MTIME(zfsvfs), NULL, &mtime, 16); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_CTIME(zfsvfs), NULL, &ctime, 16); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_CRTIME(zfsvfs), NULL, + &crtime, 16); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_LINKS(zfsvfs), NULL, &links, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_PROJID(zfsvfs), NULL, &projid, 8); + + if (S_ISBLK(zp->z_mode) || S_ISCHR(zp->z_mode)) + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_RDEV(zfsvfs), NULL, + &rdev, 8); + + if (zp->z_acl_cached != NULL) { + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_DACL_COUNT(zfsvfs), NULL, + &zp->z_acl_cached->z_acl_count, 8); + if (zp->z_acl_cached->z_version < ZFS_ACL_VERSION_FUID) + zfs_acl_xform(zp, zp->z_acl_cached, CRED()); + locate.cb_aclp = zp->z_acl_cached; + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_DACL_ACES(zfsvfs), + zfs_acl_data_locator, &locate, + zp->z_acl_cached->z_acl_bytes); + } + + if (xattr) + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_XATTR(zfsvfs), NULL, + &xattr, 8); + + if (zp->z_pflags & ZFS_BONUS_SCANSTAMP) { + bcopy((caddr_t)db->db_data + ZFS_OLD_ZNODE_PHYS_SIZE, + scanstamp, AV_SCANSTAMP_SZ); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_SCANSTAMP(zfsvfs), NULL, + scanstamp, AV_SCANSTAMP_SZ); + zp->z_pflags &= ~ZFS_BONUS_SCANSTAMP; + } + + VERIFY(dmu_set_bonustype(db, DMU_OT_SA, tx) == 0); + VERIFY(sa_replace_all_by_template_locked(hdl, attrs, count, tx) == 0); + if (znode_acl.z_acl_extern_obj) { + VERIFY(0 == dmu_object_free(zfsvfs->z_os, + znode_acl.z_acl_extern_obj, tx)); + } + + zp->z_is_sa = B_TRUE; + +out: + mutex_exit(&zp->z_lock); + mutex_exit(&hdl->sa_lock); + kmem_free(attrs, sizeof (sa_bulk_attr_t) * ZPL_END); + kmem_free(bulk, sizeof (sa_bulk_attr_t) * ZPL_END); + return (err); +} #endif static sa_idx_tab_t * diff --git a/usr/src/uts/common/fs/zfs/spa.c b/usr/src/uts/common/fs/zfs/spa.c index c72e462b4f..cf920e3657 100644 --- a/usr/src/uts/common/fs/zfs/spa.c +++ b/usr/src/uts/common/fs/zfs/spa.c @@ -1203,6 +1203,13 @@ spa_activate(spa_t *spa, int mode) offsetof(spa_error_entry_t, se_avl)); spa_keystore_init(&spa->spa_keystore); + + /* + * The taskq to upgrade datasets in this pool. Currently used by + * feature SPA_FEATURE_USEROBJ_ACCOUNTING/SPA_FEATURE_PROJECT_QUOTA. + */ + spa->spa_upgrade_taskq = taskq_create("z_upgrade", boot_ncpus, + minclsyspri, 1, INT_MAX, TASKQ_DYNAMIC); } /* @@ -1219,6 +1226,11 @@ spa_deactivate(spa_t *spa) spa_evicting_os_wait(spa); + if (spa->spa_upgrade_taskq) { + taskq_destroy(spa->spa_upgrade_taskq); + spa->spa_upgrade_taskq = NULL; + } + txg_list_destroy(&spa->spa_vdev_txg_list); list_destroy(&spa->spa_config_dirty_list); diff --git a/usr/src/uts/common/fs/zfs/sys/dmu.h b/usr/src/uts/common/fs/zfs/sys/dmu.h index ffce616cbc..1001f52864 100644 --- a/usr/src/uts/common/fs/zfs/sys/dmu.h +++ b/usr/src/uts/common/fs/zfs/sys/dmu.h @@ -306,6 +306,13 @@ void zfs_znode_byteswap(void *buf, size_t size); #define DMU_USERUSED_OBJECT (-1ULL) #define DMU_GROUPUSED_OBJECT (-2ULL) +#define DMU_PROJECTUSED_OBJECT (-3ULL) + +/* + * Zap prefix for object accounting in DMU_{USER,GROUP,PROJECT}USED_OBJECT. + */ +#define DMU_OBJACCT_PREFIX "obj-" +#define DMU_OBJACCT_PREFIX_LEN 4 /* * artificial blkids for bonus buffer and spill blocks @@ -1006,7 +1013,7 @@ extern int dmu_dir_list_next(objset_t *os, int namelen, char *name, uint64_t *idp, uint64_t *offp); typedef int objset_used_cb_t(dmu_object_type_t bonustype, - void *bonus, uint64_t *userp, uint64_t *groupp); + void *bonus, uint64_t *userp, uint64_t *groupp, uint64_t *projectp); extern void dmu_objset_register_type(dmu_objset_type_t ost, objset_used_cb_t *cb); extern void dmu_objset_set_user(objset_t *os, void *user_ptr); diff --git a/usr/src/uts/common/fs/zfs/sys/dmu_objset.h b/usr/src/uts/common/fs/zfs/sys/dmu_objset.h index 41ae18a8b9..dac448d161 100644 --- a/usr/src/uts/common/fs/zfs/sys/dmu_objset.h +++ b/usr/src/uts/common/fs/zfs/sys/dmu_objset.h @@ -51,13 +51,18 @@ struct dsl_pool; struct dsl_dataset; struct dmu_tx; -#define OBJSET_PHYS_SIZE 2048 -#define OBJSET_OLD_PHYS_SIZE 1024 +#define OBJSET_PHYS_SIZE_V1 1024 +#define OBJSET_PHYS_SIZE_V2 2048 +#define OBJSET_PHYS_SIZE_V3 4096 #define OBJSET_BUF_HAS_USERUSED(buf) \ - (arc_buf_size(buf) > OBJSET_OLD_PHYS_SIZE) + (arc_buf_size(buf) >= OBJSET_PHYS_SIZE_V2) +#define OBJSET_BUF_HAS_PROJECTUSED(buf) \ + (arc_buf_size(buf) >= OBJSET_PHYS_SIZE_V3) -#define OBJSET_FLAG_USERACCOUNTING_COMPLETE (1ULL<<0) +#define OBJSET_FLAG_USERACCOUNTING_COMPLETE (1ULL << 0) +#define OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE (1ULL << 1) +#define OBJSET_FLAG_PROJECTQUOTA_COMPLETE (1ULL << 2) /* all flags are currently non-portable */ #define OBJSET_CRYPT_PORTABLE_FLAGS_MASK (0) @@ -69,13 +74,18 @@ typedef struct objset_phys { uint64_t os_flags; uint8_t os_portable_mac[ZIO_OBJSET_MAC_LEN]; uint8_t os_local_mac[ZIO_OBJSET_MAC_LEN]; - char os_pad[OBJSET_PHYS_SIZE - sizeof (dnode_phys_t)*3 - + char os_pad0[OBJSET_PHYS_SIZE_V2 - sizeof (dnode_phys_t)*3 - sizeof (zil_header_t) - sizeof (uint64_t)*2 - 2*ZIO_OBJSET_MAC_LEN]; dnode_phys_t os_userused_dnode; dnode_phys_t os_groupused_dnode; + dnode_phys_t os_projectused_dnode; + char os_pad1[OBJSET_PHYS_SIZE_V3 - OBJSET_PHYS_SIZE_V2 - + sizeof (dnode_phys_t)]; } objset_phys_t; +typedef int (*dmu_objset_upgrade_cb_t)(objset_t *); + #define OBJSET_PROP_UNINITIALIZED ((uint64_t)-1) struct objset { /* Immutable: */ @@ -94,6 +104,7 @@ struct objset { dnode_handle_t os_meta_dnode; dnode_handle_t os_userused_dnode; dnode_handle_t os_groupused_dnode; + dnode_handle_t os_projectused_dnode; zilog_t *os_zil; list_node_t os_evicting_node; @@ -159,13 +170,20 @@ struct objset { list_t os_dnodes; list_t os_downgraded_dbufs; - /* Protects changes to DMU_{USER,GROUP}USED_OBJECT */ + /* Protects changes to DMU_{USER,GROUP,PROJECT}USED_OBJECT */ kmutex_t os_userused_lock; /* stuff we store for the user */ kmutex_t os_user_ptr_lock; void *os_user_ptr; sa_os_t *os_sa; + + /* kernel thread to upgrade this dataset */ + kmutex_t os_upgrade_lock; + taskqid_t os_upgrade_id; + dmu_objset_upgrade_cb_t os_upgrade_cb; + boolean_t os_upgrade_exit; + int os_upgrade_status; }; #define DMU_META_OBJSET 0 @@ -174,6 +192,7 @@ struct objset { #define DMU_META_DNODE(os) ((os)->os_meta_dnode.dnh_dnode) #define DMU_USERUSED_DNODE(os) ((os)->os_userused_dnode.dnh_dnode) #define DMU_GROUPUSED_DNODE(os) ((os)->os_groupused_dnode.dnh_dnode) +#define DMU_PROJECTUSED_DNODE(os) ((os)->os_projectused_dnode.dnh_dnode) #define DMU_OS_IS_L2CACHEABLE(os) \ ((os)->os_secondary_cache == ZFS_CACHE_ALL || \ @@ -225,6 +244,14 @@ 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_userobjspace_upgradable(objset_t *os); +boolean_t dmu_objset_userobjused_enabled(objset_t *os); +boolean_t dmu_objset_userobjspace_present(objset_t *os); +boolean_t dmu_objset_projectquota_enabled(objset_t *os); +boolean_t dmu_objset_projectquota_present(objset_t *os); +boolean_t dmu_objset_projectquota_upgradable(objset_t *os); +void dmu_objset_id_quota_upgrade(objset_t *os); + boolean_t dmu_objset_incompatible_encryption_version(objset_t *os); int dmu_fsname(const char *snapname, char *buf); diff --git a/usr/src/uts/common/fs/zfs/sys/dnode.h b/usr/src/uts/common/fs/zfs/sys/dnode.h index da72903113..6c8ec5e229 100644 --- a/usr/src/uts/common/fs/zfs/sys/dnode.h +++ b/usr/src/uts/common/fs/zfs/sys/dnode.h @@ -142,11 +142,14 @@ enum dnode_dirtycontext { }; /* Is dn_used in bytes? if not, it's in multiples of SPA_MINBLOCKSIZE */ -#define DNODE_FLAG_USED_BYTES (1<<0) -#define DNODE_FLAG_USERUSED_ACCOUNTED (1<<1) +#define DNODE_FLAG_USED_BYTES (1 << 0) +#define DNODE_FLAG_USERUSED_ACCOUNTED (1 << 1) /* Does dnode have a SA spill blkptr in bonus? */ -#define DNODE_FLAG_SPILL_BLKPTR (1<<2) +#define DNODE_FLAG_SPILL_BLKPTR (1 << 2) + +/* User/Group/Project dnode accounting */ +#define DNODE_FLAG_USEROBJUSED_ACCOUNTED (1 << 3) /* * VARIABLE-LENGTH (LARGE) DNODES @@ -338,8 +341,8 @@ struct dnode { /* used in syncing context */ uint64_t dn_oldused; /* old phys used bytes */ uint64_t dn_oldflags; /* old phys dn_flags */ - uint64_t dn_olduid, dn_oldgid; - uint64_t dn_newuid, dn_newgid; + uint64_t dn_olduid, dn_oldgid, dn_oldprojid; + uint64_t dn_newuid, dn_newgid, dn_newprojid; int dn_id_flags; /* holds prefetch structure */ diff --git a/usr/src/uts/common/fs/zfs/sys/dsl_deleg.h b/usr/src/uts/common/fs/zfs/sys/dsl_deleg.h index dadbda324e..bb28014ac3 100644 --- a/usr/src/uts/common/fs/zfs/sys/dsl_deleg.h +++ b/usr/src/uts/common/fs/zfs/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" @@ -60,6 +64,10 @@ extern "C" { #define ZFS_DELEG_PERM_REMAP "remap" #define ZFS_DELEG_PERM_LOAD_KEY "load-key" #define ZFS_DELEG_PERM_CHANGE_KEY "change-key" +#define ZFS_DELEG_PERM_PROJECTUSED "projectused" +#define ZFS_DELEG_PERM_PROJECTQUOTA "projectquota" +#define ZFS_DELEG_PERM_PROJECTOBJUSED "projectobjused" +#define ZFS_DELEG_PERM_PROJECTOBJQUOTA "projectobjquota" /* * Note: the names of properties that are marked delegatable are also diff --git a/usr/src/uts/common/fs/zfs/sys/sa.h b/usr/src/uts/common/fs/zfs/sys/sa.h index fe2291c50c..fa44775e13 100644 --- a/usr/src/uts/common/fs/zfs/sys/sa.h +++ b/usr/src/uts/common/fs/zfs/sys/sa.h @@ -155,6 +155,7 @@ void sa_handle_unlock(sa_handle_t *); #ifdef _KERNEL int sa_lookup_uio(sa_handle_t *, sa_attr_type_t, uio_t *); +int sa_add_projid(sa_handle_t *, dmu_tx_t *, uint64_t); #endif #ifdef __cplusplus diff --git a/usr/src/uts/common/fs/zfs/sys/spa_impl.h b/usr/src/uts/common/fs/zfs/sys/spa_impl.h index d63013ce0d..fca07ab645 100644 --- a/usr/src/uts/common/fs/zfs/sys/spa_impl.h +++ b/usr/src/uts/common/fs/zfs/sys/spa_impl.h @@ -407,6 +407,8 @@ struct spa { */ spa_config_lock_t spa_config_lock[SCL_LOCKS]; /* config changes */ zfs_refcount_t spa_refcount; /* number of opens */ + + taskq_t *spa_upgrade_taskq; /* taskq for upgrade jobs */ }; extern const char *spa_config_path; diff --git a/usr/src/uts/common/fs/zfs/sys/zfs_acl.h b/usr/src/uts/common/fs/zfs/sys/zfs_acl.h index 4eefdc563f..6e75cb5756 100644 --- a/usr/src/uts/common/fs/zfs/sys/zfs_acl.h +++ b/usr/src/uts/common/fs/zfs/sys/zfs_acl.h @@ -207,7 +207,7 @@ struct zfsvfs; int zfs_acl_ids_create(struct znode *, int, vattr_t *, cred_t *, vsecattr_t *, zfs_acl_ids_t *); void zfs_acl_ids_free(zfs_acl_ids_t *); -boolean_t zfs_acl_ids_overquota(struct zfsvfs *, zfs_acl_ids_t *); +boolean_t zfs_acl_ids_overquota(struct zfsvfs *, zfs_acl_ids_t *, uint64_t); int zfs_getacl(struct znode *, vsecattr_t *, boolean_t, cred_t *); int zfs_setacl(struct znode *, vsecattr_t *, boolean_t, cred_t *); void zfs_acl_rele(void *); @@ -236,6 +236,7 @@ void zfs_acl_xform(struct znode *, zfs_acl_t *, cred_t *); void zfs_acl_data_locator(void **, uint32_t *, uint32_t, boolean_t, void *); uint64_t zfs_mode_compute(uint64_t, zfs_acl_t *, uint64_t *, uint64_t, uint64_t); +int zfs_acl_node_read(struct znode *, boolean_t, zfs_acl_t **, boolean_t); int zfs_acl_chown_setattr(struct znode *); #endif diff --git a/usr/src/uts/common/fs/zfs/sys/zfs_context.h b/usr/src/uts/common/fs/zfs/sys/zfs_context.h index ebcdc7f111..da0ea3ab2e 100644 --- a/usr/src/uts/common/fs/zfs/sys/zfs_context.h +++ b/usr/src/uts/common/fs/zfs/sys/zfs_context.h @@ -80,6 +80,7 @@ extern "C" { #endif #define likely(x) _zfs_expect((x) != 0, 1) +#define unlikely(x) _zfs_expect((x) != 0, 0) #define CPU_SEQID (CPU->cpu_seqid) diff --git a/usr/src/uts/common/fs/zfs/sys/zfs_project.h b/usr/src/uts/common/fs/zfs/sys/zfs_project.h new file mode 100644 index 0000000000..741c8f322a --- /dev/null +++ b/usr/src/uts/common/fs/zfs/sys/zfs_project.h @@ -0,0 +1,78 @@ +/* + * 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) 2017, Intel Corporation. All rights reserved. + * Copyright 2019 Joyent, Inc. + */ + +#ifndef _SYS_ZFS_PROJECT_H +#define _SYS_ZFS_PROJECT_H + +#ifndef _KERNEL +#ifndef _SYS_MOUNT_H +/* XXX: some hack to avoid include sys/mount.h */ +#define _SYS_MOUNT_H +#endif +#endif + +#ifdef FS_PROJINHERIT_FL +#define ZFS_PROJINHERIT_FL FS_PROJINHERIT_FL +#else +#define ZFS_PROJINHERIT_FL 0x20000000 +#endif + +#ifdef FS_IOC_FSGETXATTR +typedef struct fsxattr zfsxattr_t; + +#define ZFS_IOC_FSGETXATTR FS_IOC_FSGETXATTR +#define ZFS_IOC_FSSETXATTR FS_IOC_FSSETXATTR +#else +#include +typedef struct zfsxattr { + uint32_t fsx_xflags; /* xflags field value (get/set) */ + uint32_t fsx_projid; /* project identifier (get/set) */ +} zfsxattr_t; + +#define ZFS_IOC_FSGETXATTR _IOR('X', 31, zfsxattr_t) +#define ZFS_IOC_FSSETXATTR _IOW('X', 32, zfsxattr_t) +#endif + +#define ZFS_DEFAULT_PROJID (0ULL) +/* + * It is NOT ondisk project ID value. Just means either the object has + * no project ID or the operation does not touch project ID attribute. + */ +#define ZFS_INVALID_PROJID (-1ULL) + +static inline boolean_t +zpl_is_valid_projid(uint32_t projid) +{ + /* + * zfsxattr::fsx_projid is 32-bits, when convert to uint64_t, + * the higher 32-bits will be set as zero, so cannot directly + * compare with ZFS_INVALID_PROJID (-1ULL) + */ + if ((uint32_t)ZFS_INVALID_PROJID == projid) + return (B_FALSE); + return (B_TRUE); +} + +#endif /* _SYS_ZFS_PROJECT_H */ diff --git a/usr/src/uts/common/fs/zfs/sys/zfs_sa.h b/usr/src/uts/common/fs/zfs/sys/zfs_sa.h index cd312b27a9..7877c8f730 100644 --- a/usr/src/uts/common/fs/zfs/sys/zfs_sa.h +++ b/usr/src/uts/common/fs/zfs/sys/zfs_sa.h @@ -73,6 +73,7 @@ typedef enum zpl_attr { ZPL_SYMLINK, ZPL_SCANSTAMP, ZPL_DACL_ACES, + ZPL_PROJID, ZPL_END } zpl_attr_t; @@ -86,6 +87,8 @@ typedef enum zpl_attr { #define SA_UID_OFFSET 24 #define SA_GID_OFFSET 32 #define SA_PARENT_OFFSET 40 +#define SA_FLAGS_OFFSET 48 +#define SA_PROJID_OFFSET 128 extern sa_attr_reg_t zfs_attr_table[ZPL_END + 1]; extern sa_attr_reg_t zfs_legacy_attr_table[ZPL_END + 1]; diff --git a/usr/src/uts/common/fs/zfs/sys/zfs_vfsops.h b/usr/src/uts/common/fs/zfs/sys/zfs_vfsops.h index 25d9d19dd6..13be805ccd 100644 --- a/usr/src/uts/common/fs/zfs/sys/zfs_vfsops.h +++ b/usr/src/uts/common/fs/zfs/sys/zfs_vfsops.h @@ -80,6 +80,10 @@ struct zfsvfs { 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_projectquota_obj; + uint64_t z_projectobjquota_obj; uint64_t z_replay_eof; /* New end of file - replay only */ sa_attr_type_t *z_attr_table; /* SA attr mapping->id */ #define ZFS_OBJ_MTX_SZ 64 @@ -144,10 +148,12 @@ extern int zfs_userspace_many(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, uint64_t *cookiep, void *vbuf, uint64_t *bufsizep); extern int zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, const char *domain, uint64_t rid, uint64_t quota); -extern boolean_t zfs_owner_overquota(zfsvfs_t *zfsvfs, struct znode *, - boolean_t isgroup); -extern boolean_t zfs_fuid_overquota(zfsvfs_t *zfsvfs, boolean_t isgroup, - uint64_t fuid); +extern boolean_t zfs_id_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, + uint64_t id); +extern boolean_t zfs_id_overobjquota(zfsvfs_t *zfsvfs, uint64_t usedobj, + uint64_t id); +extern boolean_t zfs_id_overquota(zfsvfs_t *zfsvfs, uint64_t usedobj, + uint64_t id); extern int zfs_set_version(zfsvfs_t *zfsvfs, uint64_t newvers); extern int zfsvfs_create(const char *name, zfsvfs_t **zfvp); extern int zfsvfs_create_impl(zfsvfs_t **zfvp, zfsvfs_t *zfsvfs, objset_t *os); diff --git a/usr/src/uts/common/fs/zfs/sys/zfs_znode.h b/usr/src/uts/common/fs/zfs/sys/zfs_znode.h index a9f9876530..c70eeec4ba 100644 --- a/usr/src/uts/common/fs/zfs/sys/zfs_znode.h +++ b/usr/src/uts/common/fs/zfs/sys/zfs_znode.h @@ -43,6 +43,7 @@ #endif #include #include +#include #ifdef __cplusplus extern "C" { @@ -50,7 +51,7 @@ extern "C" { /* * Additional file level attributes, that are stored - * in the upper half of zp_flags + * in the upper half of z_pflags */ #define ZFS_READONLY 0x0000000100000000 #define ZFS_HIDDEN 0x0000000200000000 @@ -66,6 +67,16 @@ extern "C" { #define ZFS_REPARSE 0x0000080000000000 #define ZFS_OFFLINE 0x0000100000000000 #define ZFS_SPARSE 0x0000200000000000 +/* + * PROJINHERIT attribute is used to indicate that the child object under the + * directory which has the PROJINHERIT attribute needs to inherit its parent + * project ID that is used by project quota. + */ +#define ZFS_PROJINHERIT 0x0000400000000000 +/* + * PROJID attr is used internally to indicate that the object has project ID. + */ +#define ZFS_PROJID 0x0000800000000000 #define ZFS_ATTR_SET(zp, attr, value, pflags, tx) \ { \ @@ -110,6 +121,7 @@ extern "C" { #define SA_ZPL_SIZE(z) z->z_attr_table[ZPL_SIZE] #define SA_ZPL_ZNODE_ACL(z) z->z_attr_table[ZPL_ZNODE_ACL] #define SA_ZPL_PAD(z) z->z_attr_table[ZPL_PAD] +#define SA_ZPL_PROJID(z) z->z_attr_table[ZPL_PROJID] /* * Is ID ephemeral? @@ -128,7 +140,7 @@ extern "C" { /* * Special attributes for master node. - * "userquota@" and "groupquota@" are also valid (from + * "userquota@", "groupquota@" and "projectquota@" are also valid (from * zfs_userquota_prop_prefixes[]). */ #define ZFS_FSID "FSID" @@ -197,11 +209,18 @@ typedef struct znode { uint32_t z_sync_cnt; /* synchronous open count */ kmutex_t z_acl_lock; /* acl data lock */ zfs_acl_t *z_acl_cached; /* cached acl */ + uint64_t z_projid; /* project ID */ list_node_t z_link_node; /* all znodes in fs link */ sa_handle_t *z_sa_hdl; /* handle to sa data */ boolean_t z_is_sa; /* are we native sa? */ } znode_t; +static inline uint64_t +zfs_inherit_projid(znode_t *dzp) +{ + return ((dzp->z_pflags & ZFS_PROJINHERIT) ? dzp->z_projid : + ZFS_DEFAULT_PROJID); +} /* * Range locking rules diff --git a/usr/src/uts/common/fs/zfs/zfs_acl.c b/usr/src/uts/common/fs/zfs/zfs_acl.c index be97c64514..0570da6427 100644 --- a/usr/src/uts/common/fs/zfs/zfs_acl.c +++ b/usr/src/uts/common/fs/zfs/zfs_acl.c @@ -1057,8 +1057,8 @@ zfs_mode_compute(uint64_t fmode, zfs_acl_t *aclp, * Read an external acl object. If the intent is to modify, always * create a new acl and leave any cached acl in place. */ -static int -zfs_acl_node_read(znode_t *zp, boolean_t have_lock, zfs_acl_t **aclpp, +int +zfs_acl_node_read(struct znode *zp, boolean_t have_lock, zfs_acl_t **aclpp, boolean_t will_modify) { zfs_acl_t *aclp; @@ -1771,10 +1771,12 @@ zfs_acl_ids_free(zfs_acl_ids_t *acl_ids) } boolean_t -zfs_acl_ids_overquota(zfsvfs_t *zfsvfs, zfs_acl_ids_t *acl_ids) +zfs_acl_ids_overquota(zfsvfs_t *zv, zfs_acl_ids_t *acl_ids, uint64_t projid) { - return (zfs_fuid_overquota(zfsvfs, B_FALSE, acl_ids->z_fuid) || - zfs_fuid_overquota(zfsvfs, B_TRUE, acl_ids->z_fgid)); + return (zfs_id_overquota(zv, DMU_USERUSED_OBJECT, acl_ids->z_fuid) || + zfs_id_overquota(zv, DMU_GROUPUSED_OBJECT, acl_ids->z_fgid) || + (projid != ZFS_DEFAULT_PROJID && projid != ZFS_INVALID_PROJID && + zfs_id_overquota(zv, DMU_PROJECTUSED_OBJECT, projid))); } /* diff --git a/usr/src/uts/common/fs/zfs/zfs_dir.c b/usr/src/uts/common/fs/zfs/zfs_dir.c index ad78295a54..990abb784f 100644 --- a/usr/src/uts/common/fs/zfs/zfs_dir.c +++ b/usr/src/uts/common/fs/zfs/zfs_dir.c @@ -980,7 +980,7 @@ zfs_make_xattrdir(znode_t *zp, vattr_t *vap, vnode_t **xvpp, cred_t *cr) if ((error = zfs_acl_ids_create(zp, IS_XATTR, vap, cr, NULL, &acl_ids)) != 0) return (error); - if (zfs_acl_ids_overquota(zfsvfs, &acl_ids)) { + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, zp->z_projid)) { zfs_acl_ids_free(&acl_ids); return (SET_ERROR(EDQUOT)); } diff --git a/usr/src/uts/common/fs/zfs/zfs_ioctl.c b/usr/src/uts/common/fs/zfs/zfs_ioctl.c index e220ce9dbe..4e0da09ab4 100644 --- a/usr/src/uts/common/fs/zfs/zfs_ioctl.c +++ b/usr/src/uts/common/fs/zfs/zfs_ioctl.c @@ -248,9 +248,18 @@ 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, + ZFS_DELEG_PERM_PROJECTUSED, + ZFS_DELEG_PERM_PROJECTQUOTA, + ZFS_DELEG_PERM_PROJECTOBJUSED, + ZFS_DELEG_PERM_PROJECTOBJQUOTA, }; static int zfs_ioc_userspace_upgrade(zfs_cmd_t *zc); +static int zfs_ioc_id_quota_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, @@ -1205,13 +1214,19 @@ 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 { + } else if (zc->zc_objset_type == ZFS_PROP_GROUPUSED || + zc->zc_objset_type == ZFS_PROP_GROUPQUOTA || + zc->zc_objset_type == ZFS_PROP_GROUPOBJUSED || + zc->zc_objset_type == ZFS_PROP_GROUPOBJQUOTA) { if (groupmember(zc->zc_guid, cr)) return (0); } + /* else is for project quota/used */ } return (zfs_secpolicy_write_perms(zc->zc_name, @@ -2553,6 +2568,7 @@ zfs_prop_set_special(const char *dsname, zprop_source_t source, zc = kmem_zalloc(sizeof (zfs_cmd_t), KM_SLEEP); (void) strcpy(zc->zc_name, dsname); (void) zfs_ioc_userspace_upgrade(zc); + (void) zfs_ioc_id_quota_upgrade(zc); kmem_free(zc, sizeof (zfs_cmd_t)); } break; @@ -4043,15 +4059,35 @@ 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]; + const char *pq_prefix = + zfs_userquota_prop_prefixes[ZFS_PROP_PROJECTQUOTA]; + const char *piq_prefix = zfs_userquota_prop_prefixes[\ + ZFS_PROP_PROJECTOBJQUOTA]; 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 if (strncmp(propname, pq_prefix, + strlen(pq_prefix)) == 0) { + perm = ZFS_DELEG_PERM_PROJECTQUOTA; + } else if (strncmp(propname, piq_prefix, + strlen(piq_prefix)) == 0) { + perm = ZFS_DELEG_PERM_PROJECTOBJQUOTA; } else { - /* USERUSED and GROUPUSED are read-only */ + /* {USER|GROUP|PROJECT}USED are read-only */ return (SET_ERROR(EINVAL)); } @@ -5244,7 +5280,7 @@ zfs_ioc_promote(zfs_cmd_t *zc) } /* - * Retrieve a single {user|group}{used|quota}@... property. + * Retrieve a single {user|group|project}{used|quota}@... property. * * inputs: * zc_name name of filesystem @@ -5362,6 +5398,49 @@ zfs_ioc_userspace_upgrade(zfs_cmd_t *zc) return (error); } +/* + * inputs: + * zc_name name of filesystem + * + * outputs: + * none + */ +static int +zfs_ioc_id_quota_upgrade(zfs_cmd_t *zc) +{ + objset_t *os; + int error; + + error = dmu_objset_hold(zc->zc_name, FTAG, &os); + if (error != 0) + return (error); + + dsl_dataset_long_hold(dmu_objset_ds(os), FTAG); + dsl_pool_rele(dmu_objset_pool(os), FTAG); + + if (dmu_objset_userobjspace_upgradable(os) || + dmu_objset_projectquota_upgradable(os)) { + mutex_enter(&os->os_upgrade_lock); + if (os->os_upgrade_id == 0) { + /* clear potential error code and retry */ + os->os_upgrade_status = 0; + mutex_exit(&os->os_upgrade_lock); + + dmu_objset_id_quota_upgrade(os); + } else { + mutex_exit(&os->os_upgrade_lock); + } + + taskq_wait_id(os->os_spa->spa_upgrade_taskq, os->os_upgrade_id); + error = os->os_upgrade_status; + } + + dsl_dataset_long_rele(dmu_objset_ds(os), FTAG); + dsl_dataset_rele(dmu_objset_ds(os), FTAG); + + return (error); +} + /* * We don't want to have a hard dependency * against some special symbols in sharefs diff --git a/usr/src/uts/common/fs/zfs/zfs_log.c b/usr/src/uts/common/fs/zfs/zfs_log.c index 1afaa8434b..7d3e1cc42a 100644 --- a/usr/src/uts/common/fs/zfs/zfs_log.c +++ b/usr/src/uts/common/fs/zfs/zfs_log.c @@ -166,8 +166,17 @@ zfs_log_xvattr(lr_attr_t *lrattr, xvattr_t *xvap) XAT0_AV_MODIFIED; if (XVA_ISSET_REQ(xvap, XAT_CREATETIME)) ZFS_TIME_ENCODE(&xoap->xoa_createtime, crtime); - if (XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) + if (XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) { + ASSERT(!XVA_ISSET_REQ(xvap, XAT_PROJID)); + bcopy(xoap->xoa_av_scanstamp, scanstamp, AV_SCANSTAMP_SZ); + } else if (XVA_ISSET_REQ(xvap, XAT_PROJID)) { + /* + * XAT_PROJID and XAT_AV_SCANSTAMP will never be valid + * at the same time, so we can share the same space. + */ + bcopy(&xoap->xoa_projid, scanstamp, sizeof (uint64_t)); + } if (XVA_ISSET_REQ(xvap, XAT_REPARSE)) *attrs |= (xoap->xoa_reparse == 0) ? 0 : XAT0_REPARSE; @@ -177,6 +186,9 @@ zfs_log_xvattr(lr_attr_t *lrattr, xvattr_t *xvap) if (XVA_ISSET_REQ(xvap, XAT_SPARSE)) *attrs |= (xoap->xoa_sparse == 0) ? 0 : XAT0_SPARSE; + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) + *attrs |= (xoap->xoa_projinherit == 0) ? 0 : + XAT0_PROJINHERIT; } static void * diff --git a/usr/src/uts/common/fs/zfs/zfs_replay.c b/usr/src/uts/common/fs/zfs/zfs_replay.c index f75ec48cd7..969a56dc9b 100644 --- a/usr/src/uts/common/fs/zfs/zfs_replay.c +++ b/usr/src/uts/common/fs/zfs/zfs_replay.c @@ -125,14 +125,25 @@ zfs_replay_xvattr(lr_attr_t *lrattr, xvattr_t *xvap) ((*attrs & XAT0_AV_QUARANTINED) != 0); if (XVA_ISSET_REQ(xvap, XAT_CREATETIME)) ZFS_TIME_DECODE(&xoap->xoa_createtime, crtime); - if (XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) + if (XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) { + ASSERT(!XVA_ISSET_REQ(xvap, XAT_PROJID)); + bcopy(scanstamp, xoap->xoa_av_scanstamp, AV_SCANSTAMP_SZ); + } else if (XVA_ISSET_REQ(xvap, XAT_PROJID)) { + /* + * XAT_PROJID and XAT_AV_SCANSTAMP will never be valid + * at the same time, so we can share the same space. + */ + bcopy(scanstamp, &xoap->xoa_projid, sizeof (uint64_t)); + } if (XVA_ISSET_REQ(xvap, XAT_REPARSE)) xoap->xoa_reparse = ((*attrs & XAT0_REPARSE) != 0); if (XVA_ISSET_REQ(xvap, XAT_OFFLINE)) xoap->xoa_offline = ((*attrs & XAT0_OFFLINE) != 0); if (XVA_ISSET_REQ(xvap, XAT_SPARSE)) xoap->xoa_sparse = ((*attrs & XAT0_SPARSE) != 0); + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) + xoap->xoa_projinherit = ((*attrs & XAT0_PROJINHERIT) != 0); } static int diff --git a/usr/src/uts/common/fs/zfs/zfs_sa.c b/usr/src/uts/common/fs/zfs/zfs_sa.c index a39cff1a7b..4202fb5f3d 100644 --- a/usr/src/uts/common/fs/zfs/zfs_sa.c +++ b/usr/src/uts/common/fs/zfs/zfs_sa.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include /* * ZPL attribute registration table. @@ -62,6 +64,7 @@ sa_attr_reg_t zfs_attr_table[ZPL_END+1] = { {"ZPL_SYMLINK", 0, SA_UINT8_ARRAY, 0}, {"ZPL_SCANSTAMP", 32, SA_UINT8_ARRAY, 0}, {"ZPL_DACL_ACES", 0, SA_ACL, 0}, + {"ZPL_PROJID", sizeof (uint64_t), SA_UINT64_ARRAY, 0}, {NULL, 0, 0, 0} }; @@ -196,7 +199,7 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) dmu_buf_t *db = sa_get_db(hdl); znode_t *zp = sa_get_userdata(hdl); zfsvfs_t *zfsvfs = zp->z_zfsvfs; - sa_bulk_attr_t bulk[20]; + sa_bulk_attr_t bulk[22]; int count = 0; sa_bulk_attr_t sa_attrs[20] = { 0 }; zfs_acl_locator_cb_t locate = { 0 }; @@ -247,6 +250,11 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) if (sa_bulk_lookup_locked(hdl, bulk, count) != 0) goto done; + if (dmu_objset_projectquota_enabled(hdl->sa_os) && + !(zp->z_pflags & ZFS_PROJID)) { + zp->z_pflags |= ZFS_PROJID; + zp->z_projid = ZFS_DEFAULT_PROJID; + } /* * While the order here doesn't matter its best to try and organize @@ -274,6 +282,9 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) &crtime, 16); SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_LINKS(zfsvfs), NULL, &zp->z_links, 8); + if (dmu_objset_projectquota_enabled(hdl->sa_os)) + SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_PROJID(zfsvfs), NULL, + &zp->z_projid, 8); if (zp->z_vnode->v_type == VBLK || zp->z_vnode->v_type == VCHR) SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_RDEV(zfsvfs), NULL, &rdev, 8); diff --git a/usr/src/uts/common/fs/zfs/zfs_vfsops.c b/usr/src/uts/common/fs/zfs/zfs_vfsops.c index dfd13539cd..93339107c1 100644 --- a/usr/src/uts/common/fs/zfs/zfs_vfsops.c +++ b/usr/src/uts/common/fs/zfs/zfs_vfsops.c @@ -23,6 +23,7 @@ * Copyright (c) 2012, 2015 by Delphix. All rights reserved. * Copyright (c) 2014 Integros [integros.com] * Copyright 2016 Nexenta Systems, Inc. All rights reserved. + * Copyright 2019 Joyent, Inc. */ /* Portions Copyright 2010 Robert Milkowski */ @@ -564,8 +565,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? */ @@ -585,42 +592,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; + VERIFY3U(sa.sa_magic, ==, SA_MAGIC); + } - ASSERT(bonustype == DMU_OT_SA); + hdrsize = sa_hdrsize(&sa); + VERIFY3U(hdrsize, >=, sizeof (sa_hdr_phys_t)); - 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); - } + *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); - 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); - } + 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); } @@ -647,15 +661,29 @@ 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); } - return (0); } int @@ -667,16 +695,34 @@ zfs_userspace_many(zfsvfs_t *zfsvfs, 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(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 == 0) { + 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)) { @@ -684,7 +730,15 @@ zfs_userspace_many(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, *bufsizep) break; - fuidstr_to_sid(zfsvfs, za.za_name, + /* + * 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; @@ -724,7 +778,8 @@ int zfs_userspace_one(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, const char *domain, uint64_t rid, uint64_t *valp) { - char buf[32]; + char buf[20 + DMU_OBJACCT_PREFIX_LEN]; + int offset = 0; int err; uint64_t obj; @@ -733,11 +788,33 @@ zfs_userspace_one(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, 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 == 0) + if (obj == ZFS_NO_OBJECT) return (0); - err = id_to_fuidstr(zfsvfs, domain, rid, buf, B_FALSE); + if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED || + type == ZFS_PROP_PROJECTOBJUSED) { + strncpy(buf, DMU_OBJACCT_PREFIX, DMU_OBJACCT_PREFIX_LEN); + offset = DMU_OBJACCT_PREFIX_LEN; + } + + err = id_to_fuidstr(zfsvfs, domain, rid, buf + offset, B_FALSE); if (err) return (err); @@ -757,14 +834,41 @@ zfs_set_userquota(zfsvfs_t *zfsvfs, 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 (zfsvfs->z_version < ZPL_VERSION_USERSPACE) return (SET_ERROR(ENOTSUP)); - objp = (type == ZFS_PROP_USERQUOTA) ? &zfsvfs->z_userquota_obj : - &zfsvfs->z_groupquota_obj; + 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 = id_to_fuidstr(zfsvfs, domain, rid, buf, B_TRUE); if (err) @@ -809,23 +913,46 @@ zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, } boolean_t -zfs_fuid_overquota(zfsvfs_t *zfsvfs, boolean_t isgroup, uint64_t fuid) +zfs_id_overobjquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id) { - char buf[32]; - uint64_t used, quota, usedobj, quotaobj; + char buf[20 + DMU_OBJACCT_PREFIX_LEN]; + 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 (!dmu_objset_userobjspace_present(zfsvfs->z_os)) { + if (dmu_objset_userobjspace_upgradable(zfsvfs->z_os)) + dmu_objset_id_quota_upgrade(zfsvfs->z_os); + 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)fuid); + (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); @@ -833,19 +960,50 @@ 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_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id) { - uint64_t fuid; - uint64_t quotaobj; + char buf[20]; + uint64_t used, quota, quotaobj; + int err; - 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); - fuid = isgroup ? zp->z_gid : zp->z_uid; + (void) sprintf(buf, "%llx", (longlong_t)id); + err = zap_lookup(zfsvfs->z_os, quotaobj, buf, 8, 1, "a); + if (err != 0) + return (B_FALSE); - if (quotaobj == 0 || zfsvfs->z_replay) + err = zap_lookup(zfsvfs->z_os, usedobj, buf, 8, 1, &used); + if (err != 0) return (B_FALSE); + return (used >= quota); +} - return (zfs_fuid_overquota(zfsvfs, isgroup, fuid)); +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)); } /* @@ -944,6 +1102,38 @@ 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); + if (error == ENOENT) + zfsvfs->z_userobjquota_obj = 0; + else if (error != 0) + return (error); + + error = zap_lookup(os, MASTER_NODE_OBJ, + zfs_userquota_prop_prefixes[ZFS_PROP_GROUPOBJQUOTA], + 8, 1, &zfsvfs->z_groupobjquota_obj); + if (error == ENOENT) + zfsvfs->z_groupobjquota_obj = 0; + 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) @@ -967,15 +1157,11 @@ zfsvfs_create(const char *osname, zfsvfs_t **zfvp) objset_t *os; zfsvfs_t *zfsvfs; int error; + boolean_t ro = (strchr(osname, '@') != NULL); zfsvfs = kmem_zalloc(sizeof (zfsvfs_t), KM_SLEEP); - /* - * We claim to always be readonly so we can open snapshots; - * other ZPL code will prevent us from writing to snapshots. - */ - error = dmu_objset_own(osname, DMU_OST_ZFS, B_TRUE, B_TRUE, zfsvfs, - &os); + error = dmu_objset_own(osname, DMU_OST_ZFS, ro, B_TRUE, zfsvfs, &os); if (error != 0) { kmem_free(zfsvfs, sizeof (zfsvfs_t)); return (error); @@ -1338,6 +1524,83 @@ zfs_check_global_label(const char *dsname, const char *hexsl) return (SET_ERROR(EACCES)); } +static int +zfs_statfs_project(zfsvfs_t *zfsvfs, znode_t *zp, struct statvfs64 *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, "a); + 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, "a); + 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); +} + /* * Determine whether the mount is allowed according to MAC check. * by comparing (where appropriate) label of the dataset against @@ -1662,6 +1925,7 @@ zfs_statvfs(vfs_t *vfsp, struct statvfs64 *statp) zfsvfs_t *zfsvfs = vfsp->vfs_data; dev32_t d32; uint64_t refdbytes, availbytes, usedobjs, availobjs; + int err = 0; ZFS_ENTER(zfsvfs); @@ -1716,8 +1980,33 @@ zfs_statvfs(vfs_t *vfsp, struct statvfs64 *statp) */ bzero(statp->f_fstr, sizeof (statp->f_fstr)); + if (dmu_objset_projectquota_enabled(zfsvfs->z_os) && + dmu_objset_projectquota_present(zfsvfs->z_os)) { + znode_t *zp; + + /* + * In ZoL, zfs_statvfs is passed a Linux dentry (directory + * entry), instead of a vfsp. The ZoL code uses the dentry + * to get the znode from the dentry's inode. This represents + * whatever filename was passed to the user-level statvfs + * syscall. + * + * We're using the VFS root znode here, so this represents a + * potential difference from ZoL. + */ + if (zfs_zget(zfsvfs, zfsvfs->z_root, &zp) == 0) { + uint32_t bshift = ddi_fls(statp->f_bsize) - 1; + + if (zp->z_pflags & ZFS_PROJINHERIT && zp->z_projid && + zpl_is_valid_projid(zp->z_projid)) + err = zfs_statfs_project(zfsvfs, zp, statp, + bshift); + VN_RELE(ZTOV(zp)); + } + } + ZFS_EXIT(zfsvfs); - return (0); + return (err); } static int diff --git a/usr/src/uts/common/fs/zfs/zfs_vnops.c b/usr/src/uts/common/fs/zfs/zfs_vnops.c index 87c2d64dd3..b19ef3698b 100644 --- a/usr/src/uts/common/fs/zfs/zfs_vnops.c +++ b/usr/src/uts/common/fs/zfs/zfs_vnops.c @@ -84,6 +84,8 @@ #include #include #include +#include +#include /* * Programming rules. @@ -296,6 +298,57 @@ zfs_holey(vnode_t *vp, int cmd, offset_t *off) return (error); } +static int +zfs_ioctl_getxattr(vnode_t *vp, intptr_t data, int flag, cred_t *cr, + caller_context_t *ct) +{ + zfsxattr_t fsx = { 0 }; + znode_t *zp = VTOZ(vp); + + if (zp->z_pflags & ZFS_PROJINHERIT) + fsx.fsx_xflags = ZFS_PROJINHERIT_FL; + if (zp->z_pflags & ZFS_PROJID) + fsx.fsx_projid = zp->z_projid; + if (ddi_copyout(&fsx, (void *)data, sizeof (fsx), flag)) + return (SET_ERROR(EFAULT)); + + return (0); +} + +static int zfs_setattr(vnode_t *, vattr_t *, int, cred_t *, caller_context_t *); + +static int +zfs_ioctl_setxattr(vnode_t *vp, intptr_t data, int flags, cred_t *cr, + caller_context_t *ct) +{ + znode_t *zp = VTOZ(vp); + zfsxattr_t fsx; + xvattr_t xva; + xoptattr_t *xoap; + int err; + + if (ddi_copyin((void *)data, &fsx, sizeof (fsx), flags)) + return (SET_ERROR(EFAULT)); + + if (!zpl_is_valid_projid(fsx.fsx_projid)) + return (SET_ERROR(EINVAL)); + + if (fsx.fsx_xflags & ~ZFS_PROJINHERIT_FL) + return (SET_ERROR(EOPNOTSUPP)); + + xva_init(&xva); + xoap = xva_getxoptattr(&xva); + + XVA_SET_REQ(&xva, XAT_PROJINHERIT); + if (fsx.fsx_xflags & ZFS_PROJINHERIT_FL) + xoap->xoa_projinherit = B_TRUE; + + XVA_SET_REQ(&xva, XAT_PROJID); + xoap->xoa_projid = fsx.fsx_projid; + + return (zfs_setattr(vp, (vattr_t *)&xva, flags, cr, ct)); +} + /* ARGSUSED */ static int zfs_ioctl(vnode_t *vp, int com, intptr_t data, int flag, cred_t *cred, @@ -383,6 +436,10 @@ zfs_ioctl(vnode_t *vp, int com, intptr_t data, int flag, cred_t *cred, return (SET_ERROR(EFAULT)); return (0); } + case ZFS_IOC_FSGETXATTR: + return (zfs_ioctl_getxattr(vp, data, flag, cred, ct)); + case ZFS_IOC_FSSETXATTR: + return (zfs_ioctl_setxattr(vp, data, flag, cred, ct)); } return (SET_ERROR(ENOTTY)); } @@ -811,17 +868,21 @@ zfs_write(vnode_t *vp, uio_t *uio, int ioflag, cred_t *cr, caller_context_t *ct) * and allows us to do more fine-grained space accounting. */ while (n > 0) { - abuf = NULL; woff = uio->uio_loffset; - if (zfs_owner_overquota(zfsvfs, zp, B_FALSE) || - zfs_owner_overquota(zfsvfs, zp, B_TRUE)) { - if (abuf != NULL) - dmu_return_arcbuf(abuf); + + if (zfs_id_overblockquota(zfsvfs, DMU_USERUSED_OBJECT, + zp->z_uid) || + zfs_id_overblockquota(zfsvfs, DMU_GROUPUSED_OBJECT, + zp->z_gid) || + (zp->z_projid != ZFS_DEFAULT_PROJID && + zfs_id_overblockquota(zfsvfs, DMU_PROJECTUSED_OBJECT, + zp->z_projid))) { error = SET_ERROR(EDQUOT); break; } - if (xuio && abuf == NULL) { + arc_buf_t *abuf = NULL; + if (xuio) { ASSERT(i_iov < iovcnt); aiov = &iovp[i_iov]; abuf = dmu_xuio_arcbuf(xuio, i_iov); @@ -832,8 +893,7 @@ zfs_write(vnode_t *vp, uio_t *uio, int ioflag, cred_t *cr, caller_context_t *ct) ((char *)aiov->iov_base - (char *)abuf->b_data + aiov->iov_len == arc_buf_size(abuf))); i_iov++; - } else if (abuf == NULL && n >= max_blksz && - woff >= zp->z_size && + } else if (n >= max_blksz && woff >= zp->z_size && P2PHASE(woff, max_blksz) == 0 && zp->z_blksz == max_blksz) { /* @@ -1493,6 +1553,7 @@ top: if (zp == NULL) { uint64_t txtype; + uint64_t projid = ZFS_DEFAULT_PROJID; /* * Create a new file object and update the directory @@ -1522,7 +1583,9 @@ top: goto out; have_acl = B_TRUE; - if (zfs_acl_ids_overquota(zfsvfs, &acl_ids)) { + if (vap->va_type == VREG || vap->va_type == VDIR) + projid = zfs_inherit_projid(dzp); + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, projid)) { zfs_acl_ids_free(&acl_ids); error = SET_ERROR(EDQUOT); goto out; @@ -1987,7 +2050,7 @@ top: return (error); } - if (zfs_acl_ids_overquota(zfsvfs, &acl_ids)) { + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, zfs_inherit_projid(dzp))) { zfs_acl_ids_free(&acl_ids); zfs_dirent_unlock(dl); ZFS_EXIT(zfsvfs); @@ -2694,6 +2757,17 @@ zfs_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr, ((zp->z_pflags & ZFS_SPARSE) != 0); XVA_SET_RTN(xvap, XAT_SPARSE); } + + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) { + xoap->xoa_projinherit = + ((zp->z_pflags & ZFS_PROJINHERIT) != 0); + XVA_SET_RTN(xvap, XAT_PROJINHERIT); + } + + if (XVA_ISSET_REQ(xvap, XAT_PROJID)) { + xoap->xoa_projid = zp->z_projid; + XVA_SET_RTN(xvap, XAT_PROJID); + } } ZFS_TIME_DECODE(&vap->va_atime, zp->z_atime); @@ -2715,6 +2789,119 @@ zfs_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr, return (0); } +/* + * For the operation of changing file's user/group/project, we need to + * handle not only the main object that is assigned to the file directly, + * but also the ones that are used by the file via hidden xattr directory. + * + * Because the xattr directory may contain many EA entries, it may be + * impossible to change all of them in the same transaction as changing the + * main object's user/group/project attributes. If so, we have to change them + * via other multiple independent transactions one by one. It may be not a good + * solution, but we have no better idea yet. + */ +static int +zfs_setattr_dir(znode_t *dzp) +{ + zfsvfs_t *zfsvfs = dzp->z_zfsvfs; + objset_t *os = zfsvfs->z_os; + zap_cursor_t zc; + zap_attribute_t zap; + zfs_dirlock_t *dl; + znode_t *zp = NULL; + dmu_tx_t *tx = NULL; + sa_bulk_attr_t bulk[4]; + int count = 0; + int err; + + zap_cursor_init(&zc, os, dzp->z_id); + while ((err = zap_cursor_retrieve(&zc, &zap)) == 0) { + if (zap.za_integer_length != 8 || zap.za_num_integers != 1) { + err = ENXIO; + break; + } + + err = zfs_dirent_lock(&dl, dzp, (char *)zap.za_name, &zp, + ZEXISTS, NULL, NULL); + if (err == ENOENT) + goto next; + if (err) + break; + + if (zp->z_uid == dzp->z_uid && + zp->z_gid == dzp->z_gid && + zp->z_projid == dzp->z_projid) + goto next; + + tx = dmu_tx_create(os); + if (!(zp->z_pflags & ZFS_PROJID)) + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE); + else + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); + + err = dmu_tx_assign(tx, TXG_WAIT); + if (err) + break; + + mutex_enter(&dzp->z_lock); + + if (zp->z_uid != dzp->z_uid) { + zp->z_uid = dzp->z_uid; + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_UID(zfsvfs), NULL, + &dzp->z_uid, sizeof (dzp->z_uid)); + } + + if (zp->z_gid != dzp->z_gid) { + zp->z_gid = dzp->z_gid; + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GID(zfsvfs), NULL, + &dzp->z_gid, sizeof (dzp->z_gid)); + } + + if (zp->z_projid != dzp->z_projid) { + if (!(zp->z_pflags & ZFS_PROJID)) { + zp->z_pflags |= ZFS_PROJID; + SA_ADD_BULK_ATTR(bulk, count, + SA_ZPL_FLAGS(zfsvfs), NULL, &zp->z_pflags, + sizeof (zp->z_pflags)); + } + + zp->z_projid = dzp->z_projid; + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_PROJID(zfsvfs), + NULL, &zp->z_projid, sizeof (zp->z_projid)); + } + + mutex_exit(&dzp->z_lock); + + if (likely(count > 0)) { + err = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); + dmu_tx_commit(tx); + } else { + dmu_tx_abort(tx); + } + tx = NULL; + if (err != 0 && err != ENOENT) + break; + +next: + if (zp) { + VN_RELE(ZTOV(zp)); + zp = NULL; + zfs_dirent_unlock(dl); + } + zap_cursor_advance(&zc); + } + + if (tx) + dmu_tx_abort(tx); + if (zp) { + VN_RELE(ZTOV(zp)); + zfs_dirent_unlock(dl); + } + zap_cursor_fini(&zc); + + return (err == ENOENT ? 0 : err); +} + /* * Set the file attributes to the values contained in the * vattr structure. @@ -2739,6 +2926,7 @@ zfs_setattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr, { znode_t *zp = VTOZ(vp); zfsvfs_t *zfsvfs = zp->z_zfsvfs; + objset_t *os = zfsvfs->z_os; zilog_t *zilog; dmu_tx_t *tx; vattr_t oldva; @@ -2750,16 +2938,18 @@ zfs_setattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr, uint64_t new_uid, new_gid; uint64_t xattr_obj; uint64_t mtime[2], ctime[2]; + uint64_t projid = ZFS_INVALID_PROJID; znode_t *attrzp; int need_policy = FALSE; - int err, err2; + int err, err2 = 0; zfs_fuid_info_t *fuidp = NULL; xvattr_t *xvap = (xvattr_t *)vap; /* vap may be an xvattr_t * */ xoptattr_t *xoap; zfs_acl_t *aclp; boolean_t skipaclchk = (flags & ATTR_NOACLCHECK) ? B_TRUE : B_FALSE; boolean_t fuid_dirtied = B_FALSE; - sa_bulk_attr_t bulk[7], xattr_bulk[7]; + boolean_t handle_eadir = B_FALSE; + sa_bulk_attr_t bulk[8], xattr_bulk[8]; int count = 0, xattr_count = 0; if (mask == 0) @@ -2771,6 +2961,39 @@ zfs_setattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr, ZFS_ENTER(zfsvfs); ZFS_VERIFY_ZP(zp); + /* + * If this is a xvattr_t, then get a pointer to the structure of + * optional attributes. If this is NULL, then we have a vattr_t. + */ + xoap = xva_getxoptattr(xvap); + if (xoap != NULL && (mask & AT_XVATTR)) { + if (XVA_ISSET_REQ(xvap, XAT_PROJID)) { + if (!dmu_objset_projectquota_enabled(os) || + (vp->v_type != VREG && vp->v_type != VDIR)) { + ZFS_EXIT(zfsvfs); + return (SET_ERROR(ENOTSUP)); + } + + projid = xoap->xoa_projid; + if (unlikely(projid == ZFS_INVALID_PROJID)) { + ZFS_EXIT(zfsvfs); + return (SET_ERROR(EINVAL)); + } + + if (projid == zp->z_projid && zp->z_pflags & ZFS_PROJID) + projid = ZFS_INVALID_PROJID; + else + need_policy = TRUE; + } + + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT) && + (!dmu_objset_projectquota_enabled(os) || + (vp->v_type != VREG && vp->v_type != VDIR))) { + ZFS_EXIT(zfsvfs); + return (SET_ERROR(ENOTSUP)); + } + } + zilog = zfsvfs->z_log; /* @@ -2796,12 +3019,6 @@ zfs_setattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr, return (SET_ERROR(EINVAL)); } - /* - * If this is an xvattr_t, then get a pointer to the structure of - * optional attributes. If this is NULL, then we have a vattr_t. - */ - xoap = xva_getxoptattr(xvap); - xva_init(&tmpxvattr); /* @@ -2950,6 +3167,16 @@ top: } } + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) { + if (xoap->xoa_projinherit != + ((zp->z_pflags & ZFS_PROJINHERIT) != 0)) { + need_policy = TRUE; + } else { + XVA_CLR_REQ(xvap, XAT_PROJINHERIT); + XVA_SET_REQ(&tmpxvattr, XAT_PROJINHERIT); + } + } + if (XVA_ISSET_REQ(xvap, XAT_NOUNLINK)) { if (xoap->xoa_nounlink != ((zp->z_pflags & ZFS_NOUNLINK) != 0)) { @@ -3061,7 +3288,8 @@ top: */ mask = vap->va_mask; - if ((mask & (AT_UID | AT_GID))) { + if ((mask & (AT_UID | AT_GID)) || projid != ZFS_INVALID_PROJID) { + handle_eadir = B_TRUE; err = sa_lookup(zp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs), &xattr_obj, sizeof (xattr_obj)); @@ -3074,7 +3302,8 @@ top: new_uid = zfs_fuid_create(zfsvfs, (uint64_t)vap->va_uid, cr, ZFS_OWNER, &fuidp); if (new_uid != zp->z_uid && - zfs_fuid_overquota(zfsvfs, B_FALSE, new_uid)) { + zfs_id_overquota(zfsvfs, DMU_USERUSED_OBJECT, + new_uid)) { if (attrzp) VN_RELE(ZTOV(attrzp)); err = SET_ERROR(EDQUOT); @@ -3086,15 +3315,24 @@ top: new_gid = zfs_fuid_create(zfsvfs, (uint64_t)vap->va_gid, cr, ZFS_GROUP, &fuidp); if (new_gid != zp->z_gid && - zfs_fuid_overquota(zfsvfs, B_TRUE, new_gid)) { + zfs_id_overquota(zfsvfs, DMU_GROUPUSED_OBJECT, + new_gid)) { if (attrzp) VN_RELE(ZTOV(attrzp)); err = SET_ERROR(EDQUOT); goto out2; } } + + if (projid != ZFS_INVALID_PROJID && + zfs_id_overquota(zfsvfs, DMU_PROJECTUSED_OBJECT, projid)) { + if (attrzp) + VN_RELE(ZTOV(attrzp)); + err = EDQUOT; + goto out2; + } } - tx = dmu_tx_create(zfsvfs->z_os); + tx = dmu_tx_create(os); if (mask & AT_MODE) { uint64_t pmode = zp->z_mode; @@ -3134,8 +3372,10 @@ top: mutex_exit(&zp->z_lock); dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE); } else { - if ((mask & AT_XVATTR) && - XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) + if (((mask & AT_XVATTR) && + XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) || + (projid != ZFS_INVALID_PROJID && + !(zp->z_pflags & ZFS_PROJID))) dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE); else dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); @@ -3164,6 +3404,26 @@ top: * updated as a side-effect of calling this function. */ + if (projid != ZFS_INVALID_PROJID && !(zp->z_pflags & ZFS_PROJID)) { + /* + * For the existing object that is upgraded from old system, + * its on-disk layout has no slot for the project ID attribute. + * But quota accounting logic needs to access related slots by + * offset directly. So we need to adjust old objects' layout + * to make the project ID to some unified and fixed offset. + */ + if (attrzp) + err = sa_add_projid(attrzp->z_sa_hdl, tx, projid); + if (err == 0) + err = sa_add_projid(zp->z_sa_hdl, tx, projid); + + if (unlikely(err == EEXIST)) + err = 0; + else if (err != 0) + goto out; + else + projid = ZFS_INVALID_PROJID; + } if (mask & (AT_UID|AT_GID|AT_MODE)) mutex_enter(&zp->z_acl_lock); @@ -3179,6 +3439,12 @@ top: SA_ADD_BULK_ATTR(xattr_bulk, xattr_count, SA_ZPL_FLAGS(zfsvfs), NULL, &attrzp->z_pflags, sizeof (attrzp->z_pflags)); + if (projid != ZFS_INVALID_PROJID) { + attrzp->z_projid = projid; + SA_ADD_BULK_ATTR(xattr_bulk, xattr_count, + SA_ZPL_PROJID(zfsvfs), NULL, &attrzp->z_projid, + sizeof (attrzp->z_projid)); + } } if (mask & (AT_UID|AT_GID)) { @@ -3266,6 +3532,14 @@ top: mtime, ctime, B_TRUE); } } + + if (projid != ZFS_INVALID_PROJID) { + zp->z_projid = projid; + SA_ADD_BULK_ATTR(bulk, count, + SA_ZPL_PROJID(zfsvfs), NULL, &zp->z_projid, + sizeof (zp->z_projid)); + } + /* * Do this after setting timestamps to prevent timestamp * update from toggling bit @@ -3296,6 +3570,9 @@ top: if (XVA_ISSET_REQ(&tmpxvattr, XAT_AV_QUARANTINED)) { XVA_SET_REQ(xvap, XAT_AV_QUARANTINED); } + if (XVA_ISSET_REQ(&tmpxvattr, XAT_PROJINHERIT)) { + XVA_SET_REQ(xvap, XAT_PROJINHERIT); + } if (XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) ASSERT(vp->v_type == VREG); @@ -3319,15 +3596,12 @@ top: mutex_exit(&attrzp->z_lock); } out: - if (err == 0 && attrzp) { + if (err == 0 && xattr_count > 0) { err2 = sa_bulk_update(attrzp->z_sa_hdl, xattr_bulk, xattr_count, tx); ASSERT(err2 == 0); } - if (attrzp) - VN_RELE(ZTOV(attrzp)); - if (aclp) zfs_acl_free(aclp); @@ -3338,15 +3612,23 @@ out: if (err) { dmu_tx_abort(tx); + if (attrzp) + VN_RELE(ZTOV(attrzp)); if (err == ERESTART) goto top; } else { - err2 = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); + if (count > 0) + err2 = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); dmu_tx_commit(tx); + if (attrzp) { + if (err2 == 0 && handle_eadir) + err2 = zfs_setattr_dir(attrzp); + VN_RELE(ZTOV(attrzp)); + } } out2: - if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS) + if (os->os_sync == ZFS_SYNC_ALWAYS) zil_commit(zilog, 0); ZFS_EXIT(zfsvfs); @@ -3645,6 +3927,19 @@ top: return (terr); } + /* + * If we are using project inheritance, it means if the directory has + * ZFS_PROJINHERIT set, then its descendant directories will inherit + * not only the project ID, but also the ZFS_PROJINHERIT flag. Under + * such case, we only allow renames into our tree when the project + * IDs are the same. + */ + if (tdzp->z_pflags & ZFS_PROJINHERIT && + tdzp->z_projid != szp->z_projid) { + error = SET_ERROR(EXDEV); + goto out; + } + /* * Must have write access at the source to remove the old entry * and write access at the target to create the new entry. @@ -3752,6 +4047,8 @@ top: error = zfs_link_create(tdl, szp, tx, ZRENAMING); if (error == 0) { szp->z_pflags |= ZFS_AV_MODIFIED; + if (tdzp->z_pflags & ZFS_PROJINHERIT) + szp->z_pflags |= ZFS_PROJINHERIT; error = sa_update(szp->z_sa_hdl, SA_ZPL_FLAGS(zfsvfs), (void *)&szp->z_pflags, sizeof (uint64_t), tx); @@ -3895,7 +4192,7 @@ top: return (error); } - if (zfs_acl_ids_overquota(zfsvfs, &acl_ids)) { + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, ZFS_DEFAULT_PROJID)) { zfs_acl_ids_free(&acl_ids); zfs_dirent_unlock(dl); ZFS_EXIT(zfsvfs); @@ -4068,6 +4365,18 @@ zfs_link(vnode_t *tdvp, vnode_t *svp, char *name, cred_t *cr, szp = VTOZ(svp); ZFS_VERIFY_ZP(szp); + /* + * If we are using project inheritance, it means if the directory has + * ZFS_PROJINHERIT set, then its descendant directories will inherit + * not only the project ID, but also the ZFS_PROJINHERIT flag. Under + * such case, we only allow hard link creation in our tree when the + * project IDs are the same. + */ + if (dzp->z_pflags & ZFS_PROJINHERIT && dzp->z_projid != szp->z_projid) { + ZFS_EXIT(zfsvfs); + return (SET_ERROR(EXDEV)); + } + /* * We check z_zfsvfs rather than v_vfsp here, because snapshots and the * ctldir appear to have the same v_vfsp. @@ -4250,8 +4559,8 @@ zfs_putapage(vnode_t *vp, page_t *pp, u_offset_t *offp, len = zp->z_size - off; } - if (zfs_owner_overquota(zfsvfs, zp, B_FALSE) || - zfs_owner_overquota(zfsvfs, zp, B_TRUE)) { + if (zfs_id_overblockquota(zfsvfs, DMU_USERUSED_OBJECT, zp->z_uid) || + zfs_id_overblockquota(zfsvfs, DMU_GROUPUSED_OBJECT, zp->z_gid)) { err = SET_ERROR(EDQUOT); goto out; } diff --git a/usr/src/uts/common/fs/zfs/zfs_znode.c b/usr/src/uts/common/fs/zfs/zfs_znode.c index b56cb7bd70..9abfc025d5 100644 --- a/usr/src/uts/common/fs/zfs/zfs_znode.c +++ b/usr/src/uts/common/fs/zfs/zfs_znode.c @@ -519,6 +519,7 @@ zfs_create_share_dir(zfsvfs_t *zfsvfs, dmu_tx_t *tx) sharezp->z_atime_dirty = 0; sharezp->z_zfsvfs = zfsvfs; sharezp->z_is_sa = zfsvfs->z_use_sa; + sharezp->z_pflags = 0; vp = ZTOV(sharezp); vn_reinit(vp); @@ -656,7 +657,8 @@ zfs_znode_alloc(zfsvfs_t *zfsvfs, dmu_buf_t *db, int blksz, vnode_t *vp; uint64_t mode; uint64_t parent; - sa_bulk_attr_t bulk[9]; + uint64_t projid = ZFS_DEFAULT_PROJID; + sa_bulk_attr_t bulk[11]; int count = 0; zp = kmem_cache_alloc(znode_cache, KM_SLEEP); @@ -699,13 +701,17 @@ zfs_znode_alloc(zfsvfs_t *zfsvfs, dmu_buf_t *db, int blksz, SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GID(zfsvfs), NULL, &zp->z_gid, 8); - if (sa_bulk_lookup(zp->z_sa_hdl, bulk, count) != 0 || zp->z_gen == 0) { + if (sa_bulk_lookup(zp->z_sa_hdl, bulk, count) != 0 || zp->z_gen == 0 || + (dmu_objset_projectquota_enabled(zfsvfs->z_os) && + (zp->z_pflags & ZFS_PROJID) && + sa_lookup(zp->z_sa_hdl, SA_ZPL_PROJID(zfsvfs), &projid, 8) != 0)) { if (hdl == NULL) sa_handle_destroy(zp->z_sa_hdl); kmem_cache_free(znode_cache, zp); return (NULL); } + zp->z_projid = projid; zp->z_mode = mode; vp->v_vfsp = zfsvfs->z_parent->z_vfs; @@ -794,6 +800,7 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, uint64_t crtime[2], atime[2], mtime[2], ctime[2]; uint64_t mode, size, links, parent, pflags; uint64_t dzp_pflags = 0; + uint64_t projid = ZFS_DEFAULT_PROJID; uint64_t rdev = 0; zfsvfs_t *zfsvfs = dzp->z_zfsvfs; dmu_buf_t *db; @@ -868,14 +875,12 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, */ if (flag & IS_ROOT_NODE) { dzp->z_id = obj; - } else { - dzp_pflags = dzp->z_pflags; } /* * If parent is an xattr, so am I. */ - if (dzp_pflags & ZFS_XATTR) { + if (dzp->z_pflags & ZFS_XATTR) { flag |= IS_XATTR; } @@ -900,6 +905,23 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, if (flag & IS_XATTR) pflags |= ZFS_XATTR; + if (vap->va_type == VREG || vap->va_type == VDIR) { + /* + * With ZFS_PROJID flag, we can easily know whether there is + * project ID stored on disk or not. See zfs_space_delta_cb(). + */ + if (obj_type != DMU_OT_ZNODE && + dmu_objset_projectquota_enabled(zfsvfs->z_os)) + pflags |= ZFS_PROJID; + + /* + * Inherit project ID from parent if required. + */ + projid = zfs_inherit_projid(dzp); + if (dzp->z_pflags & ZFS_PROJINHERIT) + pflags |= ZFS_PROJINHERIT; + } + /* * No execs denied will be deterimed when zfs_mode_compute() is called. */ @@ -981,6 +1003,10 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, if (obj_type == DMU_OT_ZNODE) { SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_XATTR(zfsvfs), NULL, &empty_xattr, 8); + } else if (dmu_objset_projectquota_enabled(zfsvfs->z_os) && + pflags & ZFS_PROJID) { + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_PROJID(zfsvfs), + NULL, &projid, 8); } if (obj_type == DMU_OT_ZNODE || (vap->va_type == VBLK || vap->va_type == VCHR)) { @@ -1028,6 +1054,7 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, (*zpp)->z_pflags = pflags; (*zpp)->z_mode = mode; (*zpp)->z_dnodesize = dnodesize; + (*zpp)->z_projid = projid; if (vap->va_mask & AT_XVATTR) zfs_xvattr_set(*zpp, (xvattr_t *)vap, tx); @@ -1133,6 +1160,11 @@ zfs_xvattr_set(znode_t *zp, xvattr_t *xvap, dmu_tx_t *tx) zp->z_pflags, tx); XVA_SET_RTN(xvap, XAT_SPARSE); } + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) { + ZFS_ATTR_SET(zp, ZFS_PROJINHERIT, xoap->xoa_projinherit, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_PROJINHERIT); + } } int @@ -1222,10 +1254,11 @@ zfs_rezget(znode_t *zp) dmu_buf_t *db; uint64_t obj_num = zp->z_id; uint64_t mode; - sa_bulk_attr_t bulk[8]; + sa_bulk_attr_t bulk[10]; int err; int count = 0; uint64_t gen; + uint64_t projid = ZFS_DEFAULT_PROJID; ZFS_OBJ_HOLD_ENTER(zfsvfs, obj_num); @@ -1279,6 +1312,17 @@ zfs_rezget(znode_t *zp) return (SET_ERROR(EIO)); } + if (dmu_objset_projectquota_enabled(zfsvfs->z_os)) { + err = sa_lookup(zp->z_sa_hdl, SA_ZPL_PROJID(zfsvfs), + &projid, 8); + if (err != 0 && err != ENOENT) { + zfs_znode_dmu_fini(zp); + ZFS_OBJ_HOLD_EXIT(zfsvfs, obj_num); + return (SET_ERROR(err)); + } + } + + zp->z_projid = projid; zp->z_mode = mode; if (gen != zp->z_gen) { @@ -1870,6 +1914,7 @@ zfs_create_fs(objset_t *os, cred_t *cr, nvlist_t *zplprops, dmu_tx_t *tx) rootzp->z_unlinked = 0; rootzp->z_atime_dirty = 0; rootzp->z_is_sa = USE_SA(version, os); + rootzp->z_pflags = 0; vp = ZTOV(rootzp); vn_reinit(vp); diff --git a/usr/src/uts/common/fs/zfs/zio_crypt.c b/usr/src/uts/common/fs/zfs/zio_crypt.c index 1d6b8286e3..78c26e3e90 100644 --- a/usr/src/uts/common/fs/zfs/zio_crypt.c +++ b/usr/src/uts/common/fs/zfs/zio_crypt.c @@ -1213,12 +1213,17 @@ zio_crypt_do_objset_hmacs(zio_crypt_key_t *key, void *data, uint_t datalen, bcopy(raw_portable_mac, portable_mac, ZIO_OBJSET_MAC_LEN); /* - * The local MAC protects the user and group accounting. If these - * objects are not present, the local MAC is zeroed out. + * The local MAC protects the user, group and project accounting. + * If these objects are not present, the local MAC is zeroed out. */ - if ((osp->os_userused_dnode.dn_type == DMU_OT_NONE && + if ((datalen >= OBJSET_PHYS_SIZE_V3 && + osp->os_userused_dnode.dn_type == DMU_OT_NONE && + osp->os_groupused_dnode.dn_type == DMU_OT_NONE && + osp->os_projectused_dnode.dn_type == DMU_OT_NONE) || + (datalen >= OBJSET_PHYS_SIZE_V2 && + osp->os_userused_dnode.dn_type == DMU_OT_NONE && osp->os_groupused_dnode.dn_type == DMU_OT_NONE) || - (datalen <= OBJSET_OLD_PHYS_SIZE)) { + (datalen <= OBJSET_PHYS_SIZE_V1)) { bzero(local_mac, ZIO_OBJSET_MAC_LEN); return (0); } diff --git a/usr/src/uts/common/sys/fs/zfs.h b/usr/src/uts/common/sys/fs/zfs.h index a5b311e4f1..f0136aa148 100644 --- a/usr/src/uts/common/sys/fs/zfs.h +++ b/usr/src/uts/common/sys/fs/zfs.h @@ -182,6 +182,14 @@ typedef enum { ZFS_PROP_USERQUOTA, ZFS_PROP_GROUPUSED, ZFS_PROP_GROUPQUOTA, + ZFS_PROP_USEROBJUSED, + ZFS_PROP_USEROBJQUOTA, + ZFS_PROP_GROUPOBJUSED, + ZFS_PROP_GROUPOBJQUOTA, + ZFS_PROP_PROJECTUSED, + ZFS_PROP_PROJECTQUOTA, + ZFS_PROP_PROJECTOBJUSED, + ZFS_PROP_PROJECTOBJQUOTA, ZFS_NUM_USERQUOTA_PROPS } zfs_userquota_prop_t; diff --git a/usr/src/uts/common/sys/vnode.h b/usr/src/uts/common/sys/vnode.h index 51b4f7af18..b48db0afd6 100644 --- a/usr/src/uts/common/sys/vnode.h +++ b/usr/src/uts/common/sys/vnode.h @@ -469,6 +469,8 @@ typedef struct xoptattr { uint64_t xoa_generation; uint8_t xoa_offline; uint8_t xoa_sparse; + uint8_t xoa_projinherit; + uint64_t xoa_projid; } xoptattr_t; /* @@ -651,11 +653,14 @@ typedef vattr_t vattr32_t; #define XAT0_GEN 0x00004000 /* object generation number */ #define XAT0_OFFLINE 0x00008000 /* offline */ #define XAT0_SPARSE 0x00010000 /* sparse */ +#define XAT0_PROJINHERIT 0x00020000 /* Create with parent projid */ +#define XAT0_PROJID 0x00040000 /* Project ID */ #define XAT0_ALL_ATTRS (XAT0_CREATETIME|XAT0_ARCHIVE|XAT0_SYSTEM| \ XAT0_READONLY|XAT0_HIDDEN|XAT0_NOUNLINK|XAT0_IMMUTABLE|XAT0_APPENDONLY| \ XAT0_NODUMP|XAT0_OPAQUE|XAT0_AV_QUARANTINED| XAT0_AV_MODIFIED| \ - XAT0_AV_SCANSTAMP|XAT0_REPARSE|XATO_GEN|XAT0_OFFLINE|XAT0_SPARSE) + XAT0_AV_SCANSTAMP|XAT0_REPARSE|XATO_GEN|XAT0_OFFLINE|XAT0_SPARSE| \ + XAT0_PROJINHERIT | XAT0_PROJID) /* Support for XAT_* optional attributes */ #define XVA_MASK 0xffffffff /* Used to mask off 32 bits */ @@ -692,6 +697,8 @@ typedef vattr_t vattr32_t; #define XAT_GEN ((XAT0_INDEX << XVA_SHFT) | XAT0_GEN) #define XAT_OFFLINE ((XAT0_INDEX << XVA_SHFT) | XAT0_OFFLINE) #define XAT_SPARSE ((XAT0_INDEX << XVA_SHFT) | XAT0_SPARSE) +#define XAT_PROJINHERIT ((XAT0_INDEX << XVA_SHFT) | XAT0_PROJINHERIT) +#define XAT_PROJID ((XAT0_INDEX << XVA_SHFT) | XAT0_PROJID) /* * The returned attribute map array (xva_rtnattrmap[]) is located past the -- cgit v1.2.3