]> granicus.if.org Git - zfs/commitdiff
Project Quota on ZFS
authorNasf-Fan <fan.yong@intel.com>
Tue, 13 Feb 2018 22:54:54 +0000 (06:54 +0800)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Tue, 13 Feb 2018 22:54:54 +0000 (14:54 -0800)
Project quota is a new ZFS system space/object usage accounting
and enforcement mechanism. Similar as user/group quota, project
quota is another dimension of system quota. It bases on the new
object attribute - project ID.

Project ID is a numerical value to indicate to which project an
object belongs. An object only can belong to one project though
you (the object owner or privileged user) can change the object
project ID via 'chattr -p' or 'zfs project [-s] -p' explicitly.
The object also can inherit the project ID from its parent when
created if the parent has the project inherit flag (that can be
set via 'chattr +P' or 'zfs project -s [-p]').

By accounting the spaces/objects belong to the same project, we
can know how many spaces/objects used by the project. And if we
set the upper limit then we can control the spaces/objects that
are consumed by such project. It is useful when multiple groups
and users cooperate for the same project, or a user/group needs
to participate in multiple projects.

Support the following commands and functionalities:

zfs set projectquota@project
zfs set projectobjquota@project

zfs get projectquota@project
zfs get projectobjquota@project
zfs get projectused@project
zfs get projectobjused@project

zfs projectspace

zfs allow projectquota
zfs allow projectobjquota
zfs allow projectused
zfs allow projectobjused

zfs unallow projectquota
zfs unallow projectobjquota
zfs unallow projectused
zfs unallow projectobjused

chattr +/-P
chattr -p project_id
lsattr -p

This patch also supports tree quota based on the project quota via
"zfs project" commands set as following:
zfs project [-d|-r] <file|directory ...>
zfs project -C [-k] [-r] <file|directory ...>
zfs project -c [-0] [-d|-r] [-p id] <file|directory ...>
zfs project [-p id] [-r] [-s] <file|directory ...>

For "df [-i] $DIR" command, if we set INHERIT (project ID) flag on
the $DIR, then the proejct [obj]quota and [obj]used values for the
$DIR's project ID will be shown as the total/free (avail) resource.
Keep the same behavior as EXT4/XFS does.

Reviewed-by: Andreas Dilger <andreas.dilger@intel.com>
Reviewed-by  Ned Bass <bass6@llnl.gov>
Reviewed-by: Matthew Ahrens <mahrens@delphix.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Fan Yong <fan.yong@intel.com>
TEST_ZIMPORT_POOLS="zol-0.6.1 zol-0.6.2 master"
Change-Id: Ib4f0544602e03fb61fd46a849d7ba51a6005693c
Closes #6290

82 files changed:
cmd/zdb/zdb.c
cmd/zfs/Makefile.am
cmd/zfs/zfs_main.c
cmd/zfs/zfs_project.c [new file with mode: 0644]
cmd/zfs/zfs_projectutil.h [new file with mode: 0644]
cmd/zhack/zhack.c
configure.ac
include/sys/Makefile.am
include/sys/dmu.h
include/sys/dmu_objset.h
include/sys/dnode.h
include/sys/dsl_deleg.h
include/sys/fs/zfs.h
include/sys/sa.h
include/sys/xvattr.h
include/sys/zfs_acl.h
include/sys/zfs_project.h [new file with mode: 0644]
include/sys/zfs_sa.h
include/sys/zfs_vfsops.h
include/sys/zfs_znode.h
include/zfeature_common.h
include/zfs_deleg.h
lib/libzfs/libzfs_dataset.c
man/man5/zpool-features.5
man/man8/zfs.8
module/zcommon/zfeature_common.c
module/zcommon/zfs_deleg.c
module/zcommon/zfs_prop.c
module/zfs/dbuf.c
module/zfs/dmu.c
module/zfs/dmu_objset.c
module/zfs/dmu_traverse.c
module/zfs/dnode.c
module/zfs/dsl_pool.c
module/zfs/dsl_scan.c
module/zfs/sa.c
module/zfs/spa.c
module/zfs/zfs_acl.c
module/zfs/zfs_dir.c
module/zfs/zfs_ioctl.c
module/zfs/zfs_log.c
module/zfs/zfs_replay.c
module/zfs/zfs_sa.c
module/zfs/zfs_vfsops.c
module/zfs/zfs_vnops.c
module/zfs/zfs_znode.c
module/zfs/zpl_file.c
tests/runfiles/linux.run
tests/zfs-tests/include/commands.cfg
tests/zfs-tests/tests/functional/Makefile.am
tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg
tests/zfs-tests/tests/functional/privilege/setup.ksh
tests/zfs-tests/tests/functional/projectquota/Makefile.am [new file with mode: 0644]
tests/zfs-tests/tests/functional/projectquota/cleanup.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectid_001_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectid_002_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectid_003_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectquota.cfg [new file with mode: 0644]
tests/zfs-tests/tests/functional/projectquota/projectquota_001_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectquota_002_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectquota_003_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectquota_004_neg.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectquota_005_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectquota_006_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectquota_007_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectquota_008_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectquota_009_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib [new file with mode: 0644]
tests/zfs-tests/tests/functional/projectquota/projectspace_001_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectspace_002_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectspace_003_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projectspace_004_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projecttree_001_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projecttree_002_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/projecttree_003_neg.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/projectquota/setup.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/upgrade/Makefile.am
tests/zfs-tests/tests/functional/upgrade/cleanup.ksh
tests/zfs-tests/tests/functional/upgrade/setup.ksh
tests/zfs-tests/tests/functional/upgrade/upgrade_common.kshlib [new file with mode: 0644]
tests/zfs-tests/tests/functional/upgrade/upgrade_projectquota_001_pos.ksh [new file with mode: 0755]
tests/zfs-tests/tests/functional/upgrade/upgrade_userobj_001_pos.ksh

index 063358a0432cb4200f1fd4efa873ddc65b078b18..6e3539d938ef9bc30a06779b44bb6fb7b626ba3c 100644 (file)
@@ -1880,6 +1880,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);
@@ -1942,8 +1949,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               */
@@ -2218,6 +2225,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);
index 4e689d1ee5e5b46f8f995d7e4e897ddc274294f5..8b6ddaa200100dd16d36487defc88b221a5e0af3 100644 (file)
@@ -10,7 +10,9 @@ zfs_SOURCES = \
        zfs_iter.c \
        zfs_iter.h \
        zfs_main.c \
-       zfs_util.h
+       zfs_util.h \
+       zfs_project.c \
+       zfs_projectutil.h
 
 zfs_LDADD = \
        $(top_builddir)/lib/libnvpair/libnvpair.la \
index 991dd444458614e8bfe838386e0549020749a1a0..16410d2f23115e1fc4cd882ec5d8e604e8b49f29 100644 (file)
@@ -59,6 +59,7 @@
 #include <sys/systeminfo.h>
 #include <sys/types.h>
 #include <time.h>
+#include <sys/zfs_project.h>
 
 #include <libzfs.h>
 #include <libzfs_core.h>
@@ -74,6 +75,7 @@
 #include "zfs_util.h"
 #include "zfs_comutil.h"
 #include "libzfs_impl.h"
+#include "zfs_projectutil.h"
 
 libzfs_handle_t *g_zfs;
 
@@ -111,6 +113,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.
@@ -153,6 +156,8 @@ typedef enum {
        HELP_UNALLOW,
        HELP_USERSPACE,
        HELP_GROUPSPACE,
+       HELP_PROJECTSPACE,
+       HELP_PROJECT,
        HELP_HOLD,
        HELP_HOLDS,
        HELP_RELEASE,
@@ -197,8 +202,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            },
@@ -328,6 +337,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:
@@ -489,10 +507,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");
 
@@ -500,9 +534,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"
@@ -2270,6 +2304,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.
@@ -2303,8 +2339,10 @@ static char *us_field_names[] = { "type", "name", "used", "quota",
 #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,
@@ -2459,6 +2497,13 @@ zfs_prop_is_group(unsigned p)
            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)
 {
@@ -2471,6 +2516,8 @@ us_type2str(unsigned field_type)
                return ("SMB User");
        case USTYPE_SMB_GRP:
                return ("SMB Group");
+       case USTYPE_PROJ:
+               return ("Project");
        default:
                return ("Undefined");
        }
@@ -2556,7 +2603,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;
@@ -2564,6 +2611,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;
                }
        }
 
@@ -2615,7 +2664,9 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space)
        /* Calculate/update width of USED/QUOTA fields */
        if (cb->cb_nicenum) {
                if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED ||
-                   prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA) {
+                   prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA ||
+                   prop == ZFS_PROP_PROJECTUSED ||
+                   prop == ZFS_PROP_PROJECTQUOTA) {
                        zfs_nicebytes(space, sizebuf, sizeof (sizebuf));
                } else {
                        zfs_nicenum(space, sizebuf, sizeof (sizebuf));
@@ -2625,21 +2676,24 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space)
                    (u_longlong_t)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 if (prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA) {
+       } 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_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_GROUPOBJQUOTA ||
+           prop == ZFS_PROP_PROJECTOBJQUOTA) {
                propname = "objquota";
                if (!nvlist_exists(props, "objused"))
                        (void) nvlist_add_uint64(props, "objused", 0);
@@ -2838,13 +2892,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':
@@ -2866,9 +2929,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 ':':
@@ -2965,7 +3038,8 @@ zfs_do_userspace(int argc, char **argv)
                if ((zfs_prop_is_user(p) &&
                    !(types & (USTYPE_PSX_USR | USTYPE_SMB_USR))) ||
                    (zfs_prop_is_group(p) &&
-                   !(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP))))
+                   !(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP))) ||
+                   (zfs_prop_is_project(p) && types != USTYPE_PROJ))
                        continue;
 
                cb.cb_prop = p;
@@ -4276,6 +4350,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[] = {
@@ -4307,6 +4386,10 @@ static zfs_deleg_perm_tab_t zfs_deleg_perm_tbl[] = {
        { 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 }
 };
 
@@ -4388,6 +4471,10 @@ deleg_perm_type(zfs_deleg_note_t note)
        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:
@@ -4912,6 +4999,20 @@ deleg_perm_comment(zfs_deleg_note_t note)
        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 = "";
@@ -7513,6 +7614,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 subdirectorie 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/cmd/zfs/zfs_project.c b/cmd/zfs/zfs_project.c
new file mode 100644 (file)
index 0000000..5ac88f2
--- /dev/null
@@ -0,0 +1,295 @@
+/*
+ * 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.
+ */
+
+#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 <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[PATH_MAX];
+} 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));
+       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;
+
+       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:
+       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;
+               }
+
+               sprintf(fullname, "%s/%s", name, ent->d_name);
+               ret = zfs_project_handle_one(fullname, zpc);
+               if (!ret && zpc->zpc_recursive && ent->d_type == DT_DIR)
+                       zfs_project_item_alloc(head, fullname);
+       }
+
+       if (errno && !ret) {
+               ret = -errno;
+               (void) fprintf(stderr, gettext("failed to readdir %s: %s\n"),
+                   name, strerror(errno));
+       }
+
+       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/cmd/zfs/zfs_projectutil.h b/cmd/zfs/zfs_projectutil.h
new file mode 100644 (file)
index 0000000..1792a33
--- /dev/null
@@ -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 */
index e15af8f4ea02eb207c5f3cd89b9bbd79d1f39c44..296a7fe7582546cb38abb47bedbd2d92d4a27a6e 100644 (file)
@@ -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?
index 6dd6834a3ca176b4a2b332ddff490ce2ab511b59..5ad82b4b73c8ca98fb2c833446d5a6bd96885567 100644 (file)
@@ -280,6 +280,7 @@ AC_CONFIG_FILES([
        tests/zfs-tests/tests/functional/pool_names/Makefile
        tests/zfs-tests/tests/functional/poolversion/Makefile
        tests/zfs-tests/tests/functional/privilege/Makefile
+       tests/zfs-tests/tests/functional/projectquota/Makefile
        tests/zfs-tests/tests/functional/quota/Makefile
        tests/zfs-tests/tests/functional/raidz/Makefile
        tests/zfs-tests/tests/functional/redundancy/Makefile
index 348e6584f83de4a96815cd008d42f209b2464f21..8e18a87904a8aebd424c8a4c80cd3fd285ef4f41 100644 (file)
@@ -105,6 +105,7 @@ COMMON_H = \
        $(top_srcdir)/include/sys/zfs_delay.h \
        $(top_srcdir)/include/sys/zfs_dir.h \
        $(top_srcdir)/include/sys/zfs_fuid.h \
+       $(top_srcdir)/include/sys/zfs_project.h \
        $(top_srcdir)/include/sys/zfs_ratelimit.h \
        $(top_srcdir)/include/sys/zfs_rlock.h \
        $(top_srcdir)/include/sys/zfs_sa.h \
index 5553667c3774fb5770a9ff617ca252286f7da462..cf9cbaa645e644c92486a74fae2760827bb99318 100644 (file)
@@ -276,9 +276,10 @@ 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}USED_OBJECT.
+ * Zap prefix for object accounting in DMU_{USER,GROUP,PROJECT}USED_OBJECT.
  */
 #define        DMU_OBJACCT_PREFIX      "obj-"
 #define        DMU_OBJACCT_PREFIX_LEN  4
@@ -971,7 +972,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);
index 7ee992f3116e89de2ce78a665e5f3f1144302324..df9b1a73ac2fefded5afb248382486e4574dca5f 100644 (file)
@@ -49,14 +49,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_USEROBJACCOUNTING_COMPLETE  (1ULL<<1)
+#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)
@@ -68,11 +72,14 @@ 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 *);
@@ -94,6 +101,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;
@@ -143,7 +151,7 @@ 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 */
@@ -165,6 +173,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 ||           \
@@ -215,9 +224,12 @@ int dmu_objset_userspace_upgrade(objset_t *os);
 boolean_t dmu_objset_userspace_present(objset_t *os);
 boolean_t dmu_objset_userobjused_enabled(objset_t *os);
 boolean_t dmu_objset_userobjspace_upgradable(objset_t *os);
