summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNasf-Fan <fan.yong@intel.com>2019-08-19 21:21:09 +0000
committerJerry Jelinek <jerry.jelinek@joyent.com>2019-08-20 11:27:30 +0000
commitf67950b21e185934ccabe311516f4dcbdb00ef79 (patch)
tree0876bfea682f05ade72b05689808909878907070
parenta2f04351e04971ab0879872d264d6038c156b860 (diff)
downloadillumos-joyent-f67950b21e185934ccabe311516f4dcbdb00ef79.tar.gz
11479 zfs project support
Portions contributed by: Jerry Jelinek <jerry.jelinek@joyent.com> Portions contributed by: Jinshan Xiong <jinshan.xiong@intel.com> Portions contributed by: Suman Chakravartula <schakrava@gmail.com> Portions contributed by: jxiong <jinshan.xiong@gmail.com> Reviewed by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed by: Andreas Dilger <andreas.dilger@intel.com> Reviewed by Ned Bass <bass6@llnl.gov> Reviewed by: Matthew Ahrens <mahrens@delphix.com> Reviewed by: C Fraire <cfraire@me.com> Reviewed by: Kody Kantor <kody.kantor@joyent.com> Approved by: Richard Lowe <richlowe@richlowe.net>
-rw-r--r--usr/src/cmd/zdb/zdb.c20
-rw-r--r--usr/src/cmd/zfs/Makefile8
-rw-r--r--usr/src/cmd/zfs/zfs_main.c421
-rw-r--r--usr/src/cmd/zfs/zfs_project.c304
-rw-r--r--usr/src/cmd/zfs/zfs_projectutil.h49
-rw-r--r--usr/src/cmd/zhack/zhack.c2
-rw-r--r--usr/src/common/zfs/zfeature_common.c20
-rw-r--r--usr/src/common/zfs/zfeature_common.h2
-rw-r--r--usr/src/common/zfs/zfs_deleg.c8
-rw-r--r--usr/src/common/zfs/zfs_deleg.h8
-rw-r--r--usr/src/common/zfs/zfs_prop.c10
-rw-r--r--usr/src/lib/libzfs/common/libzfs_dataset.c159
-rw-r--r--usr/src/man/man1m/zfs.1m270
-rw-r--r--usr/src/man/man5/zpool-features.565
-rw-r--r--usr/src/pkg/manifests/system-test-zfstest.mf59
-rw-r--r--usr/src/test/zfs-tests/cmd/mkfiles/mkfiles.c6
-rw-r--r--usr/src/test/zfs-tests/runfiles/delphix.run26
-rw-r--r--usr/src/test/zfs-tests/runfiles/omnios.run28
-rw-r--r--usr/src/test/zfs-tests/runfiles/openindiana.run28
-rw-r--r--usr/src/test/zfs-tests/runfiles/smartos.run22
-rw-r--r--usr/src/test/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg2
-rw-r--r--usr/src/test/zfs-tests/tests/functional/privilege/setup.ksh4
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/Makefile21
-rwxr-xr-xusr/src/test/zfs-tests/tests/functional/projectquota/cleanup.ksh37
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectid_001_pos.ksh100
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectid_002_pos.ksh91
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectid_003_pos.ksh86
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectquota.cfg46
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_001_pos.ksh89
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_002_pos.ksh87
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_003_pos.ksh99
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_004_neg.ksh87
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_005_pos.ksh68
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_006_pos.ksh72
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_007_pos.ksh58
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_008_pos.ksh91
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_009_pos.ksh131
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib101
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_001_pos.ksh91
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_002_pos.ksh83
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_003_pos.ksh119
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projectspace_004_pos.ksh76
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_001_pos.ksh108
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_002_pos.ksh120
-rw-r--r--usr/src/test/zfs-tests/tests/functional/projectquota/projecttree_003_neg.ksh103
-rwxr-xr-xusr/src/test/zfs-tests/tests/functional/projectquota/setup.ksh56
-rw-r--r--usr/src/test/zfs-tests/tests/functional/upgrade/Makefile21
-rw-r--r--usr/src/test/zfs-tests/tests/functional/upgrade/cleanup.ksh42
-rw-r--r--usr/src/test/zfs-tests/tests/functional/upgrade/setup.ksh43
-rw-r--r--usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_common.kshlib41
-rw-r--r--usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_projectquota_001_pos.ksh125
-rw-r--r--usr/src/test/zfs-tests/tests/functional/upgrade/upgrade_userobj_001_pos.ksh95
-rw-r--r--usr/src/test/zfs-tests/tests/functional/userquota/groupspace_003_pos.ksh104
-rw-r--r--usr/src/test/zfs-tests/tests/functional/userquota/userquota_001_pos.ksh4
-rw-r--r--usr/src/test/zfs-tests/tests/functional/userquota/userquota_004_pos.ksh15
-rw-r--r--usr/src/test/zfs-tests/tests/functional/userquota/userquota_010_pos.ksh2
-rw-r--r--usr/src/test/zfs-tests/tests/functional/userquota/userquota_013_pos.ksh79
-rw-r--r--usr/src/test/zfs-tests/tests/functional/userquota/userquota_common.kshlib6
-rw-r--r--usr/src/test/zfs-tests/tests/functional/userquota/userspace_002_pos.ksh3
-rw-r--r--usr/src/test/zfs-tests/tests/functional/userquota/userspace_003_pos.ksh117
-rw-r--r--usr/src/uts/common/fs/zfs/dbuf.c2
-rw-r--r--usr/src/uts/common/fs/zfs/dmu.c4
-rw-r--r--usr/src/uts/common/fs/zfs/dmu_objset.c413
-rw-r--r--usr/src/uts/common/fs/zfs/dmu_traverse.c26
-rw-r--r--usr/src/uts/common/fs/zfs/dnode.c22
-rw-r--r--usr/src/uts/common/fs/zfs/dnode_sync.c5
-rw-r--r--usr/src/uts/common/fs/zfs/dsl_pool.c2
-rw-r--r--usr/src/uts/common/fs/zfs/dsl_scan.c6
-rw-r--r--usr/src/uts/common/fs/zfs/sa.c189
-rw-r--r--usr/src/uts/common/fs/zfs/spa.c12
-rw-r--r--usr/src/uts/common/fs/zfs/sys/dmu.h9
-rw-r--r--usr/src/uts/common/fs/zfs/sys/dmu_objset.h39
-rw-r--r--usr/src/uts/common/fs/zfs/sys/dnode.h13
-rw-r--r--usr/src/uts/common/fs/zfs/sys/dsl_deleg.h8
-rw-r--r--usr/src/uts/common/fs/zfs/sys/sa.h1
-rw-r--r--usr/src/uts/common/fs/zfs/sys/spa_impl.h2
-rw-r--r--usr/src/uts/common/fs/zfs/sys/zfs_acl.h3
-rw-r--r--usr/src/uts/common/fs/zfs/sys/zfs_context.h1
-rw-r--r--usr/src/uts/common/fs/zfs/sys/zfs_project.h78
-rw-r--r--usr/src/uts/common/fs/zfs/sys/zfs_sa.h3
-rw-r--r--usr/src/uts/common/fs/zfs/sys/zfs_vfsops.h14
-rw-r--r--usr/src/uts/common/fs/zfs/sys/zfs_znode.h23
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_acl.c12
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_dir.c2
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_ioctl.c87
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_log.c14
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_replay.c13
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_sa.c13
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_vfsops.c417
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_vnops.c375
-rw-r--r--usr/src/uts/common/fs/zfs/zfs_znode.c57
-rw-r--r--usr/src/uts/common/fs/zfs/zio_crypt.c13
-rw-r--r--usr/src/uts/common/sys/fs/zfs.h8
-rw-r--r--usr/src/uts/common/sys/vnode.h9
94 files changed, 5775 insertions, 358 deletions
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 <ikozhukhov@gmail.com>.
+# 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 <sys/fs/zfs.h>
#include <sys/types.h>
#include <time.h>
+#include <sys/zfs_project.h>
#include <synch.h>
#include <libzfs.h>
@@ -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[,...]] "
"<filesystem|snapshot>\n"));
+ case HELP_PROJECTSPACE:
+ return (gettext("\tprojectspace [-Hp] [-o field[,...]] "
+ "[-s field] ... \n"
+ "\t [-S field] ... <filesystem|snapshot>\n"));
+ case HELP_PROJECT:
+ return (gettext("\tproject [-d|-r] <directory|file ...>\n"
+ "\tproject -c [-0] [-d|-r] [-p id] <directory|file ...>\n"
+ "\tproject -C [-k] [-r] <directory ...>\n"
+ "\tproject [-p id] [-r] [-s] <directory ...>\n"));
case HELP_HOLD:
return (gettext("\thold [-r] <tag> <snapshot> ...\n"));
case HELP_HOLDS:
@@ -496,10 +514,26 @@ usage(boolean_t requested)
(void) fprintf(fp, " NO NO <size>\n");
(void) fprintf(fp, "\t%-15s ", "groupused@...");
(void) fprintf(fp, " NO NO <size>\n");
+ (void) fprintf(fp, "\t%-15s ", "projectused@...");
+ (void) fprintf(fp, " NO NO <size>\n");
+ (void) fprintf(fp, "\t%-15s ", "userobjused@...");
+ (void) fprintf(fp, " NO NO <size>\n");
+ (void) fprintf(fp, "\t%-15s ", "groupobjused@...");
+ (void) fprintf(fp, " NO NO <size>\n");
+ (void) fprintf(fp, "\t%-15s ", "projectobjused@...");
+ (void) fprintf(fp, " NO NO <size>\n");
(void) fprintf(fp, "\t%-15s ", "userquota@...");
(void) fprintf(fp, "YES NO <size> | none\n");
(void) fprintf(fp, "\t%-15s ", "groupquota@...");
(void) fprintf(fp, "YES NO <size> | none\n");
+ (void) fprintf(fp, "\t%-15s ", "projectquota@...");
+ (void) fprintf(fp, "YES NO <size> | none\n");
+ (void) fprintf(fp, "\t%-15s ", "userobjquota@...");
+ (void) fprintf(fp, "YES NO <size> | none\n");
+ (void) fprintf(fp, "\t%-15s ", "groupobjquota@...");
+ (void) fprintf(fp, "YES NO <size> | none\n");
+ (void) fprintf(fp, "\t%-15s ", "projectobjquota@...");
+ (void) fprintf(fp, "YES NO <size> | none\n");
(void) fprintf(fp, "\t%-15s ", "written@<snap>");
(void) fprintf(fp, " NO NO <size>\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] <file|directory ...>
+ * 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] <file|directory ...>
+ * 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] <file|directory ...>
+ * 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] <file|directory ...>
+ * 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 <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <stddef.h>
+#include <libintl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/list.h>
+#include <limits.h>
+#include <sys/debug.h>
+#include <sys/stat.h>
+#include <sys/zfs_project.h>
+
+#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 <pool/fs>`.
+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
+<pool/fs>`.
+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 <fs> 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 <sys/zfeature.h>
#include <sys/cred.h>
#include <sys/zfs_context.h>
#include <sys/dmu_objset.h>
@@ -54,7 +55,9 @@
#include <sys/dsl_destroy.h>
#include <sys/vdev.h>
#include <sys/zfeature.h>
+#include <sys/spa_impl.h>
#include <sys/dmu_recv.h>
+#include <sys/zfs_project.h>
#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;
}
@@ -1415,6 +1469,58 @@ dmu_objset_snapshot_one(const char *fsname, const char *snapname)
}
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)
{
dnode_t *dn;
@@ -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 <sys/zio.h>
#include <sys/dmu_zfetch.h>
#include <sys/range_tree.h>
+#include <sys/zfs_project.h>
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 <sys/zfs_context.h>
@@ -46,6 +47,10 @@
#include <sys/errno.h>
#include <sys/zfs_context.h>
+#ifdef _KERNEL
+#include <sys/zfs_znode.h>
+#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 <sys/ioccom.h>
+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 <sys/zfs_acl.h>
#include <sys/zil.h>
+#include <sys/zfs_project.h>
#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
@@ -5363,6 +5399,49 @@ zfs_ioc_userspace_upgrade(zfs_cmd_t *zc)
}
/*
+ * 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
* nfs, and smbsrv. Determine them if needed when
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 <sys/sa.h>
#include <sys/zfs_acl.h>
#include <sys/zfs_sa.h>
+#include <sys/dmu_objset.h>
+#include <sys/sa_impl.h>
/*
* 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, &quota);
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, &quota);
+ 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, &quota);
+ if (err == ENOENT)
+ goto objs;
+ else if (err)
+ return (err);
+
+ err = zap_lookup(zfsvfs->z_os, DMU_PROJECTUSED_OBJECT,
+ buf + offset, 8, 1, &used);
+ if (unlikely(err == ENOENT)) {
+ uint32_t blksize;
+ u_longlong_t nblocks;
+
+ /*
+ * Quota accounting is async, so it is possible race case.
+ * There is at least one object with the given project ID.
+ */
+ sa_object_size(zp->z_sa_hdl, &blksize, &nblocks);
+ if (unlikely(zp->z_blksz == 0))
+ blksize = zfsvfs->z_max_blksz;
+
+ used = blksize * nblocks;
+ } else if (err) {
+ return (err);
+ }
+
+ statp->f_blocks = quota >> bshift;
+ statp->f_bfree = (quota > used) ? ((quota - used) >> bshift) : 0;
+ statp->f_bavail = statp->f_bfree;
+
+objs:
+ if (zfsvfs->z_projectobjquota_obj == 0)
+ return (0);
+
+ err = zap_lookup(zfsvfs->z_os, zfsvfs->z_projectobjquota_obj,
+ buf + offset, 8, 1, &quota);
+ if (err == ENOENT)
+ return (0);
+ else if (err)
+ return (err);
+
+ err = zap_lookup(zfsvfs->z_os, DMU_PROJECTUSED_OBJECT,
+ buf, 8, 1, &used);
+ if (unlikely(err == ENOENT)) {
+ /*
+ * Quota accounting is async, so it is possible race case.
+ * There is at least one object with the given project ID.
+ */
+ used = 1;
+ } else if (err) {
+ return (err);
+ }
+
+ statp->f_files = quota;
+ statp->f_ffree = (quota > used) ? (quota - used) : 0;
+
+ return (0);
+}
+
/*
* 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 <sys/cred.h>
#include <sys/attr.h>
#include <sys/zil.h>
+#include <sys/sa_impl.h>
+#include <sys/zfs_project.h>
/*
* 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);
@@ -2716,6 +2790,119 @@ zfs_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr,
}
/*
+ * 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);
@@ -3646,6 +3928,19 @@ top:
}
/*
+ * 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.
* Note that if target and source are the same, this can be
@@ -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);
@@ -4069,6 +4366,18 @@ zfs_link(vnode_t *tdvp, vnode_t *svp, char *name, cred_t *cr,
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