From 84379097a86066d896653823ecec48916d4808f5 Mon Sep 17 00:00:00 2001 From: Fan Yong Date: Tue, 4 Jul 2017 10:47:33 +0800 Subject: [PATCH] LU-7991 quota: Project Quota on ZFS 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 that can be set/modified via 'chattr -p' explicitly, or inherited from its parent object when created if such parent has the project inherit flag (via 'chattr +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 when an 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 Signed-off-by: Fan Yong Change-Id: Ib4f0544602e03fb61fd46a849d7ba51a6005693c --- cmd/zdb/zdb.c | 27 ++- cmd/zfs/zfs_main.c | 117 ++++++++- cmd/zhack/zhack.c | 2 +- cmd/zpool/zpool_main.c | 1 + configure.ac | 1 + include/sys/dmu.h | 5 +- include/sys/dmu_objset.h | 31 ++- include/sys/dnode.h | 6 +- include/sys/dsl_deleg.h | 4 + include/sys/fs/zfs.h | 8 +- include/sys/sa.h | 1 + include/sys/spa.h | 6 + include/sys/xvattr.h | 9 +- include/sys/zfs_acl.h | 2 +- include/sys/zfs_sa.h | 3 + include/sys/zfs_vfsops.h | 14 +- include/sys/zfs_znode.h | 6 +- include/sys/zpl.h | 26 ++ include/zfeature_common.h | 1 + include/zfs_deleg.h | 4 + lib/libzfs/libzfs_dataset.c | 35 ++- man/man5/zpool-features.5 | 33 +++ man/man8/zfs.8 | 99 ++++++++ module/zcommon/zfs_deleg.c | 4 + module/zcommon/zfs_prop.c | 6 +- module/zfs/dbuf.c | 2 +- module/zfs/dmu.c | 4 +- module/zfs/dmu_objset.c | 194 ++++++++++++--- module/zfs/dmu_traverse.c | 15 +- module/zfs/dnode.c | 21 +- module/zfs/dsl_pool.c | 2 +- module/zfs/dsl_scan.c | 7 +- module/zfs/sa.c | 19 ++ module/zfs/spa.c | 2 +- module/zfs/zfeature.c | 2 +- module/zfs/zfeature_common.c | 11 + module/zfs/zfs_acl.c | 10 +- module/zfs/zfs_dir.c | 4 +- module/zfs/zfs_ioctl.c | 35 ++- module/zfs/zfs_log.c | 14 +- module/zfs/zfs_replay.c | 13 +- module/zfs/zfs_sa.c | 29 ++- module/zfs/zfs_vfsops.c | 222 ++++++++++++------ module/zfs/zfs_vnops.c | 169 +++++++++++-- module/zfs/zfs_znode.c | 78 +++++- module/zfs/zpl_file.c | 64 ++++- tests/runfiles/linux.run | 9 +- tests/zfs-tests/include/commands.cfg | 1 + tests/zfs-tests/tests/functional/Makefile.am | 1 + .../cli_root/zpool_get/zpool_get.cfg | 1 + .../tests/functional/projectquota/Makefile.am | 20 ++ .../tests/functional/projectquota/cleanup.ksh | 38 +++ .../projectquota/projectid_001_pos.ksh | 80 +++++++ .../projectquota/projectid_002_pos.ksh | 81 +++++++ .../functional/projectquota/projectquota.cfg | 41 ++++ .../projectquota/projectquota_001_pos.ksh | 81 +++++++ .../projectquota/projectquota_002_pos.ksh | 87 +++++++ .../projectquota/projectquota_003_pos.ksh | 96 ++++++++ .../projectquota/projectquota_004_neg.ksh | 88 +++++++ .../projectquota/projectquota_005_pos.ksh | 69 ++++++ .../projectquota/projectquota_006_pos.ksh | 73 ++++++ .../projectquota/projectquota_007_pos.ksh | 59 +++++ .../projectquota/projectquota_008_pos.ksh | 93 ++++++++ .../projectquota/projectquota_009_pos.ksh | 133 +++++++++++ .../projectquota/projectquota_common.kshlib | 57 +++++ .../projectquota/projectspace_001_pos.ksh | 81 +++++++ .../projectquota/projectspace_002_pos.ksh | 83 +++++++ .../projectquota/projectspace_003_pos.ksh | 126 ++++++++++ .../tests/functional/projectquota/setup.ksh | 56 +++++ .../tests/functional/upgrade/Makefile.am | 4 +- .../tests/functional/upgrade/cleanup.ksh | 6 +- .../tests/functional/upgrade/setup.ksh | 5 +- .../functional/upgrade/upgrade_common.kshlib | 41 ++++ .../upgrade/upgrade_projectquota_001_pos.ksh | 108 +++++++++ .../upgrade/upgrade_userobj_001_pos.ksh | 12 +- 75 files changed, 2749 insertions(+), 249 deletions(-) create mode 100644 tests/zfs-tests/tests/functional/projectquota/Makefile.am create mode 100755 tests/zfs-tests/tests/functional/projectquota/cleanup.ksh create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectid_001_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectid_002_pos.ksh create mode 100644 tests/zfs-tests/tests/functional/projectquota/projectquota.cfg create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectquota_001_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectquota_002_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectquota_003_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectquota_004_neg.ksh create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectquota_005_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectquota_006_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectquota_007_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectquota_008_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectquota_009_pos.ksh create mode 100644 tests/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectspace_001_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectspace_002_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/projectquota/projectspace_003_pos.ksh create mode 100755 tests/zfs-tests/tests/functional/projectquota/setup.ksh create mode 100644 tests/zfs-tests/tests/functional/upgrade/upgrade_common.kshlib create mode 100755 tests/zfs-tests/tests/functional/upgrade/upgrade_projectquota_001_pos.ksh diff --git a/cmd/zdb/zdb.c b/cmd/zdb/zdb.c index 6c4071060377..4a8882e2100b 100644 --- a/cmd/zdb/zdb.c +++ b/cmd/zdb/zdb.c @@ -1785,6 +1785,7 @@ dump_znode(objset_t *os, uint64_t object, void *data, size_t size) uint64_t xattr, rdev, gen; uint64_t uid, gid, mode, fsize, parent, links; uint64_t pflags; + int64_t projid; uint64_t acctm[2], modtm[2], chgtm[2], crtm[2]; time_t z_crtime, z_atime, z_mtime, z_ctime; sa_bulk_attr_t bulk[12]; @@ -1819,10 +1820,8 @@ dump_znode(objset_t *os, uint64_t object, void *data, size_t size) SA_ADD_BULK_ATTR(bulk, idx, sa_attr_table[ZPL_FLAGS], NULL, &pflags, 8); - if (sa_bulk_lookup(hdl, bulk, idx)) { - (void) sa_handle_destroy(hdl); - return; - } + if (sa_bulk_lookup(hdl, bulk, idx)) + goto out; z_crtime = (time_t)crtm[0]; z_atime = (time_t)acctm[0]; @@ -1848,6 +1847,15 @@ 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)) { + error = sa_lookup(hdl, sa_attr_table[ZPL_PROJID], &projid, 8); + if (error == ENOENT) + projid = ZFS_INVALID_PROJID; + else if (errno != 0) + goto out; + + (void) printf("\tprojid %lld\n", (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); @@ -1855,6 +1863,8 @@ dump_znode(objset_t *os, uint64_t object, void *data, size_t size) sizeof (uint64_t)) == 0) (void) printf("\trdev 0x%016llx\n", (u_longlong_t)rdev); dump_znode_sa_xattr(hdl); + +out: sa_handle_destroy(hdl); } @@ -1910,8 +1920,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 */ @@ -2133,6 +2143,11 @@ dump_dir(objset_t *os) dump_object(os, DMU_GROUPUSED_OBJECT, verbosity, &print_header); } + if (DMU_PROJECTUSED_DNODE(os) != NULL && + DMU_PROJECTUSED_DNODE(os)->dn_type != 0) + dump_object(os, DMU_PROJECTUSED_OBJECT, verbosity, + &print_header); + object = 0; while ((error = dmu_object_next(os, &object, B_FALSE, 0)) == 0) { dump_object(os, object, verbosity, &print_header); diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index e8fe6a9fa9bf..81c05306b0e2 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -147,6 +147,7 @@ typedef enum { HELP_UNALLOW, HELP_USERSPACE, HELP_GROUPSPACE, + HELP_PROJECTSPACE, HELP_HOLD, HELP_HOLDS, HELP_RELEASE, @@ -188,6 +189,7 @@ static zfs_command_t command_table[] = { { "upgrade", zfs_do_upgrade, HELP_UPGRADE }, { "userspace", zfs_do_userspace, HELP_USERSPACE }, { "groupspace", zfs_do_userspace, HELP_GROUPSPACE }, + { "projectspace", zfs_do_userspace, HELP_PROJECTSPACE }, { NULL }, { "mount", zfs_do_mount, HELP_MOUNT }, { "unmount", zfs_do_unmount, HELP_UNMOUNT }, @@ -314,6 +316,10 @@ get_usage(zfs_help_t idx) "[-s field] ...\n" "\t [-S field] ... [-t type[,...]] " "\n")); + case HELP_PROJECTSPACE: + return (gettext("\tprojectspace [-Hp] [-o field[,...]] " + "[-s field] ...\n" + "\t [-S field] ... \n")); case HELP_HOLD: return (gettext("\thold [-r] ...\n")); case HELP_HOLDS: @@ -448,10 +454,26 @@ usage(boolean_t requested) (void) fprintf(fp, " NO NO \n"); (void) fprintf(fp, "\t%-15s ", "groupused@..."); (void) fprintf(fp, " NO NO \n"); + (void) fprintf(fp, "\t%-15s ", "projectused@..."); + (void) fprintf(fp, " NO NO \n"); + (void) fprintf(fp, "\t%-15s ", "userobjused@..."); + (void) fprintf(fp, " NO NO \n"); + (void) fprintf(fp, "\t%-15s ", "groupobjused@..."); + (void) fprintf(fp, " NO NO \n"); + (void) fprintf(fp, "\t%-15s ", "projectobjused@..."); + (void) fprintf(fp, " NO NO \n"); (void) fprintf(fp, "\t%-15s ", "userquota@..."); (void) fprintf(fp, "YES NO | none\n"); (void) fprintf(fp, "\t%-15s ", "groupquota@..."); (void) fprintf(fp, "YES NO | none\n"); + (void) fprintf(fp, "\t%-15s ", "projectquota@..."); + (void) fprintf(fp, "YES NO | none\n"); + (void) fprintf(fp, "\t%-15s ", "userobjquota@..."); + (void) fprintf(fp, "YES NO | none\n"); + (void) fprintf(fp, "\t%-15s ", "groupobjquota@..."); + (void) fprintf(fp, "YES NO | none\n"); + (void) fprintf(fp, "\t%-15s ", "projectobjquota@..."); + (void) fprintf(fp, "YES NO | none\n"); (void) fprintf(fp, "\t%-15s ", "written@"); (void) fprintf(fp, " NO NO \n"); @@ -459,9 +481,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" + "an 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" @@ -2230,6 +2252,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. @@ -2263,8 +2287,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, @@ -2419,6 +2445,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) { @@ -2431,6 +2464,8 @@ us_type2str(unsigned field_type) return ("SMB User"); case USTYPE_SMB_GRP: return ("SMB Group"); + case USTYPE_PROJ: + return ("Project"); default: return ("Undefined"); } @@ -2516,7 +2551,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; @@ -2524,6 +2559,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; } } @@ -2575,7 +2612,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)); @@ -2585,21 +2624,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); @@ -2786,7 +2828,7 @@ zfs_do_userspace(int argc, char **argv) int ret = 0; int c; zfs_sort_column_t *sortcol = NULL; - int types = USTYPE_PSX_USR | USTYPE_SMB_USR; + int types; us_cbdata_t cb; us_node_t *node; us_node_t *rmnode; @@ -2798,13 +2840,25 @@ zfs_do_userspace(int argc, char **argv) if (argc < 2) usage(B_FALSE); - if (strcmp(argv[0], "groupspace") == 0) + if (strcmp(argv[0], "userspace") == 0) { + /* Toggle default user types */ + types = USTYPE_PSX_USR | USTYPE_SMB_USR; + } else if (strcmp(argv[0], "groupspace") == 0) { /* Toggle default group types */ types = USTYPE_PSX_GRP | USTYPE_SMB_GRP; + } else { + 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': @@ -2826,9 +2880,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 ':': @@ -2925,7 +2989,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; @@ -4230,6 +4295,10 @@ zfs_do_receive(int argc, char **argv) #define ZFS_DELEG_PERM_GROUPOBJQUOTA "groupobjquota" #define ZFS_DELEG_PERM_USEROBJUSED "userobjused" #define ZFS_DELEG_PERM_GROUPOBJUSED "groupobjused" +#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_DELEG_PERM_HOLD "hold" #define ZFS_DELEG_PERM_RELEASE "release" @@ -4265,6 +4334,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 } }; @@ -4346,6 +4419,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: @@ -4864,6 +4941,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 = ""; diff --git a/cmd/zhack/zhack.c b/cmd/zhack/zhack.c index e76945141afb..bc6121e57ea9 100644 --- a/cmd/zhack/zhack.c +++ b/cmd/zhack/zhack.c @@ -105,7 +105,7 @@ fatal(spa_t *spa, void *tag, const char *fmt, ...) /* ARGSUSED */ static int space_delta_cb(dmu_object_type_t bonustype, void *data, - uint64_t *userp, uint64_t *groupp) + uint64_t *userp, uint64_t *groupp, uint64_t *projectp) { /* * Is it a valid type of object to track? diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index 2a68fe66f8b2..c881b1831009 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -6995,6 +6995,7 @@ zpool_do_upgrade(int argc, char **argv) (void) printf(gettext(" 27 Improved snapshot creation " "performance\n")); (void) printf(gettext(" 28 Multiple vdev replacements\n")); + (void) printf(gettext(" 29 Project quota\n")); (void) printf(gettext("\nFor more information on a particular " "version, including supported releases,\n")); (void) printf(gettext("see the ZFS Administration Guide.\n\n")); diff --git a/configure.ac b/configure.ac index c52d72e3dd84..a8230f386fd2 100644 --- a/configure.ac +++ b/configure.ac @@ -262,6 +262,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 diff --git a/include/sys/dmu.h b/include/sys/dmu.h index d24615262737..ab992a8145c6 100644 --- a/include/sys/dmu.h +++ b/include/sys/dmu.h @@ -255,9 +255,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 @@ -909,7 +910,7 @@ extern int dmu_dir_list_next(objset_t *os, int namelen, char *name, uint64_t *idp, uint64_t *offp); typedef int objset_used_cb_t(dmu_object_type_t bonustype, - void *bonus, uint64_t *userp, uint64_t *groupp); + void *bonus, uint64_t *userp, uint64_t *groupp, uint64_t *projectp); extern void dmu_objset_register_type(dmu_objset_type_t ost, objset_used_cb_t *cb); extern void dmu_objset_set_user(objset_t *os, void *user_ptr); diff --git a/include/sys/dmu_objset.h b/include/sys/dmu_objset.h index a836e03722c3..068c77630ae9 100644 --- a/include/sys/dmu_objset.h +++ b/include/sys/dmu_objset.h @@ -49,24 +49,32 @@ struct dsl_pool; struct dsl_dataset; struct dmu_tx; -#define OBJSET_PHYS_SIZE 2048 -#define OBJSET_OLD_PHYS_SIZE 1024 +#define OBJSET_PHYS_SIZE_V3 4096 +#define OBJSET_PHYS_SIZE_V2 2048 +#define OBJSET_PHYS_SIZE_V1 1024 +#define OBJSET_PHYS_SIZE OBJSET_PHYS_SIZE_V3 #define OBJSET_BUF_HAS_USERUSED(buf) \ - (arc_buf_size(buf) > OBJSET_OLD_PHYS_SIZE) - -#define OBJSET_FLAG_USERACCOUNTING_COMPLETE (1ULL<<0) -#define OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE (1ULL<<1) + (arc_buf_size(buf) >= OBJSET_PHYS_SIZE_V2) +#define OBJSET_BUF_HAS_PROJECTUSED(buf) \ + (arc_buf_size(buf) >= OBJSET_PHYS_SIZE) typedef struct objset_phys { dnode_phys_t os_meta_dnode; zil_header_t os_zil_header; uint64_t os_type; uint64_t os_flags; - 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]; dnode_phys_t os_userused_dnode; dnode_phys_t os_groupused_dnode; + char os_pad1[OBJSET_PHYS_SIZE -OBJSET_PHYS_SIZE_V2 - + sizeof (dnode_phys_t)]; + /* + * With os_projectused_dnode_phy introduced, the total + * size of objset_phys_t becomes OBJSET_PHYS_SIZE_V3. + */ + dnode_phys_t os_projectused_dnode; } objset_phys_t; typedef int (*dmu_objset_upgrade_cb_t)(objset_t *); @@ -86,6 +94,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; @@ -132,7 +141,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 */ @@ -154,6 +163,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 || \ @@ -196,8 +206,11 @@ 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); +void dmu_objset_id_quota_upgrade(objset_t *os); boolean_t dmu_objset_userobjspace_present(objset_t *os); +boolean_t dmu_objset_projectquota_enabled(objset_t *os); +boolean_t dmu_objset_projectquota_present(objset_t *os); +boolean_t dmu_objset_projectquota_upgradable(objset_t *os); int dmu_fsname(const char *snapname, char *buf); diff --git a/include/sys/dnode.h b/include/sys/dnode.h index d32855dcdf85..6d7a8961944b 100644 --- a/include/sys/dnode.h +++ b/include/sys/dnode.h @@ -138,7 +138,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) typedef struct dnode_phys { @@ -286,8 +286,8 @@ struct dnode { /* used in syncing context */ uint64_t dn_oldused; /* old phys used bytes */ uint64_t dn_oldflags; /* old phys dn_flags */ - uint64_t dn_olduid, dn_oldgid; - uint64_t dn_newuid, dn_newgid; + uint64_t dn_olduid, dn_oldgid, dn_oldprojid; + uint64_t dn_newuid, dn_newgid, dn_newprojid; int dn_id_flags; /* holds prefetch structure */ diff --git a/include/sys/dsl_deleg.h b/include/sys/dsl_deleg.h index d399d1da973b..ee681e48a765 100644 --- a/include/sys/dsl_deleg.h +++ b/include/sys/dsl_deleg.h @@ -57,6 +57,10 @@ extern "C" { #define ZFS_DELEG_PERM_GROUPUSED "groupused" #define ZFS_DELEG_PERM_USEROBJUSED "userobjused" #define ZFS_DELEG_PERM_GROUPOBJUSED "groupobjused" +#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_DELEG_PERM_HOLD "hold" #define ZFS_DELEG_PERM_RELEASE "release" #define ZFS_DELEG_PERM_DIFF "diff" diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 7414eee5a260..2d6b8775f25d 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -181,6 +181,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; @@ -425,6 +429,7 @@ typedef enum { #define SPA_VERSION_26 26ULL #define SPA_VERSION_27 27ULL #define SPA_VERSION_28 28ULL +#define SPA_VERSION_29 29ULL #define SPA_VERSION_5000 5000ULL /* @@ -485,7 +490,8 @@ typedef enum { #define SPA_VERSION_DEADLISTS SPA_VERSION_26 #define SPA_VERSION_FAST_SNAP SPA_VERSION_27 #define SPA_VERSION_MULTI_REPLACE SPA_VERSION_28 -#define SPA_VERSION_BEFORE_FEATURES SPA_VERSION_28 +#define SPA_VERSION_PROJECT_QUOTA SPA_VERSION_29 +#define SPA_VERSION_BEFORE_FEATURES SPA_VERSION_29 #define SPA_VERSION_FEATURES SPA_VERSION_5000 #define SPA_VERSION_IS_SUPPORTED(v) \ diff --git a/include/sys/sa.h b/include/sys/sa.h index b7ed9fe38cbd..2ece7556989f 100644 --- a/include/sys/sa.h +++ b/include/sys/sa.h @@ -127,6 +127,7 @@ void sa_handle_destroy(sa_handle_t *); int sa_buf_hold(objset_t *, uint64_t, void *, dmu_buf_t **); void sa_buf_rele(dmu_buf_t *, void *); int sa_lookup(sa_handle_t *, sa_attr_type_t, void *buf, uint32_t buflen); +int sa_lookup_locked(sa_handle_t *, sa_attr_type_t, void *buf, uint32_t buflen); int sa_update(sa_handle_t *, sa_attr_type_t, void *buf, uint32_t buflen, dmu_tx_t *); int sa_remove(sa_handle_t *, sa_attr_type_t, dmu_tx_t *); diff --git a/include/sys/spa.h b/include/sys/spa.h index 22a396689b17..e159195d7caf 100644 --- a/include/sys/spa.h +++ b/include/sys/spa.h @@ -150,6 +150,12 @@ _NOTE(CONSTCOND) } while (0) #define SPA_COMPRESSBITS 7 +/* + * 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 (-1) + /* * All SPA data is represented by 128-bit data virtual addresses (DVAs). * The members of the dva_t should be considered opaque outside the SPA. diff --git a/include/sys/xvattr.h b/include/sys/xvattr.h index 4779b632163f..e13438432999 100644 --- a/include/sys/xvattr.h +++ b/include/sys/xvattr.h @@ -64,6 +64,8 @@ typedef struct xoptattr { uint64_t xoa_generation; uint8_t xoa_offline; uint8_t xoa_sparse; + uint8_t xoa_projinherit; + int64_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 parents 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 diff --git a/include/sys/zfs_acl.h b/include/sys/zfs_acl.h index 2572fee86306..2ae302c175a6 100644 --- a/include/sys/zfs_acl.h +++ b/include/sys/zfs_acl.h @@ -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 *, int64_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 *); diff --git a/include/sys/zfs_sa.h b/include/sys/zfs_sa.h index 06c4d589aa79..4e6d28638ef6 100644 --- a/include/sys/zfs_sa.h +++ b/include/sys/zfs_sa.h @@ -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]; diff --git a/include/sys/zfs_vfsops.h b/include/sys/zfs_vfsops.h index 2326da422183..2fd158252248 100644 --- a/include/sys/zfs_vfsops.h +++ b/include/sys/zfs_vfsops.h @@ -119,8 +119,10 @@ struct zfsvfs { kmutex_t z_lock; uint64_t z_userquota_obj; uint64_t z_groupquota_obj; + uint64_t z_projectquota_obj; uint64_t z_userobjquota_obj; uint64_t z_groupobjquota_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 void zfsvfs_free(zfsvfs_t *zfsvfs); diff --git a/include/sys/zfs_znode.h b/include/sys/zfs_znode.h index c292f03739e3..c3b654b056ab 100644 --- a/include/sys/zfs_znode.h +++ b/include/sys/zfs_znode.h @@ -65,6 +65,8 @@ extern "C" { #define ZFS_REPARSE 0x0000080000000000ull #define ZFS_OFFLINE 0x0000100000000000ull #define ZFS_SPARSE 0x0000200000000000ull +#define ZFS_PROJINHERIT 0x0000400000000000ull +#define ZFS_PROJID 0x0000800000000000ull #define ZFS_ATTR_SET(zp, attr, value, pflags, tx) \ { \ @@ -110,6 +112,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 +131,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 +199,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 */ + int64_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? */ diff --git a/include/sys/zpl.h b/include/sys/zpl.h index 1f97f2511a37..8973aca63d6e 100644 --- a/include/sys/zpl.h +++ b/include/sys/zpl.h @@ -32,12 +32,38 @@ #include #include #include +#include #include #include #include #include #include +#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 { + __u32 fsx_xflags; /* xflags field value (get/set) */ + __u32 fsx_extsize; /* extsize field value (get/set) */ + __u32 fsx_nextents; /* nextents field value (get) */ + __u32 fsx_projid; /* project identifier (get/set) */ + unsigned char fsx_pad[12]; +}; +typedef struct zfsxattr zfsxattr_t; + +#define ZFS_IOC_FSGETXATTR _IOR('X', 31, zfsxattr_t) +#define ZFS_IOC_FSSETXATTR _IOW('X', 32, zfsxattr_t) +#endif + /* zpl_inode.c */ extern void zpl_vap_init(vattr_t *vap, struct inode *dir, zpl_umode_t mode, cred_t *cr); diff --git a/include/zfeature_common.h b/include/zfeature_common.h index 25d680ffcebd..06f5a2964469 100644 --- a/include/zfeature_common.h +++ b/include/zfeature_common.h @@ -57,6 +57,7 @@ typedef enum spa_feature { SPA_FEATURE_SKEIN, SPA_FEATURE_EDONR, SPA_FEATURE_USEROBJ_ACCOUNTING, + SPA_FEATURE_PROJECT_QUOTA, SPA_FEATURES } spa_feature_t; diff --git a/include/zfs_deleg.h b/include/zfs_deleg.h index 95db9921f574..c655cd7b4cd0 100644 --- a/include/zfs_deleg.h +++ b/include/zfs_deleg.h @@ -67,6 +67,10 @@ typedef enum { ZFS_DELEG_NOTE_GROUPOBJQUOTA, ZFS_DELEG_NOTE_USEROBJUSED, ZFS_DELEG_NOTE_GROUPOBJUSED, + ZFS_DELEG_NOTE_PROJECTUSED, + ZFS_DELEG_NOTE_PROJECTQUOTA, + ZFS_DELEG_NOTE_PROJECTOBJUSED, + ZFS_DELEG_NOTE_PROJECTOBJQUOTA, ZFS_DELEG_NOTE_HOLD, ZFS_DELEG_NOTE_RELEASE, ZFS_DELEG_NOTE_DIFF, diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c index 51c168ad7902..5724252856d8 100644 --- a/lib/libzfs/libzfs_dataset.c +++ b/lib/libzfs/libzfs_dataset.c @@ -1049,7 +1049,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); @@ -1074,7 +1076,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 { @@ -2854,6 +2856,8 @@ idmap_id_to_numeric_domain_rid(uid_t id, boolean_t isuser, * 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, @@ -2863,12 +2867,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) @@ -2884,6 +2889,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; @@ -2895,7 +2903,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 @@ -2936,13 +2944,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; @@ -2957,6 +2965,9 @@ userquota_propname_decode(const char *propname, boolean_t zoned, return (ENOSYS); #endif /* HAVE_IDMAP */ } else { + if (isproject && id == ZFS_INVALID_PROJID) + return (EINVAL); + *ridp = id; } } @@ -3017,10 +3028,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); @@ -4491,7 +4504,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), diff --git a/man/man5/zpool-features.5 b/man/man5/zpool-features.5 index e70af7695f99..a968f4fa8339 100644 --- a/man/man5/zpool-features.5 +++ b/man/man5/zpool-features.5 @@ -617,5 +617,38 @@ files. .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 space and object usage +information against the project ID. + +The project ID is new object-based attribute. For the object upgarded from +old device, it has no project ID attribute, then it will be handled as the +case of zero project ID. For the new created object, if its parent has the +flag of inherit project (set via \fBchattr +/-P\fR), then it will inherit +parent's project ID when being created; otherwise, its project ID will be +set as zero. Anytime, you (the owner or privileged user) can set/change an +object's project ID attribute via \fBchattr -p $prjid\fR. + +This feature will becomes\fBactive\fR as soon as it is enabled and will never +return to being \fBenabled\fR. Each filesystem will be upgraded automatically +when remounted or when new file is created under that filesystem. The upgrade +can also be triggered on filesystems via `zfs set version=current `. +The upgrade process runs in the background and may take a while to complete +for the filesystems containing a large number of files. + +.RE + .SH "SEE ALSO" \fBzpool\fR(8) diff --git a/man/man8/zfs.8 b/man/man8/zfs.8 index 439c21ac4237..0d581cd82758 100644 --- a/man/man8/zfs.8 +++ b/man/man8/zfs.8 @@ -145,6 +145,13 @@ .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 mount .Nm .Cm mount @@ -844,6 +851,51 @@ 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 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 ) +when being created. The privileged user can set and change object's +project ID via +.Nm chattr Fl p +anytime. Space is charged to the project of each file, as displayed by +.Nm lsattr Fl p . +See the +.Sy userused Ns @ Ns Em user +property for more information. +.Pp +The root user, or an 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 an 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 @@ -1414,6 +1466,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 an 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 @@ -2741,6 +2814,27 @@ 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 mount .Xc Displays all ZFS file systems currently mounted. @@ -3469,6 +3563,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 diff --git a/module/zcommon/zfs_deleg.c b/module/zcommon/zfs_deleg.c index ce659d7f5cc8..81934f6f01ab 100644 --- a/module/zcommon/zfs_deleg.c +++ b/module/zcommon/zfs_deleg.c @@ -67,6 +67,10 @@ zfs_deleg_perm_tab_t zfs_deleg_perm_tab[] = { {ZFS_DELEG_PERM_GROUPOBJQUOTA}, {ZFS_DELEG_PERM_USEROBJUSED}, {ZFS_DELEG_PERM_GROUPOBJUSED}, + {ZFS_DELEG_PERM_PROJECTUSED}, + {ZFS_DELEG_PERM_PROJECTQUOTA}, + {ZFS_DELEG_PERM_PROJECTOBJUSED}, + {ZFS_DELEG_PERM_PROJECTOBJQUOTA}, {ZFS_DELEG_PERM_HOLD}, {ZFS_DELEG_PERM_RELEASE}, {NULL} diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c index 97736ee278bf..0093392c4b06 100644 --- a/module/zcommon/zfs_prop.c +++ b/module/zcommon/zfs_prop.c @@ -57,7 +57,11 @@ const char *zfs_userquota_prop_prefixes[] = { "userobjused@", "userobjquota@", "groupobjused@", - "groupobjquota@" + "groupobjquota@", + "projectused@", + "projectquota@", + "projectobjused@", + "projectobjquota@" }; zprop_desc_t * diff --git a/module/zfs/dbuf.c b/module/zfs/dbuf.c index f4e24e2099a2..cd7b4d166ba0 100644 --- a/module/zfs/dbuf.c +++ b/module/zfs/dbuf.c @@ -2193,7 +2193,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)) diff --git a/module/zfs/dmu.c b/module/zfs/dmu.c index 48e89eef4af3..945f9c24ed1b 100644 --- a/module/zfs/dmu.c +++ b/module/zfs/dmu.c @@ -112,8 +112,8 @@ const dmu_object_type_info_t dmu_ot[DMU_OT_NUMTYPES] = { { DMU_BSWAP_UINT64, TRUE, "FUID table size" }, { DMU_BSWAP_ZAP, TRUE, "DSL dataset next clones"}, { DMU_BSWAP_ZAP, TRUE, "scan work queue" }, - { DMU_BSWAP_ZAP, TRUE, "ZFS user/group used" }, - { DMU_BSWAP_ZAP, TRUE, "ZFS user/group quota" }, + { DMU_BSWAP_ZAP, TRUE, "ZFS user/group/project used"}, + { DMU_BSWAP_ZAP, TRUE, "ZFS user/group/project quota"}, { DMU_BSWAP_ZAP, TRUE, "snapshot refcount tags"}, { DMU_BSWAP_ZAP, TRUE, "DDT ZAP algorithm" }, { DMU_BSWAP_ZAP, TRUE, "DDT statistics" }, diff --git a/module/zfs/dmu_objset.c b/module/zfs/dmu_objset.c index 9a7a6968d631..ec397e5cc70b 100644 --- a/module/zfs/dmu_objset.c +++ b/module/zfs/dmu_objset.c @@ -57,6 +57,10 @@ #include #include +#define OBJSET_FLAG_USERACCOUNTING_COMPLETE (1ULL<<0) +#define OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE (1ULL<<1) +#define OBJSET_FLAG_PROJECTQUOTA_COMPLETE (1ULL<<2) + /* * Needed to close a window in dnode_move() that allows the objset to be freed * before it can be safely accessed. @@ -332,14 +336,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 >= OBJSET_PHYS_SIZE) + dnode_byteswap(&osp->os_projectused_dnode); } } @@ -380,10 +387,17 @@ dmu_objset_open_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, objset_t **osp) { objset_t *os; - int i, err; + int i, err, size; ASSERT(ds == NULL || MUTEX_HELD(&ds->ds_opening_lock)); + if (spa_version(spa) < SPA_VERSION_USERSPACE) + size = OBJSET_PHYS_SIZE_V1; + else if (spa_version(spa) < SPA_VERSION_PROJECT_QUOTA) + size = OBJSET_PHYS_SIZE_V2; + else + size = sizeof (objset_phys_t); + os = kmem_zalloc(sizeof (objset_t), KM_SLEEP); os->os_dsl_dataset = ds; os->os_spa = spa; @@ -410,11 +424,11 @@ dmu_objset_open_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, } /* 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 (size > OBJSET_PHYS_SIZE_V1 && + size > arc_buf_size(os->os_phys_buf)) { 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); @@ -424,8 +438,6 @@ dmu_objset_open_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, os->os_phys = os->os_phys_buf->b_data; 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; 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; @@ -553,11 +565,16 @@ 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); @@ -670,8 +687,9 @@ dmu_objset_own(const char *name, dmu_objset_type_t type, err = dmu_objset_own_impl(ds, type, readonly, tag, osp); dsl_pool_rele(dp, FTAG); - if (err == 0 && dmu_objset_userobjspace_upgradable(*osp)) - dmu_objset_userobjspace_upgrade(*osp); + if (err == 0 && (dmu_objset_userobjspace_upgradable(*osp) || + dmu_objset_projectquota_upgradable(*osp))) + dmu_objset_id_quota_upgrade(*osp); return (err); } @@ -775,6 +793,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)); } @@ -831,6 +851,8 @@ dmu_objset_evict_done(objset_t *os) if (DMU_USERUSED_DNODE(os)) { dnode_special_close(&os->os_userused_dnode); dnode_special_close(&os->os_groupused_dnode); + if (DMU_PROJECTUSED_DNODE(os)) + dnode_special_close(&os->os_projectused_dnode); } zil_free(os->os_zil); @@ -931,6 +953,12 @@ dmu_objset_create_impl(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; } @@ -1241,7 +1269,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). */ bp->blk_fill = 0; for (i = 0; i < dnp->dn_nblkptr; i++) @@ -1353,6 +1381,11 @@ dmu_objset_sync(objset_t *os, zio_t *pio, dmu_tx_t *tx) dnode_sync(DMU_USERUSED_DNODE(os), tx); DMU_GROUPUSED_DNODE(os)->dn_zio = zio; 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; @@ -1437,6 +1470,15 @@ 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 (spa_version(os->os_spa) >= SPA_VERSION_PROJECT_QUOTA && + 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]; @@ -1447,6 +1489,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 @@ -1499,6 +1542,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 @@ -1523,10 +1579,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]; @@ -1538,12 +1595,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]; @@ -1556,6 +1619,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); + } } } @@ -1583,6 +1653,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; @@ -1594,18 +1668,23 @@ userquota_updates_task(void *arg) flags = dn->dn_id_flags; ASSERT(flags); if (flags & DN_ID_OLD_EXIST) { - do_userquota_update(&cache, + do_userquota_update(os, &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); + 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); @@ -1614,6 +1693,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; @@ -1647,6 +1727,14 @@ dmu_objset_do_userquota_updates(objset_t *os, dmu_tx_t *tx) DMU_OT_USERGROUP_USED, DMU_OT_NONE, 0, tx)); } + /* Allocate the projectused objects if necessary. */ + 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 = @@ -1709,6 +1797,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; @@ -1754,9 +1843,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; } /* @@ -1764,7 +1855,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 @@ -1777,9 +1868,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 = 0; } error = 0; } @@ -1817,6 +1910,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) { @@ -1885,33 +1985,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)) + if (!dmu_objset_userobjused_enabled(os) && + !dmu_objset_projectquota_enabled(os)) return (SET_ERROR(ENOTSUP)); - dmu_objset_ds(os)->ds_feature_activation_needed[ - SPA_FEATURE_USEROBJ_ACCOUNTING] = B_TRUE; + if (dmu_objset_userobjused_enabled(os)) + 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_userobjused_enabled(os)) + 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 @@ -1923,6 +2033,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) @@ -2522,7 +2641,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_id_quota_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); #endif diff --git a/module/zfs/dmu_traverse.c b/module/zfs/dmu_traverse.c index c78228d74588..34b235bf97c2 100644 --- a/module/zfs/dmu_traverse.c +++ b/module/zfs/dmu_traverse.c @@ -367,7 +367,12 @@ 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, @@ -376,11 +381,15 @@ 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)) { + if (err == 0 && OBJSET_BUF_HAS_PROJECTUSED(buf)) { + err = traverse_dnode(td, &osp->os_projectused_dnode, + zb->zb_objset, DMU_PROJECTUSED_OBJECT); + } + if (err == 0 && OBJSET_BUF_HAS_USERUSED(buf)) { 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)) { + if (err == 0 && OBJSET_BUF_HAS_USERUSED(buf)) { err = traverse_dnode(td, &osp->os_userused_dnode, zb->zb_objset, DMU_USERUSED_OBJECT); } diff --git a/module/zfs/dnode.c b/module/zfs/dnode.c index 57156484b201..8fa526fcdaf6 100644 --- a/module/zfs/dnode.c +++ b/module/zfs/dnode.c @@ -136,8 +136,10 @@ dnode_cons(void *arg, void *unused, int kmflag) dn->dn_oldflags = 0; dn->dn_olduid = 0; dn->dn_oldgid = 0; + dn->dn_oldprojid = 0; dn->dn_newuid = 0; dn->dn_newgid = 0; + dn->dn_newprojid = 0; dn->dn_id_flags = 0; dn->dn_dbufs_count = 0; @@ -188,8 +190,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); @@ -510,8 +514,10 @@ dnode_destroy(dnode_t *dn) dn->dn_oldflags = 0; dn->dn_olduid = 0; dn->dn_oldgid = 0; + dn->dn_oldprojid = 0; dn->dn_newuid = 0; dn->dn_newgid = 0; + dn->dn_newprojid = 0; dn->dn_id_flags = 0; dmu_zfetch_fini(&dn->dn_zfetch); @@ -771,8 +777,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); @@ -831,8 +839,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 = 0; odn->dn_newuid = 0; odn->dn_newgid = 0; + odn->dn_newprojid = 0; odn->dn_id_flags = 0; /* @@ -1211,9 +1221,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; diff --git a/module/zfs/dsl_pool.c b/module/zfs/dsl_pool.c index 97eb0cced707..eafecc6bd242 100644 --- a/module/zfs/dsl_pool.c +++ b/module/zfs/dsl_pool.c @@ -523,7 +523,7 @@ dsl_pool_sync(dsl_pool_t *dp, uint64_t txg) /* * After the data blocks have been written (ensured by the zio_wait() - * above), update the user/group space accounting. This happens + * above), update the user/group/project space accounting. This happens * in tasks dispatched to dp_sync_taskq, so wait for them before * continuing. */ diff --git a/module/zfs/dsl_scan.c b/module/zfs/dsl_scan.c index 89faaeb8f03c..3ad0c7fbac23 100644 --- a/module/zfs/dsl_scan.c +++ b/module/zfs/dsl_scan.c @@ -743,11 +743,16 @@ 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 * pausing. This is necessary so that the space * deltas from this txg get integrated. */ + if (OBJSET_BUF_HAS_PROJECTUSED(buf)) { + dsl_scan_visitdnode(scn, ds, osp->os_type, + &osp->os_projectused_dnode, + DMU_PROJECTUSED_OBJECT, tx); + } dsl_scan_visitdnode(scn, ds, osp->os_type, &osp->os_groupused_dnode, DMU_GROUPUSED_OBJECT, tx); diff --git a/module/zfs/sa.c b/module/zfs/sa.c index 8046dbde28ad..437b10e00477 100644 --- a/module/zfs/sa.c +++ b/module/zfs/sa.c @@ -1475,6 +1475,24 @@ sa_lookup(sa_handle_t *hdl, sa_attr_type_t attr, void *buf, uint32_t buflen) return (error); } +int +sa_lookup_locked(sa_handle_t *hdl, sa_attr_type_t attr, void *buf, + uint32_t buflen) +{ + sa_bulk_attr_t bulk; + int err; + + VERIFY3U(buflen, <=, SA_ATTR_MAX_LEN); + + bulk.sa_attr = attr; + bulk.sa_data = buf; + bulk.sa_length = buflen; + bulk.sa_data_func = NULL; + + err = sa_lookup_impl(hdl, &bulk, 1); + return (err); +} + #ifdef _KERNEL int sa_lookup_uio(sa_handle_t *hdl, sa_attr_type_t attr, uio_t *uio) @@ -2037,6 +2055,7 @@ EXPORT_SYMBOL(sa_buf_hold); EXPORT_SYMBOL(sa_buf_rele); EXPORT_SYMBOL(sa_spill_rele); EXPORT_SYMBOL(sa_lookup); +EXPORT_SYMBOL(sa_lookup_locked); EXPORT_SYMBOL(sa_update); EXPORT_SYMBOL(sa_remove); EXPORT_SYMBOL(sa_bulk_lookup); diff --git a/module/zfs/spa.c b/module/zfs/spa.c index 031535321322..3f75d7a22b58 100644 --- a/module/zfs/spa.c +++ b/module/zfs/spa.c @@ -1166,7 +1166,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); diff --git a/module/zfs/zfeature.c b/module/zfs/zfeature.c index d8220aa235a8..812ae9c72d56 100644 --- a/module/zfs/zfeature.c +++ b/module/zfs/zfeature.c @@ -88,7 +88,7 @@ * * When feature flags are enabled spa_version() is set to SPA_VERSION_FEATURES * (5000). In order for this to work the pool is automatically upgraded to - * SPA_VERSION_BEFORE_FEATURES (28) first, so all pre-feature flags on disk + * SPA_VERSION_BEFORE_FEATURES (29) first, so all pre-feature flags on disk * format changes will be in use. * * Information about features is stored in 3 ZAP objects in the pool's MOS. diff --git a/module/zfs/zfeature_common.c b/module/zfs/zfeature_common.c index 73abcb236c55..86957a8cde25 100644 --- a/module/zfs/zfeature_common.c +++ b/module/zfs/zfeature_common.c @@ -318,4 +318,15 @@ zpool_feature_init(void) ZFEATURE_FLAG_READONLY_COMPAT | ZFEATURE_FLAG_PER_DATASET, userobj_accounting_deps); } + { + static const spa_feature_t project_quota_deps[] = { + SPA_FEATURE_EXTENSIBLE_DATASET, + SPA_FEATURE_NONE + }; + zfeature_register(SPA_FEATURE_PROJECT_QUOTA, + "org.zfsonlinux:project_quota", "project_quota", + "space/object accounting based on project ID.", + ZFEATURE_FLAG_READONLY_COMPAT | ZFEATURE_FLAG_PER_DATASET, + project_quota_deps); + } } diff --git a/module/zfs/zfs_acl.c b/module/zfs/zfs_acl.c index 0e7203ea6672..1653d62c8099 100644 --- a/module/zfs/zfs_acl.c +++ b/module/zfs/zfs_acl.c @@ -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, int64_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 != 0 && projid != ZFS_INVALID_PROJID && + zfs_id_overquota(zv, DMU_PROJECTUSED_OBJECT, projid))); } /* diff --git a/module/zfs/zfs_dir.c b/module/zfs/zfs_dir.c index 1fcc69fd12e6..e299af309ce3 100644 --- a/module/zfs/zfs_dir.c +++ b/module/zfs/zfs_dir.c @@ -1005,7 +1005,9 @@ 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_pflags & ZFS_PROJINHERIT ? zp->z_projid : + ZFS_INVALID_PROJID)) { zfs_acl_ids_free(&acl_ids); return (SET_ERROR(EDQUOT)); } diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index a2f7f045f1f6..b643e52e4e1b 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -254,10 +254,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, @@ -1195,10 +1199,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, @@ -2450,7 +2458,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; @@ -3775,6 +3783,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) { @@ -3788,8 +3800,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)); } @@ -5021,7 +5039,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 @@ -5149,7 +5167,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; @@ -5161,14 +5179,15 @@ zfs_ioc_userobjspace_upgrade(zfs_cmd_t *zc) dsl_dataset_long_hold(dmu_objset_ds(os), FTAG); dsl_pool_rele(dmu_objset_pool(os), FTAG); - 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); } diff --git a/module/zfs/zfs_log.c b/module/zfs/zfs_log.c index 8887f037aa34..9730e8950662 100644 --- a/module/zfs/zfs_log.c +++ b/module/zfs/zfs_log.c @@ -166,8 +166,17 @@ zfs_log_xvattr(lr_attr_t *lrattr, xvattr_t *xvap) XAT0_AV_MODIFIED; if (XVA_ISSET_REQ(xvap, XAT_CREATETIME)) ZFS_TIME_ENCODE(&xoap->xoa_createtime, crtime); - if (XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) + if (XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) { + ASSERT(!XVA_ISSET_REQ(xvap, XAT_PROJID)); + bcopy(xoap->xoa_av_scanstamp, scanstamp, AV_SCANSTAMP_SZ); + } else if (XVA_ISSET_REQ(xvap, XAT_PROJID)) { + /* + * XAT_PROJID and XAT_AV_SCANSTAMP will never be valid + * at the same time, so we can share the same space. + */ + bcopy(&xoap->xoa_projid, scanstamp, sizeof (int64_t)); + } if (XVA_ISSET_REQ(xvap, XAT_REPARSE)) *attrs |= (xoap->xoa_reparse == 0) ? 0 : XAT0_REPARSE; @@ -177,6 +186,9 @@ zfs_log_xvattr(lr_attr_t *lrattr, xvattr_t *xvap) if (XVA_ISSET_REQ(xvap, XAT_SPARSE)) *attrs |= (xoap->xoa_sparse == 0) ? 0 : XAT0_SPARSE; + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) + *attrs |= (xoap->xoa_projinherit == 0) ? 0 : + XAT0_PROJINHERIT; } static void * diff --git a/module/zfs/zfs_replay.c b/module/zfs/zfs_replay.c index 30efb4b57bc4..36e85c0c4fcb 100644 --- a/module/zfs/zfs_replay.c +++ b/module/zfs/zfs_replay.c @@ -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 (int64_t)); + } if (XVA_ISSET_REQ(xvap, XAT_REPARSE)) xoap->xoa_reparse = ((*attrs & XAT0_REPARSE) != 0); if (XVA_ISSET_REQ(xvap, XAT_OFFLINE)) xoap->xoa_offline = ((*attrs & XAT0_OFFLINE) != 0); if (XVA_ISSET_REQ(xvap, XAT_SPARSE)) xoap->xoa_sparse = ((*attrs & XAT0_SPARSE) != 0); + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) + xoap->xoa_projinherit = ((*attrs & XAT0_PROJINHERIT) != 0); } static int diff --git a/module/zfs/zfs_sa.c b/module/zfs/zfs_sa.c index 7d9970cb89bf..275d4a9cae94 100644 --- a/module/zfs/zfs_sa.c +++ b/module/zfs/zfs_sa.c @@ -27,6 +27,8 @@ #include #include #include +#include +#include /* * 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} }; @@ -273,11 +276,13 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) znode_t *zp = sa_get_userdata(hdl); zfsvfs_t *zfsvfs = ZTOZSB(zp); int count = 0; + int err; sa_bulk_attr_t *bulk, *sa_attrs; zfs_acl_locator_cb_t locate = { 0 }; uint64_t uid, gid, mode, rdev, xattr, parent, tmp_gen; uint64_t crtime[2], mtime[2], ctime[2], atime[2]; uint64_t links; + int64_t projid = 0; zfs_acl_phys_t znode_acl; char scanstamp[AV_SCANSTAMP_SZ]; boolean_t drop_lock = B_FALSE; @@ -308,7 +313,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); @@ -324,16 +329,25 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) &znode_acl, 88); if (sa_bulk_lookup_locked(hdl, bulk, count) != 0) { - kmem_free(bulk, sizeof (sa_bulk_attr_t) * 20); + kmem_free(bulk, sizeof (sa_bulk_attr_t) * ZPL_END); goto done; } + if (dmu_objset_projectquota_enabled(hdl->sa_os)) { + err = sa_lookup_locked(hdl, SA_ZPL_PROJID(zfsvfs), &projid, 8); + if (err != 0 && err != ENOENT) { + kmem_free(bulk, sizeof (sa_bulk_attr_t) * ZPL_END); + goto done; + } + zp->z_pflags |= ZFS_PROJID; + } + /* * While the order here doesn't matter its best to try and organize * 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); @@ -359,6 +373,11 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) 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); + + if (dmu_objset_projectquota_enabled(hdl->sa_os)) + SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_PROJID(zfsvfs), NULL, + &projid, 8); + SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_DACL_COUNT(zfsvfs), NULL, &zp->z_acl_cached->z_acl_count, 8); @@ -391,8 +410,8 @@ 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); + kmem_free(bulk, sizeof (sa_bulk_attr_t) * ZPL_END); done: if (drop_lock) mutex_exit(&zp->z_lock); diff --git a/module/zfs/zfs_vfsops.c b/module/zfs/zfs_vfsops.c index 6d4761607b20..5ac40a3a280a 100644 --- a/module/zfs/zfs_vfsops.c +++ b/module/zfs/zfs_vfsops.c @@ -536,8 +536,14 @@ zfs_register_callbacks(vfs_t *vfsp) 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,48 @@ 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 = 0; + 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 = 0; + 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 = 0; + + if (swap) { + *userp = BSWAP_64(*userp); + *groupp = BSWAP_64(*groupp); + *projectp = BSWAP_64(*projectp); } return (0); } @@ -624,6 +636,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 +647,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 +670,17 @@ 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); @@ -730,8 +759,17 @@ zfs_userspace_one(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, if (!dmu_objset_userspace_present(zfsvfs->z_os)) return (SET_ERROR(ENOTSUP)); + if ((type == ZFS_PROP_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)); @@ -739,8 +777,9 @@ zfs_userspace_one(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, if (obj == ZFS_NO_OBJECT) return (0); - if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED) { - strlcpy(buf, DMU_OBJACCT_PREFIX, DMU_OBJACCT_PREFIX_LEN); + 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 +819,18 @@ 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)); + + objp = &zfsvfs->z_projectquota_obj; + break; + case ZFS_PROP_PROJECTOBJQUOTA: + if (!dmu_objset_projectquota_enabled(zfsvfs->z_os)) + return (SET_ERROR(ENOTSUP)); + + objp = &zfsvfs->z_projectobjquota_obj; + break; default: return (SET_ERROR(EINVAL)); } @@ -827,30 +878,42 @@ 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)) - dmu_objset_userobjspace_upgrade(zfsvfs->z_os); + if (dmu_objset_userobjspace_upgradable(zfsvfs->z_os) || + dmu_objset_projectquota_upgradable(zfsvfs->z_os)) + dmu_objset_id_quota_upgrade(zfsvfs->z_os); 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)) + dmu_objset_id_quota_upgrade(zfsvfs->z_os); + return (B_FALSE); + } + quotaobj = zfsvfs->z_projectobjquota_obj; + } else if (usedobj == DMU_USERUSED_OBJECT) { + quotaobj = zfsvfs->z_userobjquota_obj; + } else if (usedobj == DMU_GROUPUSED_OBJECT) { + quotaobj = zfsvfs->z_groupobjquota_obj; + } else { + return (B_FALSE); + } if (quotaobj == 0 || zfsvfs->z_replay) return (B_FALSE); - (void) sprintf(buf, "%llx", (longlong_t)fuid); + (void) sprintf(buf, "%llx", (longlong_t)id); err = zap_lookup(zfsvfs->z_os, quotaobj, buf, 8, 1, "a); if (err != 0) return (B_FALSE); - (void) sprintf(buf, DMU_OBJACCT_PREFIX "%llx", (longlong_t)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); @@ -858,19 +921,30 @@ 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)) + dmu_objset_id_quota_upgrade(zfsvfs->z_os); + 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, "a); if (err != 0) return (B_FALSE); @@ -882,20 +956,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)); } /* @@ -1002,6 +1066,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); @@ -1018,6 +1090,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) @@ -2137,9 +2217,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); diff --git a/module/zfs/zfs_vnops.c b/module/zfs/zfs_vnops.c index c065c0c59b18..28fe1bcd37e1 100644 --- a/module/zfs/zfs_vnops.c +++ b/module/zfs/zfs_vnops.c @@ -78,6 +78,7 @@ #include #include #include +#include /* * Programming rules. @@ -725,8 +726,12 @@ 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 != 0 && + zfs_id_overblockquota(zfsvfs, DMU_PROJECTUSED_OBJECT, + zp->z_projid))) { if (abuf != NULL) dmu_return_arcbuf(abuf); error = SET_ERROR(EDQUOT); @@ -1409,7 +1414,9 @@ zfs_create(struct inode *dip, char *name, vattr_t *vap, int excl, goto out; have_acl = B_TRUE; - if (zfs_acl_ids_overquota(zfsvfs, &acl_ids)) { + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, + dzp->z_pflags & ZFS_PROJINHERIT ? dzp->z_projid : + ZFS_INVALID_PROJID)) { zfs_acl_ids_free(&acl_ids); error = SET_ERROR(EDQUOT); goto out; @@ -1585,7 +1592,9 @@ zfs_tmpfile(struct inode *dip, vattr_t *vap, int excl, goto out; have_acl = B_TRUE; - if (zfs_acl_ids_overquota(zfsvfs, &acl_ids)) { + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, + dzp->z_pflags & ZFS_PROJINHERIT ? dzp->z_projid : + ZFS_INVALID_PROJID)) { zfs_acl_ids_free(&acl_ids); error = SET_ERROR(EDQUOT); goto out; @@ -1996,7 +2005,9 @@ zfs_mkdir(struct inode *dip, char *dirname, vattr_t *vap, struct inode **ipp, return (error); } - if (zfs_acl_ids_overquota(zfsvfs, &acl_ids)) { + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, + dzp->z_pflags & ZFS_PROJINHERIT ? dzp->z_projid : + ZFS_INVALID_PROJID)) { zfs_acl_ids_free(&acl_ids); zfs_dirent_unlock(dl); ZFS_EXIT(zfsvfs); @@ -2568,6 +2579,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 +2690,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; @@ -2679,6 +2702,7 @@ 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]; + int64_t projid = ZFS_INVALID_PROJID; znode_t *attrzp; int need_policy = FALSE; int err, err2; @@ -2690,8 +2714,25 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) boolean_t fuid_dirtied = B_FALSE; sa_bulk_attr_t *bulk, *xattr_bulk; int count = 0, xattr_count = 0; + int bulk_cnt = 8; - if (mask == 0) + /* + * 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); + if (xoap != NULL && (mask & ATTR_XVATTR)) { + if (XVA_ISSET_REQ(xvap, XAT_PROJID)) + projid = xoap->xoa_projid; + + if ((XVA_ISSET_REQ(xvap, XAT_PROJINHERIT) || + (projid != ZFS_INVALID_PROJID && projid != zp->z_projid)) && + !dmu_objset_projectquota_enabled(os)) + return (SET_ERROR(ENOTSUP)); + } + + if (mask == 0 || + (projid != ZFS_INVALID_PROJID && projid == zp->z_projid)) return (0); ZFS_ENTER(zfsvfs); @@ -2722,17 +2763,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) * bulk_cnt, KM_SLEEP); + xattr_bulk = kmem_alloc(sizeof (sa_bulk_attr_t) * bulk_cnt, KM_SLEEP); /* * Immutable files can only alter immutable bit and atime @@ -2878,6 +2913,22 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) } } + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) { + if (xoap->xoa_projinherit && !S_ISDIR(ip->i_mode)) { + mutex_exit(&zp->z_lock); + err = ENOTDIR; + goto out3; + } + + 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)) { @@ -2986,7 +3037,7 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) */ mask = vap->va_mask; - if ((mask & (ATTR_UID | ATTR_GID))) { + if ((mask & (ATTR_UID | ATTR_GID)) || projid != ZFS_INVALID_PROJID) { err = sa_lookup(zp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs), &xattr_obj, sizeof (xattr_obj)); @@ -2999,7 +3050,8 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) 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 = EDQUOT; @@ -3011,15 +3063,24 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) 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 = 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; @@ -3069,6 +3130,11 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) zfs_sa_upgrade_txholds(tx, zp); + /* set project ID may cause new layout */ + if (projid != ZFS_INVALID_PROJID && projid != zp->z_projid) + dmu_tx_hold_zap(tx, os->os_sa->sa_layout_attr_obj, + B_TRUE, SA_LAYOUTS); + err = dmu_tx_assign(tx, TXG_WAIT); if (err) goto out; @@ -3087,6 +3153,12 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) mutex_enter(&zp->z_acl_lock); mutex_enter(&zp->z_lock); + if (projid != ZFS_INVALID_PROJID && projid != zp->z_projid) { + zp->z_pflags |= ZFS_PROJID; + if (attrzp) + attrzp->z_pflags |= ZFS_PROJID; + } + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_FLAGS(zfsvfs), NULL, &zp->z_pflags, sizeof (zp->z_pflags)); @@ -3097,6 +3169,13 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) 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 && + projid != attrzp->z_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)) { @@ -3176,6 +3255,13 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) ctime, sizeof (ctime)); } + if (projid != ZFS_INVALID_PROJID && projid != zp->z_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, @@ -3212,6 +3298,9 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) 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)); @@ -3264,12 +3353,12 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) } 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) * bulk_cnt); + kmem_free(bulk, sizeof (sa_bulk_attr_t) * bulk_cnt); kmem_free(tmpxvattr, sizeof (xvattr_t)); ZFS_EXIT(zfsvfs); return (err); @@ -3562,6 +3651,17 @@ zfs_rename(struct inode *sdip, char *snm, struct inode *tdip, char *tnm, return (terr); } + /* + * If we are using project inheritance, we only allow renames + * into our tree when the project IDs are the same; otherwise + * tree quota mechanism would be circumvented. + */ + 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. @@ -3660,6 +3760,10 @@ zfs_rename(struct inode *sdip, char *snm, struct inode *tdip, char *tnm, error = zfs_link_create(tdl, szp, tx, ZRENAMING); if (error == 0) { szp->z_pflags |= ZFS_AV_MODIFIED; + if (tdzp->z_pflags & ZFS_PROJINHERIT && + S_ISDIR(ZTOI(szp)->i_mode) && + !(szp->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); @@ -3799,7 +3903,9 @@ zfs_symlink(struct inode *dip, char *name, vattr_t *vap, char *link, return (error); } - if (zfs_acl_ids_overquota(zfsvfs, &acl_ids)) { + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, + dzp->z_pflags & ZFS_PROJINHERIT ? dzp->z_projid : + ZFS_INVALID_PROJID)) { zfs_acl_ids_free(&acl_ids); zfs_dirent_unlock(dl); ZFS_EXIT(zfsvfs); @@ -3974,6 +4080,16 @@ 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, we only allow hard link + * creation in our tree when the project IDs are the same; + * otherwise the tree quota mechanism could be circumvented. + */ + 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. @@ -4168,8 +4284,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 != 0 && + zfs_id_overblockquota(zfsvfs, DMU_PROJECTUSED_OBJECT, + zp->z_projid))) { err = EDQUOT; } #endif diff --git a/module/zfs/zfs_znode.c b/module/zfs/zfs_znode.c index 1ec5618e08ab..a0ae8a8bff70 100644 --- a/module/zfs/zfs_znode.c +++ b/module/zfs/zfs_znode.c @@ -558,8 +558,10 @@ 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]; + int64_t projid = 0; sa_bulk_attr_t bulk[11]; int count = 0; + int err; ASSERT(zfsvfs != NULL); @@ -611,6 +613,18 @@ zfs_znode_alloc(zfsvfs_t *zfsvfs, dmu_buf_t *db, int blksz, goto error; } + 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) { + 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; @@ -695,7 +709,7 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, uint_t flag, znode_t **zpp, zfs_acl_ids_t *acl_ids) { uint64_t crtime[2], atime[2], mtime[2], ctime[2]; - uint64_t mode, size, links, parent, pflags; + uint64_t mode, size, links, parent, pflags, projid; uint64_t dzp_pflags = 0; uint64_t rdev = 0; zfsvfs_t *zfsvfs = ZTOZSB(dzp); @@ -803,6 +817,27 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, if (flag & IS_XATTR) pflags |= ZFS_XATTR; + /* + * With ZFS_PROJID flag, we can easily to know whether there is + * project ID stored on disk or not. See zfs_space_delta_cb(). + */ + if (dmu_objset_projectquota_enabled(zfsvfs->z_os)) + pflags |= ZFS_PROJID; + + /* + * Inherit project ID from parent directory. + */ + if (dzp->z_pflags & ZFS_PROJINHERIT) + projid = dzp->z_projid; + else + projid = 0; + + /* + * Inherit ZFS_PROJINHERIT flag from parent directory. + */ + if (dzp->z_pflags & ZFS_PROJINHERIT && S_ISDIR(mode)) + pflags |= ZFS_PROJINHERIT; + /* * No execs denied will be deterimed when zfs_mode_compute() is called. */ @@ -901,15 +936,21 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, sizeof (uint64_t) * 4); SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_ZNODE_ACL(zfsvfs), NULL, &acl_phys, sizeof (zfs_acl_phys_t)); - } else if (acl_ids->z_aclp->z_version >= ZFS_ACL_VERSION_FUID) { - SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_DACL_COUNT(zfsvfs), NULL, - &acl_ids->z_aclp->z_acl_count, 8); - locate.cb_aclp = acl_ids->z_aclp; - SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_DACL_ACES(zfsvfs), - zfs_acl_data_locator, &locate, - acl_ids->z_aclp->z_acl_bytes); - mode = zfs_mode_compute(mode, acl_ids->z_aclp, &pflags, - acl_ids->z_fuid, acl_ids->z_fgid); + } else { + if (dmu_objset_projectquota_enabled(zfsvfs->z_os)) + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_PROJID(zfsvfs), + NULL, &projid, 8); + if (acl_ids->z_aclp->z_version >= ZFS_ACL_VERSION_FUID) { + SA_ADD_BULK_ATTR(sa_attrs, cnt, + SA_ZPL_DACL_COUNT(zfsvfs), NULL, + &acl_ids->z_aclp->z_acl_count, 8); + locate.cb_aclp = acl_ids->z_aclp; + SA_ADD_BULK_ATTR(sa_attrs, cnt, + SA_ZPL_DACL_ACES(zfsvfs), zfs_acl_data_locator, + &locate, acl_ids->z_aclp->z_acl_bytes); + mode = zfs_mode_compute(mode, acl_ids->z_aclp, &pflags, + acl_ids->z_fuid, acl_ids->z_fgid); + } } VERIFY(sa_replace_all_by_template(sa_hdl, sa_attrs, cnt, tx) == 0); @@ -1049,6 +1090,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 +1212,7 @@ zfs_rezget(znode_t *zp) uint64_t gen; uint64_t z_uid, z_gid; uint64_t atime[2], mtime[2], ctime[2]; + int64_t projid = 0; znode_hold_t *zh; /* @@ -1241,6 +1288,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); diff --git a/module/zfs/zpl_file.c b/module/zfs/zpl_file.c index 4805abe695f2..869e32a04a47 100644 --- a/module/zfs/zpl_file.c +++ b/module/zfs/zpl_file.c @@ -720,6 +720,9 @@ zpl_fallocate(struct file *filp, int mode, loff_t offset, loff_t len) } #endif /* HAVE_FILE_FALLOCATE */ +#define ZFS_FL_USER_VISIBLE (FS_FL_USER_VISIBLE | ZFS_PROJINHERIT_FL) +#define ZFS_FL_USER_MODIFIABLE (FS_FL_USER_MODIFIABLE | ZFS_PROJINHERIT_FL) + /* * Map zfs file z_pflags (xvattr_t) to linux file attributes. Only file * attributes common to both Linux and Solaris are mapped. @@ -741,7 +744,10 @@ 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; + + ioctl_flags &= ZFS_FL_USER_VISIBLE; error = copy_to_user(arg, &ioctl_flags, sizeof (ioctl_flags)); @@ -774,10 +780,11 @@ zpl_ioctl_setflags(struct file *filp, void __user *arg) if (copy_from_user(&ioctl_flags, arg, sizeof (ioctl_flags))) return (-EFAULT); - 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) || @@ -803,6 +810,10 @@ zpl_ioctl_setflags(struct file *filp, void __user *arg) 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; + crhold(cr); cookie = spl_fstrans_mark(); error = -zfs_setattr(ip, (vattr_t *)&xva, 0, cr); @@ -812,6 +823,49 @@ zpl_ioctl_setflags(struct file *filp, void __user *arg) return (error); } +static int +zpl_ioctl_getprojid(struct file *filp, void __user *arg) +{ + struct inode *ip = file_inode(filp); + zfsxattr_t fsx = { 0 }; + int err; + + fsx.fsx_projid = ITOZ(ip)->z_projid; + err = copy_to_user(arg, &fsx, sizeof (fsx)); + return (err); +} + +static int +zpl_ioctl_setprojid(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_inode_owner_or_capable(ip)) + return (-EACCES); + + xva_init(&xva); + 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 zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { @@ -820,6 +874,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_getprojid(filp, (void *)arg)); + case ZFS_IOC_FSSETXATTR: + return (zpl_ioctl_setprojid(filp, (void *)arg)); default: return (-ENOTTY); } diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index e10ec4dc2c80..eee8c9c72356 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -533,7 +533,7 @@ tests = ['tmpfile_001_pos', 'tmpfile_002_pos', 'tmpfile_003_pos'] tests = ['truncate_001_pos', 'truncate_002_pos'] [tests/functional/upgrade] -tests = [ 'upgrade_userobj_001_pos' ] +tests = [ 'upgrade_userobj_001_pos', 'upgrade_projectquota_001_pos' ] [tests/functional/userquota] tests = [ @@ -545,6 +545,13 @@ tests = [ 'userspace_001_pos', 'userspace_002_pos', 'userspace_003_pos', 'groupspace_001_pos', 'groupspace_002_pos', 'groupspace_003_pos' ] +[tests/functional/projectquota] +tests = [ 'projectid_001_pos', 'projectid_002_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' ] + # vdev_zaps_007_pos -- fails due to a pre-existing issue with zpool split [tests/functional/vdev_zaps] tests = ['vdev_zaps_001_pos', 'vdev_zaps_002_pos', 'vdev_zaps_003_pos', diff --git a/tests/zfs-tests/include/commands.cfg b/tests/zfs-tests/include/commands.cfg index 968ab3cd1a5b..ae880d5c6a74 100644 --- a/tests/zfs-tests/include/commands.cfg +++ b/tests/zfs-tests/include/commands.cfg @@ -61,6 +61,7 @@ export SYSTEM_FILES='arp logname losetup ls + lsattr lsblk lsmod lsscsi diff --git a/tests/zfs-tests/tests/functional/Makefile.am b/tests/zfs-tests/tests/functional/Makefile.am index 1c3f4b1a877b..e175f07f71d8 100644 --- a/tests/zfs-tests/tests/functional/Makefile.am +++ b/tests/zfs-tests/tests/functional/Makefile.am @@ -38,6 +38,7 @@ SUBDIRS = \ pool_names \ poolversion \ privilege \ + projectquota \ quota \ raidz \ redundancy \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg b/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg index 0ffd6f510818..d9079f61750d 100644 --- a/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg +++ b/tests/zfs-tests/tests/functional/cli_root/zpool_get/zpool_get.cfg @@ -77,5 +77,6 @@ if is_linux; then "ashift" "feature@large_dnode" "feature@userobj_accounting" + "feature@project_quota" ) fi diff --git a/tests/zfs-tests/tests/functional/projectquota/Makefile.am b/tests/zfs-tests/tests/functional/projectquota/Makefile.am new file mode 100644 index 000000000000..3aac924d57ec --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/Makefile.am @@ -0,0 +1,20 @@ +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 \ + 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 diff --git a/tests/zfs-tests/tests/functional/projectquota/cleanup.ksh b/tests/zfs-tests/tests/functional/projectquota/cleanup.ksh new file mode 100755 index 000000000000..d61e5c5a31d9 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/cleanup.ksh @@ -0,0 +1,38 @@ +#!/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/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +log_must cleanup_projectquota +log_must clean_user_group +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 index 000000000000..39912418fe05 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectid_001_pos.ksh @@ -0,0 +1,80 @@ +#!/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/include/libtest.shlib +. $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. Project inherit flag only can be set on directory. +# 4. New created subdir or regular file should inherit its parent's +# project ID if its parent has project inherit flag. +# 5. New created subdir should inherit its parent project's inherit flag. +# + +function cleanup +{ + log_must rm -f $PRJFILE + log_must rm -rf $PRJDIR +} + +log_onexit cleanup + +log_assert "Check project ID/flags can be set/inherited properly" + +log_must touch $PRJFILE +log_must mkdir $PRJDIR + +log_must chattr -p $PRJID1 $PRJFILE +log_must lsattr -p $PRJFILE | grep "$PRJID1 ----------------" +log_must chattr -p $PRJID1 $PRJDIR +log_must lsattr -pd $PRJDIR | grep "$PRJID1 ----------------" + +log_must chattr +P $PRJDIR +log_must lsattr -pd $PRJDIR | grep "$PRJID1 ----------------P" +log_mustnot chattr +P $PRJFILE + +log_must mkdir $PRJDIR/dchild +log_must lsattr -pd $PRJDIR/dchild | grep "$PRJID1 ----------------P" +log_must touch $PRJDIR/fchild +log_must lsattr -p $PRJDIR/fchild | grep "$PRJID1 ----------------" + +log_must touch $PRJDIR/dchild/foo +log_must lsattr -p $PRJDIR/dchild/foo | grep "$PRJID1 ----------------" + +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 index 000000000000..a1bd5c1f3d5c --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectid_002_pos.ksh @@ -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. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# +# DESCRIPTION: +# Project ID affects POSIX behavior +# +# +# STRATEGY: +# 1. Create three directories +# 2. Set tdir1 and tdir3 project ID as PRJID1, +# set tdir2 project ID as PRJID2. +# 3. Create regular file under tdir1. It inherits tdir1 proejct ID. +# 4. Hardlink from tdir1's child to tdir2 should be denied, +# move tdir1's child to tdir2 will be object recreated. +# 5. Hardlink from tdir1's child to tdir3 should succeed. +# + +function cleanup +{ + log_must rm -rf $PRJDIR1 + log_must rm -rf $PRJDIR2 + log_must rm -rf $PRJDIR3 +} + +log_onexit cleanup + +log_assert "Project ID affects POSIX behavior" + +log_must mkdir $PRJDIR1 +log_must mkdir $PRJDIR2 +log_must mkdir $PRJDIR3 + +log_must chattr +P -p $PRJID1 $PRJDIR1 +log_must chattr +P -p $PRJID2 $PRJDIR2 +log_must chattr +P -p $PRJID1 $PRJDIR3 + +log_must touch $PRJDIR1/tfile1 +log_must touch $PRJDIR1/tfile2 +log_must lsattr -p $PRJDIR1/tfile1 | grep "$PRJID1 ----------------" + +log_mustnot ln $PRJDIR1/tfile1 $PRJDIR2/tfile2 + +log_must mv $PRJDIR1/tfile1 $PRJDIR2/tfile2 +log_must lsattr -p $PRJDIR2/tfile2 | grep "$PRJID2 ----------------" + +log_must ln $PRJDIR1/tfile2 $PRJDIR3/tfile3 + +log_pass "Project ID affects POSIX behavior" diff --git a/tests/zfs-tests/tests/functional/projectquota/projectquota.cfg b/tests/zfs-tests/tests/functional/projectquota/projectquota.cfg new file mode 100644 index 000000000000..f815b94001d0 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota.cfg @@ -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. +# + +export PRJID1=1001 +export PRJID2=1002 + +export PRJFILE=$TESTDIR/tfile +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 index 000000000000..3c1d315023fc --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_001_pos.ksh @@ -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. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib +. $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 +# + +function cleanup +{ + cleanup_projectquota +} + +log_onexit cleanup + +log_assert "If write operation overwrite project{obj}quota size, it will fail" + +mkmount_writable $QFS + +log_note "Check the projectquota@$PRJID1" +log_must user_run $QUSER1 mkdir $PRJDIR +log_must chattr +P -p $PRJID1 $PRJDIR + +log_must zfs set projectquota@$PRJID1=$PQUOTA_LIMIT $QFS +log_must user_run $QUSER1 mkfile $PQUOTA_LIMIT $PRJDIR/qf +sync_pool +log_mustnot user_run $QUSER1 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 $QUSER1 mkdir $PRJDIR +log_must chattr +P -p $PRJID2 $PRJDIR + +log_must zfs set projectobjquota@$PRJID2=$PQUOTA_OBJLIMIT $QFS +log_must user_run $QUSER1 mkfiles $PRJDIR/qf_ $((PQUOTA_OBJLIMIT - 1)) +sync_pool +log_mustnot user_run $QUSER1 mkfile 1 $PRJDIR/of + +log_pass "Write operation overwrite project{obj}quota size, it 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 index 000000000000..162038aae518 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_002_pos.ksh @@ -0,0 +1,87 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $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 index 000000000000..a5ce0c64c199 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_003_pos.ksh @@ -0,0 +1,96 @@ +#!/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/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the basic function project{obj}used +# +# +# STRATEGY: +# 1. Write data to fs with some project then check the project{obj}used +# + +function cleanup +{ + cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check the basic function of project{obj}used" + +sync_pool $TESTPOOL true +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 $QUSER1 mkdir $PRJDIR +log_must chattr +P -p $PRJID1 $PRJDIR +log_must user_run $QUSER1 mkfile $file_size $PRJDIR/qf +sync_pool $TESTPOOL true +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 $QUSER1 mkdir $PRJDIR +log_must chattr +P -p $PRJID2 $PRJDIR +# $PRJDIR has already used one object with the $PRJID2 +log_must user_run $QUSER1 mkfiles $PRJDIR/qf_ $((file_count - 1)) +sync_pool $TESTPOOL true +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 index 000000000000..dc67b65f8be8 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_004_neg.ksh @@ -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/include/libtest.shlib +. $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 index 000000000000..8f1b0e8a48fd --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_005_pos.ksh @@ -0,0 +1,69 @@ +#!/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/include/libtest.shlib +. $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 index 000000000000..0ba460f87dda --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_006_pos.ksh @@ -0,0 +1,73 @@ +#!/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/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Projectquota can be set beyond the fs quota. +# Pprojectquota can be set at a smaller size than its current usage. +# +# STRATEGY: +# 1. set quota to a fs and set a larger size of projectquota +# 2. write some data to the fs and set a smaller projectquota +# + +function cleanup +{ + log_must cleanup_projectquota + log_must zfs set quota=none $QFS +} + +log_onexit cleanup + +log_assert "Check set projectquota to larger than the quota size of a fs" + +log_must zfs set quota=200m $QFS +log_must zfs set projectquota@$PRJID1=500m $QFS + +log_must zfs get projectquota@$PRJID1 $QFS + +log_note "write some data to the $QFS" +mkmount_writable $QFS +log_must user_run $QUSER1 mkdir $PRJDIR +log_must chattr +P -p $PRJID1 $PRJDIR +log_must user_run $QUSER1 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 index 000000000000..f9a6f300bd50 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_007_pos.ksh @@ -0,0 +1,59 @@ +#!/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/include/libtest.shlib +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# zfs get all does not print out project{obj}quota +# +# STRATEGY: +# 1. set project{obj}quota to a fs +# 2. check zfs get all fs +# + +function cleanup +{ + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check zfs get all will not print out project{obj}quota" + +log_must zfs set projectquota@$PRJID1=50m $QFS +log_must zfs set projectobjquota@$PRJID2=100 $QFS + +log_mustnot zfs get all $QFS | grep projectquota +log_mustnot 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 index 000000000000..c8461b0a01f2 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_008_pos.ksh @@ -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. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib +. $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 index 000000000000..c218d0d24d25 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_009_pos.ksh @@ -0,0 +1,133 @@ +#!/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/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib +. $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 index 000000000000..c8a3407ceccd --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib @@ -0,0 +1,57 @@ +# +# 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/userquota/userquota.cfg +. $STF_SUITE/tests/functional/projectquota/projectquota.cfg +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib + +# +# reset the projectquota and delete temporary files +# +function cleanup_projectquota +{ + if datasetexists $QFS; then + 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 + recovery_writable $QFS + 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 +} 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 index 000000000000..6ac545ca8836 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectspace_001_pos.ksh @@ -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/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the zfs projectspace with all 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 +# + +function cleanup +{ + if datasetexists $snap_fs; then + log_must zfs destroy $snap_fs + fi + + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check the zfs projectspace with all possible parameters" + +set -A 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 $QUSER1 mkdir $PRJDIR +log_must chattr +P -p $PRJID1 $PRJDIR +log_must user_run $QUSER1 mkfile 50m $PRJDIR/qf +sync + +log_must zfs snapshot $snap_fs + +for param in "${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_pass "zfs projectspace with all possible parameters pass as expect" 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 index 000000000000..2640bacb09b6 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectspace_002_pos.ksh @@ -0,0 +1,83 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the project used size and quota in zfs projectspace +# +# +# STRATEGY: +# 1. set zfs projectquota to a fs +# 2. write some data to the fs with specified project and size +# 3. use zfs projectspace to check the used size and quota size +# + +function cleanup +{ + if datasetexists $snapfs; then + log_must zfs destroy $snapfs + fi + + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check the zfs projectspace used and quota" + +log_must zfs set projectquota@$PRJID1=100m $QFS + +mkmount_writable $QFS +log_must user_run $QUSER1 mkdir $PRJDIR +log_must chattr +P -p $PRJID1 $PRJDIR +log_must user_run $QUSER1 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 index 000000000000..8fa78b53930f --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectspace_003_pos.ksh @@ -0,0 +1,126 @@ +#!/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/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib +. $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 +} + +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 +} + +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 $QUSER1 mkdir $PRJDIR1 +log_must user_run $QUSER1 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 $QUSER1 mkfiles $PRJDIR1/qf $((prj_cnt1 - 1)) +log_must user_run $QUSER1 mkfiles $PRJDIR2/qf $((prj_cnt2 - 1)) +sync_pool $TESTPOOL true + +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 $TESTPOOL true + +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 $TESTPOOL true + +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/setup.ksh b/tests/zfs-tests/tests/functional/projectquota/setup.ksh new file mode 100755 index 000000000000..0fc429bea9df --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/setup.ksh @@ -0,0 +1,56 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/userquota/userquota_common.kshlib +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +verify_runnable "both" + +log_must clean_user_group + +log_must add_group $QGROUP +log_must add_user $QGROUP $QUSER1 + +# +# 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 $QUSER1 zfs list +if [ $? -ne 0 ]; then + log_unsupported "Test user $QUSER1 cannot execute zfs utilities" +fi + +DISK=${DISKS%% *} +default_setup $DISK diff --git a/tests/zfs-tests/tests/functional/upgrade/Makefile.am b/tests/zfs-tests/tests/functional/upgrade/Makefile.am index 31034342f30c..ee1b928465f0 100644 --- a/tests/zfs-tests/tests/functional/upgrade/Makefile.am +++ b/tests/zfs-tests/tests/functional/upgrade/Makefile.am @@ -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 diff --git a/tests/zfs-tests/tests/functional/upgrade/cleanup.ksh b/tests/zfs-tests/tests/functional/upgrade/cleanup.ksh index 19f4de24a9be..1f0c9b63d9f1 100755 --- a/tests/zfs-tests/tests/functional/upgrade/cleanup.ksh +++ b/tests/zfs-tests/tests/functional/upgrade/cleanup.ksh @@ -33,12 +33,10 @@ # 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 diff --git a/tests/zfs-tests/tests/functional/upgrade/setup.ksh b/tests/zfs-tests/tests/functional/upgrade/setup.ksh index c3b89b3047df..c25d25df6b9e 100755 --- a/tests/zfs-tests/tests/functional/upgrade/setup.ksh +++ b/tests/zfs-tests/tests/functional/upgrade/setup.ksh @@ -33,12 +33,11 @@ # 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 index 000000000000..2ff0cb7ebc04 --- /dev/null +++ b/tests/zfs-tests/tests/functional/upgrade/upgrade_common.kshlib @@ -0,0 +1,41 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib + +export TMPDEV=/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 index 000000000000..9e1356834819 --- /dev/null +++ b/tests/zfs-tests/tests/functional/upgrade/upgrade_projectquota_001_pos.ksh @@ -0,0 +1,108 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (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" + +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" + +# 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 3 # 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" + +# All in all, after having been through this, the dataset for testpool +# still shouldn't be upgraded +zfs projectspace -o used $TESTPOOL | grep -q "USED" && + log_fail "project quota should be disabled for $TESTPOOL" + +# Manual upgrade root dataset +# uses an ioctl which will wait for the upgrade to be done before returning +log_must zfs set version=current $TESTPOOL +zfs projectspace -o used $TESTPOOL | grep -q "USED" || + log_fail "project quota should be enabled for $TESTPOOL" + +log_pass "Project Quota upgrade done" diff --git a/tests/zfs-tests/tests/functional/upgrade/upgrade_userobj_001_pos.ksh b/tests/zfs-tests/tests/functional/upgrade/upgrade_userobj_001_pos.ksh index dda594f4e2ab..b437a0cdfa3d 100755 --- a/tests/zfs-tests/tests/functional/upgrade/upgrade_userobj_001_pos.ksh +++ b/tests/zfs-tests/tests/functional/upgrade/upgrade_userobj_001_pos.ksh @@ -25,7 +25,7 @@ # Copyright (c) 2017 Datto Inc. # -. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/upgrade/upgrade_common.kshlib # # DESCRIPTION: @@ -41,16 +41,12 @@ # 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