-void dmu_objset_userobjspace_upgrade(objset_t *os);
 boolean_t dmu_objset_userobjspace_present(objset_t *os);
 boolean_t dmu_objset_incompatible_encryption_version(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);
 
 int dmu_fsname(const char *snapname, char *buf);
 
index 691fd443a260894e832e1f2c241add069a60b443..9c44a22324039b48b7582d88ad18a5cc303b3342 100644 (file)
@@ -147,7 +147,7 @@ enum dnode_dirtycontext {
 /* Does dnode have a SA spill blkptr in bonus? */
 #define        DNODE_FLAG_SPILL_BLKPTR                 (1 << 2)
 
-/* User/Group dnode accounting */
+/* User/Group/Project dnode accounting */
 #define        DNODE_FLAG_USEROBJUSED_ACCOUNTED        (1 << 3)
 
 #define        DNODE_CRYPT_PORTABLE_FLAGS_MASK         (DNODE_FLAG_SPILL_BLKPTR)
@@ -356,8 +356,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 */
index 153c08f934939357c8b721c55b03fdfb717a0a51..eb95c68e8a9deb42ac09aa57d30647ef611963c4 100644 (file)
@@ -63,6 +63,10 @@ extern "C" {
 #define        ZFS_DELEG_PERM_BOOKMARK         "bookmark"
 #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
index 7b86f6631097366ca5a66f23af433e5819af634e..88f590276fede2d6cbca75bba573a7d131005de3 100644 (file)
@@ -192,6 +192,10 @@ typedef enum {
        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;
 
index b7ed9fe38cbdcc175a249ee88bac46654d76e81d..50b90622164b3b1f73e6058da3bc8bb0dc9670fc 100644 (file)
@@ -159,6 +159,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
index 4779b632163f33b84b18a881c4d24c73975ca1c9..1c919454d8eb712ee5349bb284d47a40ac4c3c51 100644 (file)
@@ -64,6 +64,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;
 
 /*
@@ -169,11 +171,14 @@ typedef struct xvattr {
 #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 */
@@ -210,6 +215,8 @@ typedef struct xvattr {
 #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
index 2572fee8630611e72c96b87f52f29ebbf55080b1..6d3db50416088ff2ac86745a54c5c7a63ca98738 100644 (file)
@@ -208,7 +208,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 *);
@@ -237,6 +237,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/include/sys/zfs_project.h b/include/sys/zfs_project.h
new file mode 100644 (file)
index 0000000..52d5204
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * 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        _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
+
+#include <linux/fs.h>
+
+#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
+struct zfsxattr {
+       uint32_t        fsx_xflags;     /* xflags field value (get/set) */
+       uint32_t        fsx_extsize;    /* extsize field value (get/set) */
+       uint32_t        fsx_nextents;   /* nextents field value (get)   */
+       uint32_t        fsx_projid;     /* project identifier (get/set) */
+       uint32_t        fsx_cowextsize;
+       unsigned char   fsx_pad[8];
+};
+typedef struct zfsxattr 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 */
index 06c4d589aa791d1efef2c930b2e7abf3f44b8c80..4e6d28638ef68297c04cb1288b749fe202c7106a 100644 (file)
@@ -74,6 +74,7 @@ typedef enum zpl_attr {
        ZPL_SCANSTAMP,
        ZPL_DACL_ACES,
        ZPL_DXATTR,
+       ZPL_PROJID,
        ZPL_END
 } zpl_attr_t;
 
@@ -87,6 +88,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];
index 7dbdfd718396d3d91d41f5f91bdde3907ec83018..70f0cd50defc683ff6cbf3e1bff0a1cdd1584a89 100644 (file)
@@ -121,6 +121,8 @@ struct zfsvfs {
        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 */
        uint64_t        z_hold_size;    /* znode hold array size */
@@ -195,12 +197,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_fuid_overobjquota(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);
index 6a3a3b23313b56d8afe934d0153f0cb1d07bf0f7..311babe59151963350f54800a1026c8d61c583eb 100644 (file)
@@ -42,6 +42,7 @@
 #endif
 #include <sys/zfs_acl.h>
 #include <sys/zil.h>
+#include <sys/zfs_project.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -66,6 +67,18 @@ extern "C" {
 #define        ZFS_OFFLINE             0x0000100000000000ull
 #define        ZFS_SPARSE              0x0000200000000000ull
 
+/*
+ * 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         0x0000400000000000ull
+
+/*
+ * PROJID attr is used internally to indicate that the object has project ID.
+ */
+#define        ZFS_PROJID              0x0000800000000000ull
+
 #define        ZFS_ATTR_SET(zp, attr, value, pflags, tx) \
 { \
        if (value) \
@@ -110,6 +123,7 @@ extern "C" {
 #define        SA_ZPL_ZNODE_ACL(z)     z->z_attr_table[ZPL_ZNODE_ACL]
 #define        SA_ZPL_DXATTR(z)        z->z_attr_table[ZPL_DXATTR]
 #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 +142,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"
@@ -196,6 +210,7 @@ typedef struct znode {
        krwlock_t       z_xattr_lock;   /* xattr data lock */
        nvlist_t        *z_xattr_cached; /* cached xattrs */
        uint64_t        z_xattr_parent; /* parent obj for this xattr */
+       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? */
@@ -212,6 +227,13 @@ typedef struct znode_hold {
        refcount_t      zh_refcount;    /* active consumer reference count */
 } znode_hold_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
  * --------------------
index d55b46a2297a26c874bb6c888ecebcd391a66190..3afa64b11dbcba4a976a7d88fb47e44c57e4e8f8 100644 (file)
@@ -58,6 +58,7 @@ typedef enum spa_feature {
        SPA_FEATURE_EDONR,
        SPA_FEATURE_USEROBJ_ACCOUNTING,
        SPA_FEATURE_ENCRYPTION,
+       SPA_FEATURE_PROJECT_QUOTA,
        SPA_FEATURES
 } spa_feature_t;
 
index deab01131bc483a3629ac2bb20fe2c92181c2564..e18849ebbaf88f9726114455623f5de1a1656df0 100644 (file)
@@ -73,6 +73,10 @@ typedef enum {
        ZFS_DELEG_NOTE_BOOKMARK,
        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;
 
index 3d47180257b7b3c3806256c98878ae2c2fd22a96..1879eb757f332b11b44db9852c45377c685c95f8 100644 (file)
@@ -1050,7 +1050,9 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl,
                        if (uqtype != ZFS_PROP_USERQUOTA &&
                            uqtype != ZFS_PROP_GROUPQUOTA &&
                            uqtype != ZFS_PROP_USEROBJQUOTA &&
-                           uqtype != ZFS_PROP_GROUPOBJQUOTA) {
+                           uqtype != ZFS_PROP_GROUPOBJQUOTA &&
+                           uqtype != ZFS_PROP_PROJECTQUOTA &&
+                           uqtype != ZFS_PROP_PROJECTOBJQUOTA) {
                                zfs_error_aux(hdl,
                                    dgettext(TEXT_DOMAIN, "'%s' is readonly"),
                                    propname);
@@ -1075,7 +1077,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 {
@@ -3007,6 +3009,8 @@ out:
  * 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,
@@ -3016,12 +3020,13 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
        char *cp;
        boolean_t isuser;
        boolean_t isgroup;
+       boolean_t isproject;
        struct passwd *pw;
        struct group *gr;
 
        domain[0] = '\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)
@@ -3037,6 +3042,9 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
        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;
 
@@ -3048,7 +3056,7 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
                if (zoned && getzoneid() == GLOBAL_ZONEID)
                        return (ENOENT);
                *ridp = gr->gr_gid;
-       } else if (strchr(cp, '@')) {
+       } else if (!isproject && strchr(cp, '@')) {
 #ifdef HAVE_IDMAP
                /*
                 * It's a SID name (eg "user@domain") that needs to be
@@ -3089,13 +3097,13 @@ userquota_propname_decode(const char *propname, boolean_t zoned,
                return (ENOSYS);
 #endif /* HAVE_IDMAP */
        } else {
-               /* It's a user/group ID (eg "12345"). */
+               /* It's a user/group/project ID (eg "12345"). */
                uid_t id;
                char *end;
                id = strtoul(cp, &end, 10);
                if (*end != '\0')
                        return (EINVAL);
-               if (id > MAXUID) {
+               if (id > MAXUID && !isproject) {
 #ifdef HAVE_IDMAP
                        /* It's an ephemeral ID. */
                        idmap_rid_t rid;
@@ -3170,10 +3178,12 @@ zfs_prop_get_userquota(zfs_handle_t *zhp, const char *propname,
                    (u_longlong_t)propvalue);
        } else if (propvalue == 0 &&
            (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_GROUPQUOTA ||
-           type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA)) {
+           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_USERUSED || type == ZFS_PROP_GROUPUSED ||
+           type == ZFS_PROP_PROJECTUSED || type == ZFS_PROP_PROJECTQUOTA) {
                zfs_nicebytes(propvalue, propbuf, proplen);
        } else {
                zfs_nicenum(propvalue, propbuf, proplen);
@@ -4728,7 +4738,11 @@ zfs_userspace(zfs_handle_t *zhp, zfs_userquota_prop_t type,
                            (type == ZFS_PROP_USEROBJUSED ||
                            type == ZFS_PROP_GROUPOBJUSED ||
                            type == ZFS_PROP_USEROBJQUOTA ||
-                           type == ZFS_PROP_GROUPOBJQUOTA)))
+                           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),
index 72a6c57b127afe74cf5ff7b91790d07563e40c2d..523fd1fd09cc64c116da8c26f103bd4f0177e82f 100644 (file)
@@ -640,5 +640,39 @@ are destroyed.
 
 .RE
 
+.sp
+.ne 2
+.na
+\fB\fBproject_quota\fR\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 spaces and objects usage
+information against the project identifier (ID).
+
+The project ID is new object-based attribute. When upgrading an existing
+filesystem, object without project ID attribute will be assigned a zero
+project ID. After this feature is enabled, newly created object will inherit
+its parent directory's project ID if the parent inherit flag is set (via
+\fBchattr +/-P\fR or \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
+anytime by the owner (or privileged user) via \fBchattr -p $prjid\fR or
+\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 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"
 \fBzpool\fR(8)
index 2e1ffc6ea728de0d918332c4e5c27bd6cd9de3e0..f428513285069a869bdb38668d58abac0b766ca5 100644 (file)
 .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
@@ -905,6 +933,56 @@ The root user, or a user who has been granted the
 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 chattr Fl /+P
+or
+.Nm zfs project Fl s )
+when being created. The privileged user can set and change object's project
+ID via
+.Nm chattr Fl p
+or
+.Nm zfs project Fl s
+anytime. Space is charged to the project of each file, as displayed by
+.Nm lsattr Fl p
+or
+.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 .
+When a filesystem is configured to use
+.Sy xattr=sa
+no additional internal objects are required. 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
@@ -1566,6 +1644,27 @@ is similar to
 but it limits 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/changed.
+.Pp
+The root user, or a user who has been granted the
+.Sy projectquota
+privilege with
+.Nm zfs allow ,
+can access all projects' quota.
+.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 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
@@ -3000,6 +3099,114 @@ 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 file(s) or directories.
+.Bl -tag -width "-d"
+.It Fl d
+Show the directory project ID and inherit flag, not its childrens. 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 file(s) 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 file(s) 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 childrens. It will
+overwrite the former specified
+.Fl r
+option.
+.It Fl 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.
+.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
+.Bl -tag -width "-p"
+Set project ID and/or inherit flag on the file(s) or directories.
+.It Fl p
+Set the file(s)' 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 file(s) 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.
@@ -3812,6 +4019,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
 acltype          property
index 7b782b45d6144a9e0ec932727762fc4c080a8294..36d0d9613fc728137289144632cadddd774d7a96 100644 (file)
@@ -321,6 +321,18 @@ zpool_feature_init(void)
            "Support for dataset level encryption",
            ZFEATURE_FLAG_PER_DATASET, encryption_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);
+       }
 }
 
 #if defined(_KERNEL) && defined(HAVE_SPL)
index 18e5c11cce03856f94d82054692258a44a215969..3a51bc49aa9c0d2db257a417567c96ad00d9476d 100644 (file)
@@ -71,6 +71,10 @@ zfs_deleg_perm_tab_t zfs_deleg_perm_tab[] = {
        {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}
 };
 
index 42af9468c0e2fa5b15646ff60267b9bce40721b2..0d44fd139b7a371cf2a1399162cb4702ce373fc8 100644 (file)
@@ -58,7 +58,11 @@ const char *zfs_userquota_prop_prefixes[] = {
        "userobjused@",
        "userobjquota@",
        "groupobjused@",
-       "groupobjquota@"
+       "groupobjquota@",
+       "projectused@",
+       "projectquota@",
+       "projectobjused@",
+       "projectobjquota@"
 };
 
 zprop_desc_t *
index 3668ea31533c672ec232e465a71e1e0f58e061e2..51cb0c982f4991e2a852abc1e1c02d8c2eadd9fb 100644 (file)
@@ -2456,7 +2456,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.
  */
 __attribute__((always_inline))
index 20ed3ebffcaca20437a10bf660f139d7175531c0..cb86800f4bb374e2efcaf160c31e826b6e0ec58d 100644 (file)
@@ -113,8 +113,8 @@ const dmu_object_type_info_t dmu_ot[DMU_OT_NUMTYPES] = {
        { DMU_BSWAP_UINT64,     TRUE,   FALSE,  "FUID table size"       },
        { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "DSL dataset next clones"},
        { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "scan work queue"       },
-       { DMU_BSWAP_ZAP,        TRUE,   TRUE,   "ZFS user/group used"   },
-       { DMU_BSWAP_ZAP,        TRUE,   TRUE,   "ZFS user/group quota"  },
+       { DMU_BSWAP_ZAP,        TRUE,   TRUE,   "ZFS user/group/project used" },
+       { DMU_BSWAP_ZAP,        TRUE,   TRUE,   "ZFS user/group/project quota"},
        { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "snapshot refcount tags"},
        { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "DDT ZAP algorithm"     },
        { DMU_BSWAP_ZAP,        TRUE,   FALSE,  "DDT statistics"        },
index befce9be6bc8457496208d059148384761d41924..0d9273fbbfb1963cf08834419a7362f7c638b598 100644 (file)
@@ -58,6 +58,7 @@
 #include <sys/policy.h>
 #include <sys/spa_impl.h>
 #include <sys/dmu_send.h>
+#include <sys/zfs_project.h>
 
 /*
  * Needed to close a window in dnode_move() that allows the objset to be freed
@@ -336,14 +337,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);
        }
 }
 
@@ -395,6 +399,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);
@@ -420,12 +425,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);
@@ -436,7 +448,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;
@@ -568,11 +580,15 @@ 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);
@@ -711,9 +727,10 @@ dmu_objset_own(const char *name, dmu_objset_type_t type,
        }
 
        /* user accounting requires the dataset to be decrypted */
-       if (dmu_objset_userobjspace_upgradable(*osp) &&
+       if ((dmu_objset_userobjspace_upgradable(*osp) ||
+           dmu_objset_projectquota_upgradable(*osp)) &&
            (ds->ds_dir->dd_crypto_obj == 0 || decrypt))
-               dmu_objset_userobjspace_upgrade(*osp);
+               dmu_objset_id_quota_upgrade(*osp);
 
        dsl_pool_rele(dp, FTAG);
        return (0);
@@ -835,6 +852,8 @@ dmu_objset_evict_dbufs(objset_t *os)
        kmem_free(dn_marker, sizeof (dnode_t));
 
        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));
        }
@@ -889,6 +908,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);
        }
@@ -1004,6 +1025,12 @@ dmu_objset_create_impl_dnstats(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp,
                        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;
        }
 
@@ -1408,7 +1435,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]);
@@ -1537,6 +1564,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) &&
@@ -1620,6 +1653,14 @@ dmu_objset_userobjused_enabled(objset_t *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 {
        /* must be in the first filed, see userquota_update_cache() */
        char            uqn_id[20 + DMU_OBJACCT_PREFIX_LEN];
@@ -1630,6 +1671,7 @@ typedef struct userquota_node {
 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
@@ -1682,6 +1724,19 @@ do_userquota_cacheflush(objset_t *os, userquota_cache_t *cache, dmu_tx_t *tx)
                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
@@ -1706,10 +1761,11 @@ userquota_update_cache(avl_tree_t *avl, const char *id, int64_t 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)) {
+       if (flags & DNODE_FLAG_USERUSED_ACCOUNTED) {
                int64_t delta = DNODE_MIN_SIZE + used;
                char name[20];
 
@@ -1721,12 +1777,18 @@ do_userquota_update(userquota_cache_t *cache, uint64_t used, uint64_t flags,
 
                (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(userquota_cache_t *cache, uint64_t flags,
-    uint64_t user, uint64_t group, boolean_t subtract)
+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];
@@ -1739,6 +1801,13 @@ do_userobjquota_update(userquota_cache_t *cache, uint64_t flags,
                (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);
+               }
        }
 }
 
@@ -1766,6 +1835,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;
@@ -1777,18 +1850,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_userobjquota_update(&cache, 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,
+                       do_userquota_update(os, &cache,
                            DN_USED_BYTES(dn->dn_phys), dn->dn_phys->dn_flags,
-                           dn->dn_newuid, dn->dn_newgid, B_FALSE);
-                       do_userobjquota_update(&cache, dn->dn_phys->dn_flags,
-                           dn->dn_newuid, dn->dn_newgid, B_FALSE);
+                           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);
@@ -1797,6 +1873,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;
@@ -1824,7 +1901,7 @@ dmu_objset_do_userquota_updates(objset_t *os, dmu_tx_t *tx)
        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,
@@ -1834,6 +1911,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 =
@@ -1896,6 +1979,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;
@@ -1953,9 +2037,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;
        }
 
        /*
@@ -1963,7 +2049,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
@@ -1976,9 +2062,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;
        }
@@ -2016,6 +2104,13 @@ dmu_objset_userobjspace_present(objset_t *os)
            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)
 {
@@ -2085,33 +2180,43 @@ dmu_objset_userspace_upgrade(objset_t *os)
 }
 
 static int
-dmu_objset_userobjspace_upgrade_cb(objset_t *os)
+dmu_objset_id_quota_upgrade_cb(objset_t *os)
 {
        int err = 0;
 
-       if (dmu_objset_userobjspace_present(os))
+       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_userobjspace_upgrade(objset_t *os)
+dmu_objset_id_quota_upgrade(objset_t *os)
 {
-       dmu_objset_upgrade(os, dmu_objset_userobjspace_upgrade_cb);
+       dmu_objset_upgrade(os, dmu_objset_id_quota_upgrade_cb);
 }
 
 boolean_t
@@ -2123,6 +2228,15 @@ dmu_objset_userobjspace_upgradable(objset_t *os)
            !dmu_objset_userobjspace_present(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)
@@ -2731,7 +2845,10 @@ EXPORT_SYMBOL(dmu_objset_userused_enabled);
 EXPORT_SYMBOL(dmu_objset_userspace_upgrade);
 EXPORT_SYMBOL(dmu_objset_userspace_present);
 EXPORT_SYMBOL(dmu_objset_userobjused_enabled);
-EXPORT_SYMBOL(dmu_objset_userobjspace_upgrade);
 EXPORT_SYMBOL(dmu_objset_userobjspace_upgradable);
 EXPORT_SYMBOL(dmu_objset_userobjspace_present);
+EXPORT_SYMBOL(dmu_objset_projectquota_enabled);
+EXPORT_SYMBOL(dmu_objset_projectquota_present);
+EXPORT_SYMBOL(dmu_objset_projectquota_upgradable);
+EXPORT_SYMBOL(dmu_objset_id_quota_upgrade);
 #endif
index 15d29198fb7e6ada7b722e9ede6a2c186e68ec34..5407e4817292473edb77b6811788595591a2f783 100644 (file)
@@ -386,7 +386,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,
@@ -395,13 +399,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);
                }
        }
 
index b4c131e9832328f32ff88fcb5f40cd3342ade748..596983b47acdf50e5e02dc0f193df5af3ada0281 100644 (file)
@@ -38,6 +38,7 @@
 #include <sys/dmu_zfetch.h>
 #include <sys/range_tree.h>
 #include <sys/trace_dnode.h>
+#include <sys/zfs_project.h>
 
 dnode_stats_t dnode_stats = {
        { "dnode_hold_dbuf_hold",               KSTAT_DATA_UINT64 },
@@ -157,8 +158,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;
@@ -210,8 +213,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);
@@ -543,8 +548,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);
@@ -799,8 +806,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);
@@ -859,8 +868,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;
 
        /*
@@ -1265,9 +1276,14 @@ dnode_hold_impl(objset_t *os, uint64_t object, int flag, int slots,
            (spa_is_root(os->os_spa) &&
            spa_config_held(os->os_spa, SCL_STATE, RW_WRITER)));
 
-       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;
index 86863fad87192acb4158e80949452a64549b322b..db2e67742e3c8c02cf0850515a876a1e98591d89 100644 (file)
@@ -580,7 +580,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.
         */
index fc0c24e1c31505ed1405bee2dffa4ddd66203178..776f8f23956633393baf3fd68684563a01689e20 100644 (file)
@@ -1684,11 +1684,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);
index f0a18bad8dc4f791c03d68858a026640bc46407d..4a863f9a56458af0bb9dc5424aead54ec599a67f 100644 (file)
 #include <sys/errno.h>
 #include <sys/zfs_context.h>
 
+#ifdef _KERNEL
+#include <sys/zfs_znode.h>
+#endif
+
 /*
  * ZFS System attributes:
  *
@@ -1456,8 +1460,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;
@@ -1470,9 +1475,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);
 }
 
@@ -1497,6 +1512,173 @@ sa_lookup_uio(sa_handle_t *hdl, sa_attr_type_t attr, uio_t *uio)
        mutex_exit(&hdl->sa_lock);
        return (error);
 }
+
+/*
+ * For the existed 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 = ZTOZSB(zp);
+       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(ZTOI(zp)->i_mode) || S_ISCHR(ZTOI(zp)->i_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 = ZTOI(zp)->i_nlink;
+       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(ZTOI(zp)->i_mode) || S_ISCHR(ZTOI(zp)->i_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 *
@@ -2062,4 +2244,5 @@ EXPORT_SYMBOL(sa_hdrsize);
 EXPORT_SYMBOL(sa_handle_lock);
 EXPORT_SYMBOL(sa_handle_unlock);
 EXPORT_SYMBOL(sa_lookup_uio);
+EXPORT_SYMBOL(sa_add_projid);
 #endif /* _KERNEL */
index dac04246460faa46d2b09776ea52dddd4ea35190..736b51feae1e67aa7cfab77e2dc987bbe52ba8b0 100644 (file)
@@ -1188,7 +1188,7 @@ spa_activate(spa_t *spa, int mode)
 
        /*
         * The taskq to upgrade datasets in this pool. Currently used by
-        * feature SPA_FEATURE_USEROBJ_ACCOUNTING.
+        * feature SPA_FEATURE_USEROBJ_ACCOUNTING/SPA_FEATURE_PROJECT_QUOTA.
         */
        spa->spa_upgrade_taskq = taskq_create("z_upgrade", boot_ncpus,
            defclsyspri, 1, INT_MAX, TASKQ_DYNAMIC);
index 5ef20f08826d9ebd6de72220c1aad4b109253ec3..b366e8f1cbee8b411f33f57deca2e0233a884bdd 100644 (file)
@@ -1054,8 +1054,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;
@@ -1883,12 +1883,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) ||
-           zfs_fuid_overobjquota(zfsvfs, B_FALSE, acl_ids->z_fuid) ||
-           zfs_fuid_overobjquota(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)));
 }
 
 /*
index 6398a1d155e241866a1cff1c0711816946d62597..7eb426b781191370722e16498546fa653019c062 100644 (file)
@@ -1036,7 +1036,7 @@ zfs_make_xattrdir(znode_t *zp, vattr_t *vap, struct inode **xipp, 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));
        }
index fcd2fca128d34eda5375be318837ee9f2d291e9d..6bee042a05a63995a69df7f11d75bb8ed691c5f4 100644 (file)
@@ -260,10 +260,14 @@ static const char *userquota_perms[] = {
        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_userobjspace_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,
@@ -1200,10 +1204,14 @@ zfs_secpolicy_userspace_one(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr)
                    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,
@@ -2516,7 +2524,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_userobjspace_upgrade(zc);
+                       (void) zfs_ioc_id_quota_upgrade(zc);
                        kmem_free(zc, sizeof (zfs_cmd_t));
                }
                break;
@@ -3897,6 +3905,10 @@ zfs_check_settable(const char *dsname, nvpair_t *pair, cred_t *cr)
                            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) {
@@ -3910,8 +3922,14 @@ zfs_check_settable(const char *dsname, nvpair_t *pair, cred_t *cr)
                        } 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));
                        }
 
@@ -5180,7 +5198,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
@@ -5306,7 +5324,7 @@ zfs_ioc_userspace_upgrade(zfs_cmd_t *zc)
  * none
  */
 static int
