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/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..daa19fc7ca7a 100644 --- a/include/sys/dmu_objset.h +++ b/include/sys/dmu_objset.h @@ -49,24 +49,28 @@ 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; + dnode_phys_t os_projectused_dnode; + char os_pad1[OBJSET_PHYS_SIZE_V3 - OBJSET_PHYS_SIZE_V2 - + sizeof (dnode_phys_t)]; } objset_phys_t; typedef int (*dmu_objset_upgrade_cb_t)(objset_t *); @@ -86,6 +90,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 +137,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 +159,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 +202,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..027e7eabc4f5 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; 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..0279fdd6d88a 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]; @@ -137,6 +140,7 @@ void zfs_sa_get_scanstamp(struct znode *, xvattr_t *); void zfs_sa_set_scanstamp(struct znode *, xvattr_t *, dmu_tx_t *); int zfs_sa_get_xattr(struct znode *); int zfs_sa_set_xattr(struct znode *); +int zfs_sa_upgrade_with_projid(struct sa_handle *, dmu_tx_t *, int64_t); void zfs_sa_upgrade(struct sa_handle *, dmu_tx_t *); void zfs_sa_upgrade_txholds(dmu_tx_t *, struct znode *); void zfs_sa_init(void); 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..e029508caf00 100644 --- a/include/sys/zfs_znode.h +++ b/include/sys/zfs_znode.h @@ -66,6 +66,19 @@ extern "C" { #define ZFS_OFFLINE 0x0000100000000000ull #define ZFS_SPARSE 0x0000200000000000ull +/* + * PROJINHERIT attribute is used to indicate that the child object under the + * directory which has the PROJINHERIT attribute needs to inherit its parent + * project ID that is used by project quota. + * + */ +#define ZFS_PROJINHERIT 0x0000400000000000ull + +/* + * PROJID attr is used internally to indicate that the object has project ID. + */ +#define ZFS_PROJID 0x0000800000000000ull + #define ZFS_ATTR_SET(zp, attr, value, pflags, tx) \ { \ if (value) \ @@ -110,6 +123,7 @@ extern "C" { #define SA_ZPL_ZNODE_ACL(z) z->z_attr_table[ZPL_ZNODE_ACL] #define SA_ZPL_DXATTR(z) z->z_attr_table[ZPL_DXATTR] #define SA_ZPL_PAD(z) z->z_attr_table[ZPL_PAD] +#define SA_ZPL_PROJID(z) z->z_attr_table[ZPL_PROJID] /* * Is ID ephemeral? @@ -128,7 +142,7 @@ extern "C" { /* * Special attributes for master node. - * "userquota@" and "groupquota@" are also valid (from + * "userquota@", "groupquota@" and "projectquota@" are also valid (from * zfs_userquota_prop_prefixes[]). */ #define ZFS_FSID "FSID" @@ -196,6 +210,7 @@ typedef struct znode { krwlock_t z_xattr_lock; /* xattr data lock */ nvlist_t *z_xattr_cached; /* cached xattrs */ uint64_t z_xattr_parent; /* parent obj for this xattr */ + 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..2192a4cf5502 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,7 +387,7 @@ 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)); @@ -409,12 +416,19 @@ dmu_objset_open_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, return (err); } + if (spa_version(spa) < SPA_VERSION_USERSPACE) + size = OBJSET_PHYS_SIZE_V1; + else if (!spa_feature_is_enabled(spa, + SPA_FEATURE_PROJECT_QUOTA)) + size = OBJSET_PHYS_SIZE_V2; + else + size = sizeof (objset_phys_t); + /* Increase the blocksize if we are permitted. */ - if (spa_version(spa) >= SPA_VERSION_USERSPACE && - arc_buf_size(os->os_phys_buf) < sizeof (objset_phys_t)) { + if (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,11 @@ 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; + if (spa_version(spa) < SPA_VERSION_USERSPACE) + size = OBJSET_PHYS_SIZE_V1; + else + size = sizeof (objset_phys_t); + 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 +570,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 +692,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 +798,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 +856,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 +958,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 +1274,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 +1386,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 +1475,14 @@ dmu_objset_userobjused_enabled(objset_t *os) spa_feature_is_enabled(os->os_spa, SPA_FEATURE_USEROBJ_ACCOUNTING)); } +boolean_t +dmu_objset_projectquota_enabled(objset_t *os) +{ + return (used_cbs[os->os_phys->os_type] != NULL && + DMU_PROJECTUSED_DNODE(os) != NULL && + spa_feature_is_enabled(os->os_spa, SPA_FEATURE_PROJECT_QUOTA)); +} + typedef struct userquota_node { /* must be in the first filed, see userquota_update_cache() */ char uqn_id[20 + DMU_OBJACCT_PREFIX_LEN]; @@ -1447,6 +1493,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 +1546,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 +1583,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 +1599,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 +1623,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 +1657,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 +1672,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 +1697,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 +1731,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 +1801,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 +1847,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 +1859,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 +1872,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 +1914,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,11 +1989,12 @@ 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)); @@ -1898,20 +2003,26 @@ dmu_objset_userobjspace_upgrade_cb(objset_t *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_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 +2034,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 +2642,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_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..539b74341679 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} }; @@ -266,13 +269,14 @@ zfs_sa_set_xattr(znode_t *zp) * All new files will be created with the new format. */ -void -zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) +int +zfs_sa_upgrade_with_projid(sa_handle_t *hdl, dmu_tx_t *tx, int64_t projid) { dmu_buf_t *db = sa_get_db(hdl); znode_t *zp = sa_get_userdata(hdl); zfsvfs_t *zfsvfs = ZTOZSB(zp); int count = 0; + int err = 0; sa_bulk_attr_t *bulk, *sa_attrs; zfs_acl_locator_cb_t locate = { 0 }; uint64_t uid, gid, mode, rdev, xattr, parent, tmp_gen; @@ -289,7 +293,7 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) * interfaces that would be messy */ if (zp->z_acl_cached == NULL || S_ISLNK(ZTOI(zp)->i_mode)) - return; + return (0); /* * If the z_lock is held and we aren't the owner @@ -302,13 +306,13 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) */ if (mutex_owner(&zp->z_lock) != curthread) { if (mutex_tryenter(&zp->z_lock) == 0) - return; + return (SET_ERROR(EAGAIN)); else drop_lock = B_TRUE; } /* 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); @@ -323,17 +327,36 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ZNODE_ACL(zfsvfs), NULL, &znode_acl, 88); - if (sa_bulk_lookup_locked(hdl, bulk, count) != 0) { - kmem_free(bulk, sizeof (sa_bulk_attr_t) * 20); + err = sa_bulk_lookup_locked(hdl, bulk, count); + if (err != 0) { + kmem_free(bulk, sizeof (sa_bulk_attr_t) * ZPL_END); goto done; } + if (dmu_objset_projectquota_enabled(hdl->sa_os)) { + if (projid == ZFS_INVALID_PROJID) { + err = sa_lookup_locked(hdl, SA_ZPL_PROJID(zfsvfs), + &projid, 8); + if (err == ENOENT) { + err = 0; + projid = 0; + } + if (err != 0) { + 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); @@ -356,9 +379,13 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) links = ZTOI(zp)->i_nlink; SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_LINKS(zfsvfs), NULL, &links, 8); + if (dmu_objset_projectquota_enabled(hdl->sa_os)) + SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_PROJID(zfsvfs), NULL, + &projid, 8); if (S_ISBLK(ZTOI(zp)->i_mode) || S_ISCHR(ZTOI(zp)->i_mode)) SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_RDEV(zfsvfs), NULL, &rdev, 8); + SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_DACL_COUNT(zfsvfs), NULL, &zp->z_acl_cached->z_acl_count, 8); @@ -391,11 +418,18 @@ 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); + return (err); +} + +void +zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) +{ + (void) zfs_sa_upgrade_with_projid(hdl, tx, ZFS_INVALID_PROJID); } void 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..ac07e5f2d3e2 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; @@ -2688,12 +2712,34 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) zfs_acl_t *aclp; boolean_t skipaclchk = (flags & ATTR_NOACLCHECK) ? B_TRUE : B_FALSE; boolean_t fuid_dirtied = B_FALSE; + boolean_t done = B_FALSE; sa_bulk_attr_t *bulk, *xattr_bulk; int count = 0, xattr_count = 0; + int bulk_cnt = 8; if (mask == 0) return (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 && + !(zp->z_pflags & ZFS_PROJID))) && + !dmu_objset_projectquota_enabled(os)) + return (SET_ERROR(ENOTSUP)); + } + + if (projid != ZFS_INVALID_PROJID && projid == zp->z_projid && + zp->z_pflags & ZFS_PROJID) + return (0); + ZFS_ENTER(zfsvfs); ZFS_VERIFY_ZP(zp); @@ -2722,17 +2768,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 +2918,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 +3042,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 +3055,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 +3068,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 +3135,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 && !(zp->z_pflags & ZFS_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; @@ -3082,6 +3153,31 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) * updated as a side-effect of calling this function. */ + /* + * For the object updated from old system, it has no project + * attr ID before, setting project ID will change the layout. + */ + if (projid != ZFS_INVALID_PROJID && !(zp->z_pflags & ZFS_PROJID)) { + if (attrzp) { + mutex_enter(&attrzp->z_lock); + err = zfs_sa_upgrade_with_projid(attrzp->z_sa_hdl, tx, + projid); + mutex_exit(&attrzp->z_lock); + if (err != 0) + goto out; + } + + mutex_enter(&zp->z_lock); + err = zfs_sa_upgrade_with_projid(zp->z_sa_hdl, tx, projid); + mutex_exit(&zp->z_lock); + if (err == 0) { + zfs_log_setattr(zilog, tx, TX_SETATTR, zp, vap, mask, + fuidp); + done = B_TRUE; + } + + goto out; + } if (mask & (ATTR_UID|ATTR_GID|ATTR_MODE)) mutex_enter(&zp->z_acl_lock); @@ -3097,6 +3193,12 @@ 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) { + 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 +3278,13 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) ctime, sizeof (ctime)); } + if (projid != ZFS_INVALID_PROJID) { + zp->z_projid = projid; + SA_ADD_BULK_ATTR(bulk, count, + SA_ZPL_PROJID(zfsvfs), NULL, &zp->z_projid, + sizeof (zp->z_projid)); + } + if (attrzp && mask) { SA_ADD_BULK_ATTR(xattr_bulk, xattr_count, SA_ZPL_CTIME(zfsvfs), NULL, &ctime, @@ -3212,6 +3321,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)); @@ -3235,7 +3347,7 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) mutex_exit(&attrzp->z_lock); } out: - if (err == 0 && attrzp) { + if (err == 0 && attrzp && !done) { err2 = sa_bulk_update(attrzp->z_sa_hdl, xattr_bulk, xattr_count, tx); ASSERT(err2 == 0); @@ -3256,7 +3368,8 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) if (err == ERESTART) goto top; } else { - err2 = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); + if (!done) + err2 = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); dmu_tx_commit(tx); if (attrzp) iput(ZTOI(attrzp)); @@ -3264,12 +3377,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 +3675,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 +3784,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 +3927,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 +4104,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 +4308,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..38ccee7f3b20 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,28 @@ 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 (obj_type != DMU_OT_ZNODE && + 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(vap->va_mode)) + pflags |= ZFS_PROJINHERIT; + /* * No execs denied will be deterimed when zfs_mode_compute() is called. */ @@ -884,7 +920,11 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, if (obj_type == DMU_OT_ZNODE) { SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_XATTR(zfsvfs), NULL, &empty_xattr, 8); + } else if (dmu_objset_projectquota_enabled(zfsvfs->z_os)) { + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_PROJID(zfsvfs), + NULL, &projid, 8); } + if (obj_type == DMU_OT_ZNODE || (S_ISBLK(vap->va_mode) || S_ISCHR(vap->va_mode))) { SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_RDEV(zfsvfs), @@ -901,15 +941,18 @@ 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 (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 +1092,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 +1214,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 +1290,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