-zfs_ioc_userobjspace_upgrade(zfs_cmd_t *zc)
+zfs_ioc_id_quota_upgrade(zfs_cmd_t *zc)
 {
        objset_t *os;
        int error;
@@ -5315,14 +5333,15 @@ zfs_ioc_userobjspace_upgrade(zfs_cmd_t *zc)
        if (error != 0)
                return (error);
 
-       if (dmu_objset_userobjspace_upgradable(os)) {
+       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_userobjspace_upgrade(os);
+                       dmu_objset_id_quota_upgrade(os);
                } else {
                        mutex_exit(&os->os_upgrade_lock);
                }
index 8887f037aa341889cfdd07aae072954747649da4..ce7b84927e1e1c3efa99f36622299b179b7ef87f 100644 (file)
@@ -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 *
index bfba2fea0292b4a45a4166881c61d76fdc6e2752..e2ff0078935f6df5396c58cec553b86527f8d062 100644 (file)
@@ -128,14 +128,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
index 3eff6acc6f3ebb6eb3c430c05b766c5f600b5902..bd21ba896cc3de1dafaf89a0e98537141ae3dd22 100644 (file)
@@ -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.
@@ -63,6 +65,7 @@ sa_attr_reg_t zfs_attr_table[ZPL_END+1] = {
        {"ZPL_SCANSTAMP", 32, SA_UINT8_ARRAY, 0},
        {"ZPL_DACL_ACES", 0, SA_ACL, 0},
        {"ZPL_DXATTR", 0, SA_UINT8_ARRAY, 0},
+       {"ZPL_PROJID", sizeof (uint64_t), SA_UINT64_ARRAY, 0},
        {NULL, 0, 0, 0}
 };
 
@@ -317,7 +320,7 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx)
        }
 
        /* First do a bulk query of the attributes that aren't cached */
-       bulk = kmem_alloc(sizeof (sa_bulk_attr_t) * 20, KM_SLEEP);
+       bulk = kmem_alloc(sizeof (sa_bulk_attr_t) * ZPL_END, KM_SLEEP);
        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);
@@ -332,9 +335,13 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx)
        SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ZNODE_ACL(zfsvfs), NULL,
            &znode_acl, 88);
 
-       if (sa_bulk_lookup_locked(hdl, bulk, count) != 0) {
-               kmem_free(bulk, sizeof (sa_bulk_attr_t) * 20);
+       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;
        }
 
        /*
@@ -342,7 +349,7 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx)
         * it is such a way to pick up an already existing layout number
         */
        count = 0;
-       sa_attrs = kmem_zalloc(sizeof (sa_bulk_attr_t) * 20, KM_SLEEP);
+       sa_attrs = kmem_zalloc(sizeof (sa_bulk_attr_t) * ZPL_END, KM_SLEEP);
        SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_MODE(zfsvfs), NULL, &mode, 8);
        SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_SIZE(zfsvfs), NULL,
            &zp->z_size, 8);
@@ -365,6 +372,9 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx)
        links = ZTOI(zp)->i_nlink;
        SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_LINKS(zfsvfs), NULL,
            &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 (S_ISBLK(ZTOI(zp)->i_mode) || S_ISCHR(ZTOI(zp)->i_mode))
                SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_RDEV(zfsvfs), NULL,
                    &rdev, 8);
@@ -400,9 +410,9 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx)
                    znode_acl.z_acl_extern_obj, tx));
 
        zp->z_is_sa = B_TRUE;
-       kmem_free(sa_attrs, sizeof (sa_bulk_attr_t) * 20);
-       kmem_free(bulk, sizeof (sa_bulk_attr_t) * 20);
+       kmem_free(sa_attrs, sizeof (sa_bulk_attr_t) * ZPL_END);
 done:
+       kmem_free(bulk, sizeof (sa_bulk_attr_t) * ZPL_END);
        if (drop_lock)
                mutex_exit(&zp->z_lock);
 }
index bb380c920cbc10e24a7e9cd81ef3cb04bd34456f..971fd54bc9dd28824ac75cac4b5ec63f5d406023 100644 (file)
@@ -536,8 +536,14 @@ unregister:
 
 static int
 zfs_space_delta_cb(dmu_object_type_t bonustype, void *data,
-    uint64_t *userp, uint64_t *groupp)
+    uint64_t *userp, uint64_t *groupp, uint64_t *projectp)
 {
+       sa_hdr_phys_t sa;
+       sa_hdr_phys_t *sap = data;
+       uint64_t flags;
+       int hdrsize;
+       boolean_t swap = B_FALSE;
+
        /*
         * Is it a valid type of object to track?
         */
@@ -557,42 +563,49 @@ zfs_space_delta_cb(dmu_object_type_t bonustype, void *data,
                znode_phys_t *znp = data;
                *userp = znp->zp_uid;
                *groupp = znp->zp_gid;
+               *projectp = ZFS_DEFAULT_PROJID;
+               return (0);
+       }
+
+       if (sap->sa_magic == 0) {
+               /*
+                * This should only happen for newly created files
+                * that haven't had the znode data filled in yet.
+                */
+               *userp = 0;
+               *groupp = 0;
+               *projectp = ZFS_DEFAULT_PROJID;
+               return (0);
+       }
+
+       sa = *sap;
+       if (sa.sa_magic == BSWAP_32(SA_MAGIC)) {
+               sa.sa_magic = SA_MAGIC;
+               sa.sa_layout_info = BSWAP_16(sa.sa_layout_info);
+               swap = B_TRUE;
        } else {
-               int hdrsize;
-               sa_hdr_phys_t *sap = data;
-               sa_hdr_phys_t sa = *sap;
-               boolean_t swap = B_FALSE;
-
-               ASSERT(bonustype == DMU_OT_SA);
-
-               if (sa.sa_magic == 0) {
-                       /*
-                        * This should only happen for newly created
-                        * files that haven't had the znode data filled
-                        * in yet.
-                        */
-                       *userp = 0;
-                       *groupp = 0;
-                       return (0);
-               }
-               if (sa.sa_magic == BSWAP_32(SA_MAGIC)) {
-                       sa.sa_magic = SA_MAGIC;
-                       sa.sa_layout_info = BSWAP_16(sa.sa_layout_info);
-                       swap = B_TRUE;
-               } else {
-                       VERIFY3U(sa.sa_magic, ==, SA_MAGIC);
-               }
+               VERIFY3U(sa.sa_magic, ==, SA_MAGIC);
+       }
 
-               hdrsize = sa_hdrsize(&sa);
-               VERIFY3U(hdrsize, >=, sizeof (sa_hdr_phys_t));
-               *userp = *((uint64_t *)((uintptr_t)data + hdrsize +
-                   SA_UID_OFFSET));
-               *groupp = *((uint64_t *)((uintptr_t)data + hdrsize +
-                   SA_GID_OFFSET));
-               if (swap) {
-                       *userp = BSWAP_64(*userp);
-                       *groupp = BSWAP_64(*groupp);
-               }
+       hdrsize = sa_hdrsize(&sa);
+       VERIFY3U(hdrsize, >=, sizeof (sa_hdr_phys_t));
+
+       *userp = *((uint64_t *)((uintptr_t)data + hdrsize + SA_UID_OFFSET));
+       *groupp = *((uint64_t *)((uintptr_t)data + hdrsize + SA_GID_OFFSET));
+       flags = *((uint64_t *)((uintptr_t)data + hdrsize + SA_FLAGS_OFFSET));
+       if (swap)
+               flags = BSWAP_64(flags);
+
+       if (flags & ZFS_PROJID)
+               *projectp = *((uint64_t *)((uintptr_t)data + hdrsize +
+                   SA_PROJID_OFFSET));
+       else
+               *projectp = ZFS_DEFAULT_PROJID;
+
+       if (swap) {
+               *userp = BSWAP_64(*userp);
+               *groupp = BSWAP_64(*groupp);
+               *projectp = BSWAP_64(*projectp);
        }
        return (0);
 }
@@ -624,6 +637,9 @@ zfs_userquota_prop_to_obj(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type)
        case ZFS_PROP_GROUPUSED:
        case ZFS_PROP_GROUPOBJUSED:
                return (DMU_GROUPUSED_OBJECT);
+       case ZFS_PROP_PROJECTUSED:
+       case ZFS_PROP_PROJECTOBJUSED:
+               return (DMU_PROJECTUSED_OBJECT);
        case ZFS_PROP_USERQUOTA:
                return (zfsvfs->z_userquota_obj);
        case ZFS_PROP_GROUPQUOTA:
@@ -632,6 +648,10 @@ zfs_userquota_prop_to_obj(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type)
                return (zfsvfs->z_userobjquota_obj);
        case ZFS_PROP_GROUPOBJQUOTA:
                return (zfsvfs->z_groupobjquota_obj);
+       case ZFS_PROP_PROJECTQUOTA:
+               return (zfsvfs->z_projectquota_obj);
+       case ZFS_PROP_PROJECTOBJQUOTA:
+               return (zfsvfs->z_projectobjquota_obj);
        default:
                return (ZFS_NO_OBJECT);
        }
@@ -651,8 +671,16 @@ zfs_userspace_many(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type,
        if (!dmu_objset_userspace_present(zfsvfs->z_os))
                return (SET_ERROR(ENOTSUP));
 
+       if ((type == ZFS_PROP_PROJECTQUOTA || type == ZFS_PROP_PROJECTUSED ||
+           type == ZFS_PROP_PROJECTOBJQUOTA ||
+           type == ZFS_PROP_PROJECTOBJUSED) &&
+           !dmu_objset_projectquota_present(zfsvfs->z_os))
+               return (SET_ERROR(ENOTSUP));
+
        if ((type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED ||
-           type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA) &&
+           type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA ||
+           type == ZFS_PROP_PROJECTOBJUSED ||
+           type == ZFS_PROP_PROJECTOBJQUOTA) &&
            !dmu_objset_userobjspace_present(zfsvfs->z_os))
                return (SET_ERROR(ENOTSUP));
 
@@ -662,7 +690,8 @@ zfs_userspace_many(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type,
                return (0);
        }
 
-       if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED)
+       if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED ||
+           type == ZFS_PROP_PROJECTOBJUSED)
                offset = DMU_OBJACCT_PREFIX_LEN;
 
        for (zap_cursor_init_serialized(&zc, zfsvfs->z_os, obj, *cookiep);
@@ -731,15 +760,27 @@ zfs_userspace_one(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type,
                return (SET_ERROR(ENOTSUP));
 
        if ((type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED ||
-           type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA) &&
+           type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA ||
+           type == ZFS_PROP_PROJECTOBJUSED ||
+           type == ZFS_PROP_PROJECTOBJQUOTA) &&
            !dmu_objset_userobjspace_present(zfsvfs->z_os))
                return (SET_ERROR(ENOTSUP));
 
+       if (type == ZFS_PROP_PROJECTQUOTA || type == ZFS_PROP_PROJECTUSED ||
+           type == ZFS_PROP_PROJECTOBJQUOTA ||
+           type == ZFS_PROP_PROJECTOBJUSED) {
+               if (!dmu_objset_projectquota_present(zfsvfs->z_os))
+                       return (SET_ERROR(ENOTSUP));
+               if (!zpl_is_valid_projid(rid))
+                       return (SET_ERROR(EINVAL));
+       }
+
        obj = zfs_userquota_prop_to_obj(zfsvfs, type);
        if (obj == ZFS_NO_OBJECT)
                return (0);
 
-       if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED) {
+       if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED ||
+           type == ZFS_PROP_PROJECTOBJUSED) {
                strlcpy(buf, DMU_OBJACCT_PREFIX, DMU_OBJACCT_PREFIX_LEN + 1);
                offset = DMU_OBJACCT_PREFIX_LEN;
        }
@@ -780,6 +821,22 @@ zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type,
        case ZFS_PROP_GROUPOBJQUOTA:
                objp = &zfsvfs->z_groupobjquota_obj;
                break;
+       case ZFS_PROP_PROJECTQUOTA:
+               if (!dmu_objset_projectquota_enabled(zfsvfs->z_os))
+                       return (SET_ERROR(ENOTSUP));
+               if (!zpl_is_valid_projid(rid))
+                       return (SET_ERROR(EINVAL));
+
+               objp = &zfsvfs->z_projectquota_obj;
+               break;
+       case ZFS_PROP_PROJECTOBJQUOTA:
+               if (!dmu_objset_projectquota_enabled(zfsvfs->z_os))
+                       return (SET_ERROR(ENOTSUP));
+               if (!zpl_is_valid_projid(rid))
+                       return (SET_ERROR(EINVAL));
+
+               objp = &zfsvfs->z_projectobjquota_obj;
+               break;
        default:
                return (SET_ERROR(EINVAL));
        }
@@ -827,35 +884,51 @@ zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type,
 }
 
 boolean_t
-zfs_fuid_overobjquota(zfsvfs_t *zfsvfs, boolean_t isgroup, uint64_t fuid)
+zfs_id_overobjquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id)
 {
        char buf[20 + DMU_OBJACCT_PREFIX_LEN];
-       uint64_t used, quota, usedobj, quotaobj;
+       uint64_t used, quota, quotaobj;
        int err;
 
        if (!dmu_objset_userobjspace_present(zfsvfs->z_os)) {
                if (dmu_objset_userobjspace_upgradable(zfsvfs->z_os)) {
                        dsl_pool_config_enter(
                            dmu_objset_pool(zfsvfs->z_os), FTAG);
-                       dmu_objset_userobjspace_upgrade(zfsvfs->z_os);
+                       dmu_objset_id_quota_upgrade(zfsvfs->z_os);
                        dsl_pool_config_exit(
                            dmu_objset_pool(zfsvfs->z_os), FTAG);
                }
                return (B_FALSE);
        }
 
-       usedobj = isgroup ? DMU_GROUPUSED_OBJECT : DMU_USERUSED_OBJECT;
-       quotaobj = isgroup ? zfsvfs->z_groupobjquota_obj :
-           zfsvfs->z_userobjquota_obj;
+       if (usedobj == DMU_PROJECTUSED_OBJECT) {
+               if (!dmu_objset_projectquota_present(zfsvfs->z_os)) {
+                       if (dmu_objset_projectquota_upgradable(zfsvfs->z_os)) {
+                               dsl_pool_config_enter(
+                                   dmu_objset_pool(zfsvfs->z_os), FTAG);
+                               dmu_objset_id_quota_upgrade(zfsvfs->z_os);
+                               dsl_pool_config_exit(
+                                   dmu_objset_pool(zfsvfs->z_os), FTAG);
+                       }
+                       return (B_FALSE);
+               }
+               quotaobj = zfsvfs->z_projectobjquota_obj;
+       } else if (usedobj == DMU_USERUSED_OBJECT) {
+               quotaobj = zfsvfs->z_userobjquota_obj;
+       } else if (usedobj == DMU_GROUPUSED_OBJECT) {
+               quotaobj = zfsvfs->z_groupobjquota_obj;
+       } else {
+               return (B_FALSE);
+       }
        if (quotaobj == 0 || zfsvfs->z_replay)
                return (B_FALSE);
 
-       (void) sprintf(buf, "%llx", (longlong_t)fuid);
+       (void) sprintf(buf, "%llx", (longlong_t)id);
        err = zap_lookup(zfsvfs->z_os, quotaobj, buf, 8, 1, &quota);
        if (err != 0)
                return (B_FALSE);
 
-       (void) sprintf(buf, DMU_OBJACCT_PREFIX "%llx", (longlong_t)fuid);
+       (void) sprintf(buf, DMU_OBJACCT_PREFIX "%llx", (longlong_t)id);
        err = zap_lookup(zfsvfs->z_os, usedobj, buf, 8, 1, &used);
        if (err != 0)
                return (B_FALSE);
@@ -863,19 +936,35 @@ zfs_fuid_overobjquota(zfsvfs_t *zfsvfs, boolean_t isgroup, uint64_t fuid)
 }
 
 boolean_t
-zfs_fuid_overquota(zfsvfs_t *zfsvfs, boolean_t isgroup, uint64_t fuid)
+zfs_id_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id)
 {
        char buf[20];
-       uint64_t used, quota, usedobj, quotaobj;
+       uint64_t used, quota, quotaobj;
        int err;
 
-       usedobj = isgroup ? DMU_GROUPUSED_OBJECT : DMU_USERUSED_OBJECT;
-       quotaobj = isgroup ? zfsvfs->z_groupquota_obj : zfsvfs->z_userquota_obj;
-
+       if (usedobj == DMU_PROJECTUSED_OBJECT) {
+               if (!dmu_objset_projectquota_present(zfsvfs->z_os)) {
+                       if (dmu_objset_projectquota_upgradable(zfsvfs->z_os)) {
+                               dsl_pool_config_enter(
+                                   dmu_objset_pool(zfsvfs->z_os), FTAG);
+                               dmu_objset_id_quota_upgrade(zfsvfs->z_os);
+                               dsl_pool_config_exit(
+                                   dmu_objset_pool(zfsvfs->z_os), FTAG);
+                       }
+                       return (B_FALSE);
+               }
+               quotaobj = zfsvfs->z_projectquota_obj;
+       } else if (usedobj == DMU_USERUSED_OBJECT) {
+               quotaobj = zfsvfs->z_userquota_obj;
+       } else if (usedobj == DMU_GROUPUSED_OBJECT) {
+               quotaobj = zfsvfs->z_groupquota_obj;
+       } else {
+               return (B_FALSE);
+       }
        if (quotaobj == 0 || zfsvfs->z_replay)
                return (B_FALSE);
 
-       (void) sprintf(buf, "%llx", (longlong_t)fuid);
+       (void) sprintf(buf, "%llx", (longlong_t)id);
        err = zap_lookup(zfsvfs->z_os, quotaobj, buf, 8, 1, &quota);
        if (err != 0)
                return (B_FALSE);
@@ -887,20 +976,10 @@ zfs_fuid_overquota(zfsvfs_t *zfsvfs, boolean_t isgroup, uint64_t fuid)
 }
 
 boolean_t
-zfs_owner_overquota(zfsvfs_t *zfsvfs, znode_t *zp, boolean_t isgroup)
+zfs_id_overquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id)
 {
-       uint64_t fuid;
-       uint64_t quotaobj;
-       struct inode *ip = ZTOI(zp);
-
-       quotaobj = isgroup ? zfsvfs->z_groupquota_obj : zfsvfs->z_userquota_obj;
-
-       fuid = isgroup ? KGID_TO_SGID(ip->i_gid) : KUID_TO_SUID(ip->i_uid);
-
-       if (quotaobj == 0 || zfsvfs->z_replay)
-               return (B_FALSE);
-
-       return (zfs_fuid_overquota(zfsvfs, isgroup, fuid));
+       return (zfs_id_overblockquota(zfsvfs, usedobj, id) ||
+           zfs_id_overobjquota(zfsvfs, usedobj, id));
 }
 
 /*
@@ -1007,6 +1086,14 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os)
        else if (error != 0)
                return (error);
 
+       error = zap_lookup(os, MASTER_NODE_OBJ,
+           zfs_userquota_prop_prefixes[ZFS_PROP_PROJECTQUOTA],
+           8, 1, &zfsvfs->z_projectquota_obj);
+       if (error == ENOENT)
+               zfsvfs->z_projectquota_obj = 0;
+       else if (error != 0)
+               return (error);
+
        error = zap_lookup(os, MASTER_NODE_OBJ,
            zfs_userquota_prop_prefixes[ZFS_PROP_USEROBJQUOTA],
            8, 1, &zfsvfs->z_userobjquota_obj);
@@ -1023,6 +1110,14 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os)
        else if (error != 0)
                return (error);
 
+       error = zap_lookup(os, MASTER_NODE_OBJ,
+           zfs_userquota_prop_prefixes[ZFS_PROP_PROJECTOBJQUOTA],
+           8, 1, &zfsvfs->z_projectobjquota_obj);
+       if (error == ENOENT)
+               zfsvfs->z_projectobjquota_obj = 0;
+       else if (error != 0)
+               return (error);
+
        error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_FUID_TABLES, 8, 1,
            &zfsvfs->z_fuid_obj);
        if (error == ENOENT)
@@ -1264,6 +1359,83 @@ zfs_check_global_label(const char *dsname, const char *hexsl)
 }
 #endif /* HAVE_MLSLABEL */
 
+static int
+zfs_statfs_project(zfsvfs_t *zfsvfs, znode_t *zp, struct kstatfs *statp,
+    uint32_t bshift)
+{
+       char buf[20 + DMU_OBJACCT_PREFIX_LEN];
+       uint64_t offset = DMU_OBJACCT_PREFIX_LEN;
+       uint64_t quota;
+       uint64_t used;
+       int err;
+
+       strlcpy(buf, DMU_OBJACCT_PREFIX, DMU_OBJACCT_PREFIX_LEN + 1);
+       err = id_to_fuidstr(zfsvfs, NULL, zp->z_projid, buf + offset, B_FALSE);
+       if (err)
+               return (err);
+
+       if (zfsvfs->z_projectquota_obj == 0)
+               goto objs;
+
+       err = zap_lookup(zfsvfs->z_os, zfsvfs->z_projectquota_obj,
+           buf + offset, 8, 1, &quota);
+       if (err == ENOENT)
+               goto objs;
+       else if (err)
+               return (err);
+
+       err = zap_lookup(zfsvfs->z_os, DMU_PROJECTUSED_OBJECT,
+           buf + offset, 8, 1, &used);
+       if (unlikely(err == ENOENT)) {
+               uint32_t blksize;
+               u_longlong_t nblocks;
+
+               /*
+                * Quota accounting is async, so it is possible race case.
+                * There is at least one object with the given project ID.
+                */
+               sa_object_size(zp->z_sa_hdl, &blksize, &nblocks);
+               if (unlikely(zp->z_blksz == 0))
+                       blksize = zfsvfs->z_max_blksz;
+
+               used = blksize * nblocks;
+       } else if (err) {
+               return (err);
+       }
+
+       statp->f_blocks = quota >> bshift;
+       statp->f_bfree = (quota > used) ? ((quota - used) >> bshift) : 0;
+       statp->f_bavail = statp->f_bfree;
+
+objs:
+       if (zfsvfs->z_projectobjquota_obj == 0)
+               return (0);
+
+       err = zap_lookup(zfsvfs->z_os, zfsvfs->z_projectobjquota_obj,
+           buf + offset, 8, 1, &quota);
+       if (err == ENOENT)
+               return (0);
+       else if (err)
+               return (err);
+
+       err = zap_lookup(zfsvfs->z_os, DMU_PROJECTUSED_OBJECT,
+           buf, 8, 1, &used);
+       if (unlikely(err == ENOENT)) {
+               /*
+                * Quota accounting is async, so it is possible race case.
+                * There is at least one object with the given project ID.
+                */
+               used = 1;
+       } else if (err) {
+               return (err);
+       }
+
+       statp->f_files = quota;
+       statp->f_ffree = (quota > used) ? (quota - used) : 0;
+
+       return (0);
+}
+
 int
 zfs_statvfs(struct dentry *dentry, struct kstatfs *statp)
 {
@@ -1271,6 +1443,7 @@ zfs_statvfs(struct dentry *dentry, struct kstatfs *statp)
        uint64_t refdbytes, availbytes, usedobjs, availobjs;
        uint64_t fsid;
        uint32_t bshift;
+       int err = 0;
 
        ZFS_ENTER(zfsvfs);
 
@@ -1322,8 +1495,17 @@ zfs_statvfs(struct dentry *dentry, struct kstatfs *statp)
         */
        bzero(statp->f_spare, sizeof (statp->f_spare));
 
+       if (dmu_objset_projectquota_enabled(zfsvfs->z_os) &&
+           dmu_objset_projectquota_present(zfsvfs->z_os)) {
+               znode_t *zp = ITOZ(dentry->d_inode);
+
+               if (zp->z_pflags & ZFS_PROJINHERIT && zp->z_projid &&
+                   zpl_is_valid_projid(zp->z_projid))
+                       err = zfs_statfs_project(zfsvfs, zp, statp, bshift);
+       }
+
        ZFS_EXIT(zfsvfs);
-       return (0);
+       return (err);
 }
 
 int
@@ -2170,9 +2352,9 @@ EXPORT_SYMBOL(zfs_resume_fs);
 EXPORT_SYMBOL(zfs_userspace_one);
 EXPORT_SYMBOL(zfs_userspace_many);
 EXPORT_SYMBOL(zfs_set_userquota);
-EXPORT_SYMBOL(zfs_owner_overquota);
-EXPORT_SYMBOL(zfs_fuid_overquota);
-EXPORT_SYMBOL(zfs_fuid_overobjquota);
+EXPORT_SYMBOL(zfs_id_overblockquota);
+EXPORT_SYMBOL(zfs_id_overobjquota);
+EXPORT_SYMBOL(zfs_id_overquota);
 EXPORT_SYMBOL(zfs_set_version);
 EXPORT_SYMBOL(zfsvfs_create);
 EXPORT_SYMBOL(zfsvfs_free);
index e21fe1acae2a208be5c8a1aae39fc6aacd2e752f..f35165de3e8976cd95b2baab53e255935dc2517e 100644 (file)
@@ -79,6 +79,7 @@
 #include <sys/attr.h>
 #include <sys/zpl.h>
 #include <sys/zil.h>
+#include <sys/sa_impl.h>
 
 /*
  * Programming rules.
@@ -728,8 +729,13 @@ zfs_write(struct inode *ip, uio_t *uio, int ioflag, cred_t *cr)
        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 (zfs_id_overblockquota(zfsvfs, DMU_USERUSED_OBJECT,
+                   KUID_TO_SUID(ip->i_uid)) ||
+                   zfs_id_overblockquota(zfsvfs, DMU_GROUPUSED_OBJECT,
+                   KGID_TO_SGID(ip->i_gid)) ||
+                   (zp->z_projid != ZFS_DEFAULT_PROJID &&
+                   zfs_id_overblockquota(zfsvfs, DMU_PROJECTUSED_OBJECT,
+                   zp->z_projid))) {
                        if (abuf != NULL)
                                dmu_return_arcbuf(abuf);
                        error = SET_ERROR(EDQUOT);
@@ -1380,6 +1386,7 @@ top:
 
        if (zp == NULL) {
                uint64_t txtype;
+               uint64_t projid = ZFS_DEFAULT_PROJID;
 
                /*
                 * Create a new file object and update the directory
@@ -1408,7 +1415,9 @@ top:
                        goto out;
                have_acl = B_TRUE;
 
-               if (zfs_acl_ids_overquota(zfsvfs, &acl_ids)) {
+               if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode))
+                       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;
@@ -1552,6 +1561,7 @@ zfs_tmpfile(struct inode *dip, vattr_t *vap, int excl,
        uid_t           uid;
        gid_t           gid;
        zfs_acl_ids_t   acl_ids;
+       uint64_t        projid = ZFS_DEFAULT_PROJID;
        boolean_t       fuid_dirtied;
        boolean_t       have_acl = B_FALSE;
        boolean_t       waited = B_FALSE;
@@ -1598,7 +1608,9 @@ top:
                goto out;
        have_acl = B_TRUE;
 
-       if (zfs_acl_ids_overquota(zfsvfs, &acl_ids)) {
+       if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode))
+               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;
@@ -2009,7 +2021,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);
@@ -2591,6 +2603,17 @@ zfs_getattr(struct inode *ip, 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, atime);
@@ -2668,6 +2691,125 @@ zfs_getattr_fast(struct inode *ip, struct kstat *sp)
        return (0);
 }
 
+/*
+ * For the operation of changing file's user/group/project, we need to
+ * handle not only the main object that is assigned to the file directly,
+ * but also the ones that are used by the file via hidden xattr directory.
+ *
+ * Because the xattr directory may contains many EA entries, as to it may
+ * be impossible to change all of them via the transaction of changing the
+ * main object's user/group/project attributes. Then we have to change them
+ * via other multiple independent transactions one by one. It may be not good
+ * solution, but we have no better idea yet.
+ */
+static int
+zfs_setattr_dir(znode_t *dzp)
+{
+       struct inode    *dxip = ZTOI(dzp);
+       struct inode    *xip = NULL;
+       zfsvfs_t        *zfsvfs = ITOZSB(dxip);
+       objset_t        *os = zfsvfs->z_os;
+       zap_cursor_t    zc;
+       zap_attribute_t zap;
+       zfs_dirlock_t   *dl;
+       znode_t         *zp;
+       dmu_tx_t        *tx = NULL;
+       uint64_t        uid, gid;
+       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;
+
+               xip = ZTOI(zp);
+               if (KUID_TO_SUID(xip->i_uid) == KUID_TO_SUID(dxip->i_uid) &&
+                   KGID_TO_SGID(xip->i_gid) == KGID_TO_SGID(dxip->i_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 (KUID_TO_SUID(xip->i_uid) != KUID_TO_SUID(dxip->i_uid)) {
+                       xip->i_uid = dxip->i_uid;
+                       uid = zfs_uid_read(dxip);
+                       SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_UID(zfsvfs), NULL,
+                           &uid, sizeof (uid));
+               }
+
+               if (KGID_TO_SGID(xip->i_gid) != KGID_TO_SGID(dxip->i_gid)) {
+                       xip->i_gid = dxip->i_gid;
+                       gid = zfs_gid_read(dxip);
+                       SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GID(zfsvfs), NULL,
+                           &gid, sizeof (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 (xip) {
+                       iput(xip);
+                       xip = NULL;
+                       zfs_dirent_unlock(dl);
+               }
+               zap_cursor_advance(&zc);
+       }
+
+       if (tx)
+               dmu_tx_abort(tx);
+       if (xip) {
+               iput(xip);
+               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.
@@ -2691,6 +2833,7 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr)
 {
        znode_t         *zp = ITOZ(ip);
        zfsvfs_t        *zfsvfs = ITOZSB(ip);
+       objset_t        *os = zfsvfs->z_os;
        zilog_t         *zilog;
        dmu_tx_t        *tx;
        vattr_t         oldva;
@@ -2702,17 +2845,19 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr)
        uint64_t        new_kuid = 0, new_kgid = 0, new_uid, new_gid;
        uint64_t        xattr_obj;
        uint64_t        mtime[2], ctime[2], atime[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;
+       boolean_t       handle_eadir = B_FALSE;
        sa_bulk_attr_t  *bulk, *xattr_bulk;
-       int             count = 0, xattr_count = 0;
+       int             count = 0, xattr_count = 0, bulks = 8;
 
        if (mask == 0)
                return (0);
@@ -2720,6 +2865,39 @@ zfs_setattr(struct inode *ip, 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 & ATTR_XVATTR)) {
+               if (XVA_ISSET_REQ(xvap, XAT_PROJID)) {
+                       if (!dmu_objset_projectquota_enabled(os) ||
+                           (!S_ISREG(ip->i_mode) && !S_ISDIR(ip->i_mode))) {
+                               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) ||
+                   (!S_ISREG(ip->i_mode) && !S_ISDIR(ip->i_mode)))) {
+                               ZFS_EXIT(zfsvfs);
+                               return (SET_ERROR(ENOTSUP));
+               }
+       }
+
        zilog = zfsvfs->z_log;
 
        /*
@@ -2745,17 +2923,11 @@ zfs_setattr(struct inode *ip, 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);
-
        tmpxvattr = kmem_alloc(sizeof (xvattr_t), KM_SLEEP);
        xva_init(tmpxvattr);
 
-       bulk = kmem_alloc(sizeof (sa_bulk_attr_t) * 7, KM_SLEEP);
-       xattr_bulk = kmem_alloc(sizeof (sa_bulk_attr_t) * 7, KM_SLEEP);
+       bulk = kmem_alloc(sizeof (sa_bulk_attr_t) * bulks, KM_SLEEP);
+       xattr_bulk = kmem_alloc(sizeof (sa_bulk_attr_t) * bulks, KM_SLEEP);
 
        /*
         * Immutable files can only alter immutable bit and atime
@@ -2901,6 +3073,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)) {
@@ -3009,7 +3191,8 @@ top:
         */
        mask = vap->va_mask;
 
-       if ((mask & (ATTR_UID | ATTR_GID))) {
+       if ((mask & (ATTR_UID | ATTR_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));
 
@@ -3022,7 +3205,8 @@ top:
                        new_kuid = zfs_fuid_create(zfsvfs,
                            (uint64_t)vap->va_uid, cr, ZFS_OWNER, &fuidp);
                        if (new_kuid != KUID_TO_SUID(ZTOI(zp)->i_uid) &&
-                           zfs_fuid_overquota(zfsvfs, B_FALSE, new_kuid)) {
+                           zfs_id_overquota(zfsvfs, DMU_USERUSED_OBJECT,
+                           new_kuid)) {
                                if (attrzp)
                                        iput(ZTOI(attrzp));
                                err = SET_ERROR(EDQUOT);
@@ -3034,15 +3218,24 @@ top:
                        new_kgid = zfs_fuid_create(zfsvfs,
                            (uint64_t)vap->va_gid, cr, ZFS_GROUP, &fuidp);
                        if (new_kgid != KGID_TO_SGID(ZTOI(zp)->i_gid) &&
-                           zfs_fuid_overquota(zfsvfs, B_TRUE, new_kgid)) {
+                           zfs_id_overquota(zfsvfs, DMU_GROUPUSED_OBJECT,
+                           new_kgid)) {
                                if (attrzp)
                                        iput(ZTOI(attrzp));
                                err = SET_ERROR(EDQUOT);
                                goto out2;
                        }
                }
+
+               if (projid != ZFS_INVALID_PROJID &&
+                   zfs_id_overquota(zfsvfs, DMU_PROJECTUSED_OBJECT, projid)) {
+                       if (attrzp)
+                               iput(ZTOI(attrzp));
+                       err = EDQUOT;
+                       goto out2;
+               }
        }
-       tx = dmu_tx_create(zfsvfs->z_os);
+       tx = dmu_tx_create(os);
 
        if (mask & ATTR_MODE) {
                uint64_t pmode = zp->z_mode;
@@ -3075,8 +3268,10 @@ top:
                mutex_exit(&zp->z_lock);
                dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE);
        } else {
-               if ((mask & ATTR_XVATTR) &&
-                   XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP))
+               if (((mask & ATTR_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);
@@ -3105,6 +3300,26 @@ top:
         * updated as a side-effect of calling this function.
         */
 
+       if (projid != ZFS_INVALID_PROJID && !(zp->z_pflags & ZFS_PROJID)) {
+               /*
+                * For the existed 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 & (ATTR_UID|ATTR_GID|ATTR_MODE))
                mutex_enter(&zp->z_acl_lock);
@@ -3120,6 +3335,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 & (ATTR_UID|ATTR_GID)) {
@@ -3199,6 +3420,13 @@ top:
                    ctime, sizeof (ctime));
        }
 
+       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));
+       }
+
        if (attrzp && mask) {
                SA_ADD_BULK_ATTR(xattr_bulk, xattr_count,
                    SA_ZPL_CTIME(zfsvfs), NULL, &ctime,
@@ -3235,6 +3463,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(S_ISREG(ip->i_mode));
@@ -3258,7 +3489,7 @@ 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);
@@ -3279,20 +3510,24 @@ out:
                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 (attrzp) {
+                       if (err2 == 0 && handle_eadir)
+                               err2 = zfs_setattr_dir(attrzp);
                        iput(ZTOI(attrzp));
+               }
                zfs_inode_update(zp);
        }
 
 out2:
-       if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS)
+       if (os->os_sync == ZFS_SYNC_ALWAYS)
                zil_commit(zilog, 0);
 
 out3:
-       kmem_free(xattr_bulk, sizeof (sa_bulk_attr_t) * 7);
-       kmem_free(bulk, sizeof (sa_bulk_attr_t) * 7);
+       kmem_free(xattr_bulk, sizeof (sa_bulk_attr_t) * bulks);
+       kmem_free(bulk, sizeof (sa_bulk_attr_t) * bulks);
        kmem_free(tmpxvattr, sizeof (xvattr_t));
        ZFS_EXIT(zfsvfs);
        return (err);
@@ -3585,6 +3820,19 @@ top:
                return (terr);
        }
 
+       /*
+        * If we are using project inheritance, 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.
@@ -3683,6 +3931,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);
@@ -3829,7 +4079,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);
@@ -4012,6 +4262,18 @@ zfs_link(struct inode *tdip, struct inode *sip, char *name, cred_t *cr,
        szp = ITOZ(sip);
        ZFS_VERIFY_ZP(szp);
 
+       /*
+        * If we are using project inheritance, 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 i_sb because snapshots and the ctldir must have different
         * super blocks.
@@ -4206,8 +4468,13 @@ zfs_putpage(struct inode *ip, struct page *pp, struct writeback_control *wbc)
         * is to register a page_mkwrite() handler to count the page
         * against its quota when it is about to be dirtied.
         */
-       if (zfs_owner_overquota(zfsvfs, zp, B_FALSE) ||
-           zfs_owner_overquota(zfsvfs, zp, B_TRUE)) {
+       if (zfs_id_overblockquota(zfsvfs, DMU_USERUSED_OBJECT,
+           KUID_TO_SUID(ip->i_uid)) ||
+           zfs_id_overblockquota(zfsvfs, DMU_GROUPUSED_OBJECT,
+           KGID_TO_SGID(ip->i_gid)) ||
+           (zp->z_projid != ZFS_DEFAULT_PROJID &&
+           zfs_id_overblockquota(zfsvfs, DMU_PROJECTUSED_OBJECT,
+           zp->z_projid))) {
                err = EDQUOT;
        }
 #endif
index d3b68403b320403f04daf2433435e3af45b20eb4..5288c9c68498819aaba001fa9ca883f53138c78a 100644 (file)
@@ -328,6 +328,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);
@@ -558,6 +559,7 @@ zfs_znode_alloc(zfsvfs_t *zfsvfs, dmu_buf_t *db, int blksz,
        uint64_t links;
        uint64_t z_uid, z_gid;
        uint64_t atime[2], mtime[2], ctime[2];
+       uint64_t projid = ZFS_DEFAULT_PROJID;
        sa_bulk_attr_t bulk[11];
        int count = 0;
 
@@ -604,13 +606,17 @@ zfs_znode_alloc(zfsvfs_t *zfsvfs, dmu_buf_t *db, int blksz,
        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);
 
-       if (sa_bulk_lookup(zp->z_sa_hdl, bulk, count) != 0 || tmp_gen == 0) {
+       if (sa_bulk_lookup(zp->z_sa_hdl, bulk, count) != 0 || tmp_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);
                zp->z_sa_hdl = NULL;
                goto error;
        }
 
+       zp->z_projid = projid;
        zp->z_mode = ip->i_mode = mode;
        ip->i_generation = (uint32_t)tmp_gen;
        ip->i_blkbits = SPA_MINBLOCKSHIFT;
@@ -696,7 +702,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 = ZTOZSB(dzp);
        dmu_buf_t       *db;
@@ -771,14 +777,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;
        }
 
@@ -803,6 +807,23 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr,
        if (flag & IS_XATTR)
                pflags |= ZFS_XATTR;
 
+       if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) {
+               /*
+                * 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.
         */
@@ -884,6 +905,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 ||
            (S_ISBLK(vap->va_mode) || S_ISCHR(vap->va_mode))) {
@@ -942,6 +967,7 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr,
        (*zpp)->z_pflags = pflags;
        (*zpp)->z_mode = ZTOI(*zpp)->i_mode = mode;
        (*zpp)->z_dnodesize = dnodesize;
+       (*zpp)->z_projid = projid;
 
        if (obj_type == DMU_OT_ZNODE ||
            acl_ids->z_aclp->z_version < ZFS_ACL_VERSION_FUID) {
@@ -1049,6 +1075,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);
+       }
 
        if (update_inode)
                zfs_set_inode_flags(zp, ZTOI(zp));
@@ -1166,6 +1197,7 @@ zfs_rezget(znode_t *zp)
        uint64_t gen;
        uint64_t z_uid, z_gid;
        uint64_t atime[2], mtime[2], ctime[2];
+       uint64_t projid = ZFS_DEFAULT_PROJID;
        znode_hold_t *zh;
 
        /*
@@ -1241,6 +1273,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_znode_hold_exit(zfsvfs, zh);
+                       return (SET_ERROR(err));
+               }
+       }
+
+       zp->z_projid = projid;
        zp->z_mode = ZTOI(zp)->i_mode = mode;
        zfs_uid_write(ZTOI(zp), z_uid);
        zfs_gid_write(ZTOI(zp), z_gid);
@@ -1861,6 +1904,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;
 
        zfsvfs = kmem_zalloc(sizeof (zfsvfs_t), KM_SLEEP);
        zfsvfs->z_os = os;
index 4dfa0dea43583f892129a45e14343c0c005ea057..1c5f5e4096371d89016b41e483406e6a4093617d 100644 (file)
@@ -31,7 +31,7 @@
 #include <sys/zfs_vfsops.h>
 #include <sys/zfs_vnops.h>
 #include <sys/zfs_znode.h>
-#include <sys/zpl.h>
+#include <sys/zfs_project.h>
 
 
 static int
@@ -720,17 +720,14 @@ zpl_fallocate(struct file *filp, int mode, loff_t offset, loff_t len)
 }
 #endif /* HAVE_FILE_FALLOCATE */
 
-/*
- * Map zfs file z_pflags (xvattr_t) to linux file attributes. Only file
- * attributes common to both Linux and Solaris are mapped.
- */
-static int
-zpl_ioctl_getflags(struct file *filp, void __user *arg)
+#define        ZFS_FL_USER_VISIBLE     (FS_FL_USER_VISIBLE | ZFS_PROJINHERIT_FL)
+#define        ZFS_FL_USER_MODIFIABLE  (FS_FL_USER_MODIFIABLE | ZFS_PROJINHERIT_FL)
+
+static uint32_t
+__zpl_ioctl_getflags(struct inode *ip)
 {
-       struct inode *ip = file_inode(filp);
-       unsigned int ioctl_flags = 0;
        uint64_t zfs_flags = ITOZ(ip)->z_pflags;
-       int error;
+       uint32_t ioctl_flags = 0;
 
        if (zfs_flags & ZFS_IMMUTABLE)
                ioctl_flags |= FS_IMMUTABLE_FL;
@@ -741,11 +738,26 @@ zpl_ioctl_getflags(struct file *filp, void __user *arg)
        if (zfs_flags & ZFS_NODUMP)
                ioctl_flags |= FS_NODUMP_FL;
 
-       ioctl_flags &= FS_FL_USER_VISIBLE;
+       if (zfs_flags & ZFS_PROJINHERIT)
+               ioctl_flags |= ZFS_PROJINHERIT_FL;
 
-       error = copy_to_user(arg, &ioctl_flags, sizeof (ioctl_flags));
+       return (ioctl_flags & ZFS_FL_USER_VISIBLE);
+}
 
-       return (error);
+/*
+ * Map zfs file z_pflags (xvattr_t) to linux file attributes. Only file
+ * attributes common to both Linux and Solaris are mapped.
+ */
+static int
+zpl_ioctl_getflags(struct file *filp, void __user *arg)
+{
+       uint32_t flags;
+       int err;
+
+       flags = __zpl_ioctl_getflags(file_inode(filp));
+       err = copy_to_user(arg, &flags, sizeof (flags));
+
+       return (err);
 }
 
 /*
@@ -760,24 +772,16 @@ zpl_ioctl_getflags(struct file *filp, void __user *arg)
 #define        fchange(f0, f1, b0, b1) (!((f0) & (b0)) != !((f1) & (b1)))
 
 static int
-zpl_ioctl_setflags(struct file *filp, void __user *arg)
+__zpl_ioctl_setflags(struct inode *ip, uint32_t ioctl_flags, xvattr_t *xva)
 {
-       struct inode    *ip = file_inode(filp);
-       uint64_t        zfs_flags = ITOZ(ip)->z_pflags;
-       unsigned int    ioctl_flags;
-       cred_t          *cr = CRED();
-       xvattr_t        xva;
-       xoptattr_t      *xoap;
-       int             error;
-       fstrans_cookie_t cookie;
-
-       if (copy_from_user(&ioctl_flags, arg, sizeof (ioctl_flags)))
-               return (-EFAULT);
+       uint64_t zfs_flags = ITOZ(ip)->z_pflags;
+       xoptattr_t *xoap;
 
-       if ((ioctl_flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL)))
+       if (ioctl_flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL |
+           ZFS_PROJINHERIT_FL))
                return (-EOPNOTSUPP);
 
-       if ((ioctl_flags & ~(FS_FL_USER_MODIFIABLE)))
+       if (ioctl_flags & ~ZFS_FL_USER_MODIFIABLE)
                return (-EACCES);
 
        if ((fchange(ioctl_flags, zfs_flags, FS_IMMUTABLE_FL, ZFS_IMMUTABLE) ||
@@ -788,28 +792,100 @@ zpl_ioctl_setflags(struct file *filp, void __user *arg)
        if (!zpl_inode_owner_or_capable(ip))
                return (-EACCES);
 
-       xva_init(&xva);
-       xoap = xva_getxoptattr(&xva);
+       xva_init(xva);
+       xoap = xva_getxoptattr(xva);
 
-       XVA_SET_REQ(&xva, XAT_IMMUTABLE);
+       XVA_SET_REQ(xva, XAT_IMMUTABLE);
        if (ioctl_flags & FS_IMMUTABLE_FL)
                xoap->xoa_immutable = B_TRUE;
 
-       XVA_SET_REQ(&xva, XAT_APPENDONLY);
+       XVA_SET_REQ(xva, XAT_APPENDONLY);
        if (ioctl_flags & FS_APPEND_FL)
                xoap->xoa_appendonly = B_TRUE;
 
-       XVA_SET_REQ(&xva, XAT_NODUMP);
+       XVA_SET_REQ(xva, XAT_NODUMP);
        if (ioctl_flags & FS_NODUMP_FL)
                xoap->xoa_nodump = B_TRUE;
 
+       XVA_SET_REQ(xva, XAT_PROJINHERIT);
+       if (ioctl_flags & ZFS_PROJINHERIT_FL)
+               xoap->xoa_projinherit = B_TRUE;
+
+       return (0);
+}
+
+static int
+zpl_ioctl_setflags(struct file *filp, void __user *arg)
+{
+       struct inode *ip = file_inode(filp);
+       uint32_t flags;
+       cred_t *cr = CRED();
+       xvattr_t xva;
+       int err;
+       fstrans_cookie_t cookie;
+
+       if (copy_from_user(&flags, arg, sizeof (flags)))
+               return (-EFAULT);
+
+       err = __zpl_ioctl_setflags(ip, flags, &xva);
+       if (err)
+               return (err);
+
        crhold(cr);
        cookie = spl_fstrans_mark();
-       error = -zfs_setattr(ip, (vattr_t *)&xva, 0, cr);
+       err = -zfs_setattr(ip, (vattr_t *)&xva, 0, cr);
        spl_fstrans_unmark(cookie);
        crfree(cr);
 
-       return (error);
+       return (err);
+}
+
+static int
+zpl_ioctl_getxattr(struct file *filp, void __user *arg)
+{
+       zfsxattr_t fsx = { 0 };
+       struct inode *ip = file_inode(filp);
+       int err;
+
+       fsx.fsx_xflags = __zpl_ioctl_getflags(ip);
+       fsx.fsx_projid = ITOZ(ip)->z_projid;
+       err = copy_to_user(arg, &fsx, sizeof (fsx));
+
+       return (err);
+}
+
+static int
+zpl_ioctl_setxattr(struct file *filp, void __user *arg)
+{
+       struct inode *ip = file_inode(filp);
+       zfsxattr_t fsx;
+       cred_t *cr = CRED();
+       xvattr_t xva;
+       xoptattr_t *xoap;
+       int err;
+       fstrans_cookie_t cookie;
+
+       if (copy_from_user(&fsx, arg, sizeof (fsx)))
+               return (-EFAULT);
+
+       if (!zpl_is_valid_projid(fsx.fsx_projid))
+               return (-EINVAL);
+
+       err = __zpl_ioctl_setflags(ip, fsx.fsx_xflags, &xva);
+       if (err)
+               return (err);
+
+       xoap = xva_getxoptattr(&xva);
+       XVA_SET_REQ(&xva, XAT_PROJID);
+       xoap->xoa_projid = fsx.fsx_projid;
+
+       crhold(cr);
+       cookie = spl_fstrans_mark();
+       err = -zfs_setattr(ip, (vattr_t *)&xva, 0, cr);
+       spl_fstrans_unmark(cookie);
+       crfree(cr);
+
+       return (err);
 }
 
 static long
@@ -820,6 +896,10 @@ zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
                return (zpl_ioctl_getflags(filp, (void *)arg));
        case FS_IOC_SETFLAGS:
                return (zpl_ioctl_setflags(filp, (void *)arg));
+       case ZFS_IOC_FSGETXATTR:
+               return (zpl_ioctl_getxattr(filp, (void *)arg));
+       case ZFS_IOC_FSSETXATTR:
+               return (zpl_ioctl_setxattr(filp, (void *)arg));
        default:
                return (-ENOTTY);
        }
index 43dd5cb1fb84436cd5f9f05b6972fa0a82b6d5d6..da9c791f9b6cb1e53ce2abf85e2431757533f1d2 100644 (file)
@@ -616,6 +616,16 @@ tags = ['functional', 'poolversion']
 tests = ['privilege_001_pos', 'privilege_002_pos']
 tags = ['functional', 'privilege']
 
+[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_001_pos', 'projecttree_002_pos', 'projecttree_003_neg' ]
+tags = ['functional', 'projectquota']
+
 [tests/functional/quota]
 tests = ['quota_001_pos', 'quota_002_pos', 'quota_003_pos',
          'quota_004_pos', 'quota_005_pos', 'quota_006_neg']
@@ -728,7 +738,7 @@ tests = ['truncate_001_pos', 'truncate_002_pos', 'truncate_timestamps']
 tags = ['functional', 'truncate']
 
 [tests/functional/upgrade]
-tests = [ 'upgrade_userobj_001_pos' ]
+tests = ['upgrade_userobj_001_pos', 'upgrade_projectquota_001_pos']
 tags = ['functional', 'upgrade']
 
 [tests/functional/userquota]
index e6f04060ca281d98dceb06925f4b09241fd36755..4aede9f096170e6ba2e0e57157a0013c960a44ce 100644 (file)
@@ -65,6 +65,7 @@ export SYSTEM_FILES='arp
     logname
     losetup
     ls
+    lsattr
     lsblk
     lsmod
     lsscsi
index 1e9bbd4a484f592884b4cb51cac0091f922356a3..9df1d8e3ef0ec773d24c10980f7d428065513ed8 100644 (file)
@@ -43,6 +43,7 @@ SUBDIRS = \
        pool_names \
        poolversion \
        privilege \
+       projectquota \
        quota \
        raidz \
        redundancy \
index 1e184db823d81baf5569d1f5b14b7d239d8e5fc6..d5791372d0e4389a807b2e0f65ba7f749a9ada30 100644 (file)
@@ -79,5 +79,6 @@ if is_linux; then
            "feature@large_dnode"
            "feature@userobj_accounting"
            "feature@encryption"
+           "feature@project_quota"
        )
 fi
index d8e79d1a2333c0336c2c5333e91a6e79d10ef3c6..badd83bed45c9bb223e51b4ec836824a2238610c 100755 (executable)
 
 . $STF_SUITE/include/libtest.shlib
 
+if is_linux; then
+       log_unsupported "Requires pfexec command"
+fi
+
 ZFS_USER=zfsrbac
 USES_NIS=false
 
diff --git a/tests/zfs-tests/tests/functional/projectquota/Makefile.am b/tests/zfs-tests/tests/functional/projectquota/Makefile.am
new file mode 100644 (file)
index 0000000..4abfda0
--- /dev/null
@@ -0,0 +1,25 @@
+pkgdatadir = $(datadir)/@PACKAGE@/zfs-tests/tests/functional/projectquota
+dist_pkgdata_SCRIPTS = \
+       projectquota.cfg \
+       projectquota_common.kshlib \
+       setup.ksh \
+       cleanup.ksh \
+       projectid_001_pos.ksh \
+       projectid_002_pos.ksh \
+       projectid_003_pos.ksh \
+       projectquota_001_pos.ksh \
+       projectquota_002_pos.ksh \
+       projectquota_003_pos.ksh \
+       projectquota_004_neg.ksh \
+       projectquota_005_pos.ksh \
+       projectquota_006_pos.ksh \
+       projectquota_007_pos.ksh \
+       projectquota_008_pos.ksh \
+       projectquota_009_pos.ksh \
+       projectspace_001_pos.ksh \
+       projectspace_002_pos.ksh \
+       projectspace_003_pos.ksh \
+       projectspace_004_pos.ksh \
+       projecttree_001_pos.ksh \
+       projecttree_002_pos.ksh \
+       projecttree_003_neg.ksh
diff --git a/tests/zfs-tests/tests/functional/projectquota/cleanup.ksh b/tests/zfs-tests/tests/functional/projectquota/cleanup.ksh
new file mode 100755 (executable)
index 0000000..0440e3d
--- /dev/null
@@ -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/tests/zfs-tests/tests/functional/projectquota/projectid_001_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectid_001_pos.ksh
new file mode 100755 (executable)
index 0000000..5f56d88
--- /dev/null
@@ -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 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
+}
+
+if ! lsattr -pd > /dev/null 2>&1; then
+       log_unsupported "Current e2fsprogs does not support set/show project ID"
+fi
+
+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 eval "lsattr -p $PRJFILE | grep $PRJID1 | grep '\- '"
+log_must chattr -p $PRJID1 $PRJDIR
+log_must eval "lsattr -pd $PRJDIR | grep $PRJID1 | grep '\- '"
+
+log_must chattr +P $PRJDIR
+log_must eval "lsattr -pd $PRJDIR | grep $PRJID1 | grep '\-P '"
+
+# "-1" is invalid project ID, should be denied
+log_mustnot chattr -p -1 $PRJFILE
+log_must eval "lsattr -p $PRJFILE | grep $PRJID1 | grep '\- '"
+
+log_must mkdir $PRJDIR/dchild
+log_must eval "lsattr -pd $PRJDIR/dchild | grep $PRJID1 | grep '\-P '"
+log_must touch $PRJDIR/fchild
+log_must eval "lsattr -p $PRJDIR/fchild | grep $PRJID1"
+
+log_must touch $PRJDIR/dchild/foo
+log_must eval "lsattr -p $PRJDIR/dchild/foo | grep $PRJID1"
+
+# not support project ID/flag on symlink
+log_must ln -s $PRJDIR/dchild/foo $PRJDIR/dchild/s_foo
+log_mustnot lsattr -p $PRJDIR/dchild/s_foo
+log_mustnot chattr -p 123 $PRJDIR/dchild/s_foo
+log_mustnot chattr +P $PRJDIR/dchild/s_foo
+
+# 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 chattr +P $PRJDIR/dchild/b_foo
+
+# 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 chattr +P $PRJDIR/dchild/c_foo
+
+log_pass "Check project ID/flags can be set/inherited properly"
diff --git a/tests/zfs-tests/tests/functional/projectquota/projectid_002_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectid_002_pos.ksh
new file mode 100755 (executable)
index 0000000..1a402e2
--- /dev/null
@@ -0,0 +1,88 @@
+#!/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:
+#      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
+}
+
+if ! lsattr -pd > /dev/null 2>&1; then
+       log_unsupported "Current e2fsprogs does not support set/show project ID"
+fi
+
+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 chattr +P -p $PRJID2 $PRJDIR2
+
+log_must touch $PRJDIR1/tfile1
+log_must touch $PRJDIR1/tfile2
+log_must eval "lsattr -p $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 mv $PRJDIR3/dir $PRJDIR2/
+log_must eval "lsattr -dp $PRJDIR2/dir | grep $PRJID2"
+
+log_must chattr +P -p $PRJID1 $PRJDIR3
+log_must ln $PRJDIR1/tfile2 $PRJDIR3/tfile3
+
+log_pass "Project ID affects POSIX behavior"
diff --git a/tests/zfs-tests/tests/functional/projectquota/projectid_003_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectid_003_pos.ksh
new file mode 100755 (executable)
index 0000000..d6dbaaf
--- /dev/null
@@ -0,0 +1,81 @@
+#!/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.
+#
+
+. $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
+}
+
+if ! lsattr -pd > /dev/null 2>&1; then
+       log_unsupported "Current e2fsprogs does not support set/show project ID"
+fi
+
+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 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
+
+sync_pool
+typeset prj_bef=$(project_obj_count $QFS $PRJID1)
+
+log_must chattr -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/tests/zfs-tests/tests/functional/projectquota/projectquota.cfg b/tests/zfs-tests/tests/functional/projectquota/projectquota.cfg
new file mode 100644 (file)
index 0000000..564ab3e
--- /dev/null
@@ -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/tests/zfs-tests/tests/functional/projectquota/projectquota_001_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectquota_001_pos.ksh
new file mode 100755 (executable)
index 0000000..3f8c3d6
--- /dev/null
@@ -0,0 +1,88 @@
+#!/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 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 overcreate 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
+}
+
+if ! lsattr -pd > /dev/null 2>&1; then
+       log_unsupported "Current e2fsprogs does not support set/show project ID"
+fi
+
+log_onexit cleanup
+
+log_assert "If operation overwrite 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 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=sa $QFS
+log_must user_run $PUSER mkdir $PRJDIR
+log_must chattr +P -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_mustnot user_run $PUSER chattr -p $PRJID2 $PRJFILE
+
+log_pass "Operation overwrite project{obj}quota size failed as expect"
diff --git a/tests/zfs-tests/tests/functional/projectquota/projectquota_002_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectquota_002_pos.ksh
new file mode 100755 (executable)
index 0000000..c036190
--- /dev/null
@@ -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. All rights reserved.
+#
+
+. $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=/var/tmp/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 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 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/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/tests/zfs-tests/tests/functional/projectquota/projectquota_003_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectquota_003_pos.ksh
new file mode 100755 (executable)
index 0000000..06f360d
--- /dev/null
@@ -0,0 +1,98 @@
+#!/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 basic function project{obj}used
+#
+#
+# STRATEGY:
+#      1. Write data to fs with some project then check the project{obj}used
+#
+
+function cleanup
+{
+       cleanup_projectquota
+}
+
+if ! lsattr -pd > /dev/null 2>&1; then
+       log_unsupported "Current e2fsprogs does not support set/show project ID"
+fi
+
+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 != 0 ]]; then
+       log_fail "FAIL: projectused is $project_used, should be 0"
+fi
+
+mkmount_writable $QFS
+log_must user_run $PUSER mkdir $PRJDIR
+log_must chattr +P -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 != 0 ]]; then
+       log_fail "FAIL: projectobjused is $project_obj_used, should be 0"
+fi
+
+log_must zfs set xattr=sa $QFS
+log_must user_run $PUSER mkdir $PRJDIR
+log_must chattr +P -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)
+if [[ $project_obj_used != $file_count ]]; then
+       log_note "project $PRJID2 used is $project_obj_used"
+       log_fail "projectobjused for project $PRJID2 expected to be " \
+           "$file_count, not $project_obj_used"
+fi
+
+log_pass "Check the basic function of project{obj}used pass as expect"
diff --git a/tests/zfs-tests/tests/functional/projectquota/projectquota_004_neg.ksh b/tests/zfs-tests/tests/functional/projectquota/projectquota_004_neg.ksh
new file mode 100755 (executable)
index 0000000..df0eda7
--- /dev/null
@@ -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 numberic id even that id is not existed"
+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/tests/zfs-tests/tests/functional/projectquota/projectquota_005_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectquota_005_pos.ksh
new file mode 100755 (executable)
index 0000000..b52f302
--- /dev/null
@@ -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/tests/zfs-tests/tests/functional/projectquota/projectquota_006_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectquota_006_pos.ksh
new file mode 100755 (executable)
index 0000000..6b375d4
--- /dev/null
@@ -0,0 +1,75 @@
+#!/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
+}
+
+if ! lsattr -pd > /dev/null 2>&1; then
+       log_unsupported "Current e2fsprogs does not support set/show project ID"
+fi
+
+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 user_run $PUSER mkfile 100m $PRJDIR/qf
+sync
+
+log_note "set projectquota at a smaller size than it 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/tests/zfs-tests/tests/functional/projectquota/projectquota_007_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectquota_007_pos.ksh
new file mode 100755 (executable)
index 0000000..3572e01
--- /dev/null
@@ -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/tests/zfs-tests/tests/functional/projectquota/projectquota_008_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectquota_008_pos.ksh
new file mode 100755 (executable)
index 0000000..365b562
--- /dev/null
@@ -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/tests/zfs-tests/tests/functional/projectquota/projectquota_009_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectquota_009_pos.ksh
new file mode 100755 (executable)
index 0000000..a867b53
--- /dev/null
@@ -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/tests/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib b/tests/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib
new file mode 100644 (file)
index 0000000..23f7c2a
--- /dev/null
@@ -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/tests/zfs-tests/tests/functional/projectquota/projectspace_001_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectspace_001_pos.ksh
new file mode 100755 (executable)
index 0000000..a84ff9f
--- /dev/null
@@ -0,0 +1,93 @@
+#!/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.
+#
+
+. $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
+}
+
+if ! lsattr -pd > /dev/null 2>&1; then
+       log_unsupported "Current e2fsprogs does not support set/show project ID"
+fi
+
+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 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/tests/zfs-tests/tests/functional/projectquota/projectspace_002_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectspace_002_pos.ksh
new file mode 100755 (executable)
index 0000000..216855e
--- /dev/null
@@ -0,0 +1,85 @@
+#!/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 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
+}
+
+if ! lsattr -pd > /dev/null 2>&1; then
+       log_unsupported "Current e2fsprogs does not support set/show project ID"
+fi
+
+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 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/tests/zfs-tests/tests/functional/projectquota/projectspace_003_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectspace_003_pos.ksh
new file mode 100755 (executable)
index 0000000..629b3b3
--- /dev/null
@@ -0,0 +1,118 @@
+#!/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 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
+}
+
+if ! lsattr -pd > /dev/null 2>&1; then
+       log_unsupported "Current e2fsprogs does not support set/show project ID"
+fi
+
+log_onexit cleanup
+
+log_assert "Check the zfs projectspace object used"
+
+mkmount_writable $QFS
+log_must zfs set xattr=sa $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
+
+((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 -eq $prj_cnt1 ]] ||
+               log_fail "($PRJID1) expected $prj_cnt1, got $prjused"
+       prjused=$(project_obj_count $fs $PRJID2)
+       [[ $prjused -eq $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*
+sync_pool
+
+prjused=$(project_obj_count $QFS $PRJID1)
+[[ $prjused -eq 1 ]] ||
+       log_fail "expected 1 for project $PRJID1, got $prjused"
+
+prjused=$(project_obj_count $snapfs $PRJID1)
+[[ $prjused -eq $prj_cnt1 ]] ||
+       log_fail "expected $prj_cnt1 for $PRJID1 in snapfs, got $prjused"
+
+prjused=$(project_obj_count $QFS $PRJID2)
+[[ $prjused -eq $((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 -eq 0 ]] || log_fail "expected 0 for $PRJID1, got $prjused"
+
+cleanup
+log_pass "Check the zfs projectspace object used"
diff --git a/tests/zfs-tests/tests/functional/projectquota/projectspace_004_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectspace_004_pos.ksh
new file mode 100755 (executable)
index 0000000..494d7f3
--- /dev/null
@@ -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.
+#
+
+. $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
+}
+
+if ! lsattr -pd > /dev/null 2>&1; then
+       log_unsupported "Current e2fsprogs does not support set/show project ID"
+fi
+
+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 user_run $PUSER mkfile 50m $PRJDIR/qf
+sync_pool
+
+total=$(df $PRJDIR | tail -n 1 | awk '{ print $2 }')
+[[ $total -eq 102400 ]] || log_fail "expect '102400' resource, but got '$total'"
+
+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/tests/zfs-tests/tests/functional/projectquota/projecttree_001_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projecttree_001_pos.ksh
new file mode 100755 (executable)
index 0000000..570e6a8
--- /dev/null
@@ -0,0 +1,98 @@
+#!/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
+
+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 '\- '"
+
+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 '\- '"
+
+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 '\- '"
+
+log_pass "Check 'zfs project' is compatible with chattr/lsattr"
diff --git a/tests/zfs-tests/tests/functional/projectquota/projecttree_002_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projecttree_002_pos.ksh
new file mode 100755 (executable)
index 0000000..4008811
--- /dev/null
@@ -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 directroy 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/tests/zfs-tests/tests/functional/projectquota/projecttree_003_neg.ksh b/tests/zfs-tests/tests/functional/projectquota/projecttree_003_neg.ksh
new file mode 100755 (executable)
index 0000000..33382fd
--- /dev/null
@@ -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/tests/zfs-tests/tests/functional/projectquota/setup.ksh b/tests/zfs-tests/tests/functional/projectquota/setup.ksh
new file mode 100755 (executable)
index 0000000..c81b300
--- /dev/null
@@ -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 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
index 31034342f30cf008b8ebebdf5278f0f786896088..ee1b928465f0ad6ac1234c03fe5d615a242b661c 100644 (file)
@@ -1,5 +1,7 @@
 pkgdatadir = $(datadir)/@PACKAGE@/zfs-tests/tests/functional/upgrade
 dist_pkgdata_SCRIPTS = \
+       upgrade_common.kshlib \
        setup.ksh \
        cleanup.ksh \
-       upgrade_userobj_001_pos.ksh
+       upgrade_userobj_001_pos.ksh \
+       upgrade_projectquota_001_pos.ksh
index 19f4de24a9bef4e27948121b2a7228a16cee5e7e..1f0c9b63d9f1b3f1a3a1fecaa56aa83eb4ea2579 100755 (executable)
 # Copyright (c) 2016 by Jinshan Xiong. No rights reserved.
 #
 
-. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/tests/functional/upgrade/upgrade_common.kshlib
 
 verify_runnable "global"
 
-log_must zpool destroy $TESTPOOL
-
-log_must rm /tmp/zpool_upgrade_test.dat
+log_must rm -f $TMPDEV
 
 default_cleanup
index c3b89b3047dfdf4df03fb863f85077d06174bd2d..c25d25df6b9e45f88f1cc0a1ef9e6bc52ee0d22a 100755 (executable)
 # Copyright (c) 2016 by Jinshan Xiong. No rights reserved.
 #
 
-. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/tests/functional/upgrade/upgrade_common.kshlib
 
 verify_runnable "global"
 
 # create a pool without any features
-log_must mkfile 128m /tmp/zpool_upgrade_test.dat
-log_must zpool create -d -m $TESTDIR $TESTPOOL /tmp/zpool_upgrade_test.dat
+log_must mkfile 128m $TMPDEV
 
 log_pass
diff --git a/tests/zfs-tests/tests/functional/upgrade/upgrade_common.kshlib b/tests/zfs-tests/tests/functional/upgrade/upgrade_common.kshlib
new file mode 100644 (file)
index 0000000..2ff0cb7
--- /dev/null
@@ -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=/tmp/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/tests/zfs-tests/tests/functional/upgrade/upgrade_projectquota_001_pos.ksh b/tests/zfs-tests/tests/functional/upgrade/upgrade_projectquota_001_pos.ksh
new file mode 100755 (executable)
index 0000000..27449ad
--- /dev/null
@@ -0,0 +1,128 @@
+#!/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.
+#
+
+. $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"
+
+if ! lsattr -pd > /dev/null 2>&1; then
+       log_unsupported "Current e2fsprogs does not support set/show project ID"
+fi
+
+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
+
+# 'chattr -p' should fail before upgrade
+log_mustnot chattr -p 100 $TESTDIR/fs3/dir
+
+# 'chattr +P' should fail before upgrade
+log_mustnot chattr +P $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"
+
+# "lsattr -p" should NOT trigger upgrade
+log_must lsattr -p -d $TESTDIR/fs3/dir
+zfs projectspace -o used $TESTPOOL/fs3 | grep -q "USED" &&
+       log_fail "project quota should not active for $TESTPOOL/fs3"
+
+# 'chattr -p' should trigger dataset upgrade
+log_must chattr -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"
index dda594f4e2ab0b0072a99100867f337d7512fb47..b437a0cdfa3d4835961b2348be88e7ad6a27399b 100755 (executable)
@@ -25,7 +25,7 @@
 # Copyright (c) 2017 Datto Inc.
 #
 
-. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/tests/functional/upgrade/upgrade_common.kshlib
 
 #
 # DESCRIPTION:
 # 4. Make sure manual upgrade work
 #
 
-function cleanup
-{
-       datasetexists $TESTPOOL/fs1 && log_must zfs destroy $TESTPOOL/fs1
-       datasetexists $TESTPOOL/fs2 && log_must zfs destroy $TESTPOOL/fs2
-}
-
 verify_runnable "global"
 
 log_assert "pool upgrade for userobj accounting should work"
-log_onexit cleanup
+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