diff --git a/cmd/zdb/zdb.c b/cmd/zdb/zdb.c index 063358a0432c..6e3539d938ef 100644 --- a/cmd/zdb/zdb.c +++ b/cmd/zdb/zdb.c @@ -1880,6 +1880,13 @@ dump_znode(objset_t *os, uint64_t object, void *data, size_t size) (void) printf("\tparent %llu\n", (u_longlong_t)parent); (void) printf("\tlinks %llu\n", (u_longlong_t)links); (void) printf("\tpflags %llx\n", (u_longlong_t)pflags); + if (dmu_objset_projectquota_enabled(os) && (pflags & ZFS_PROJID)) { + uint64_t projid; + + if (sa_lookup(hdl, sa_attr_table[ZPL_PROJID], &projid, + sizeof (uint64_t)) == 0) + (void) printf("\tprojid %llu\n", (u_longlong_t)projid); + } if (sa_lookup(hdl, sa_attr_table[ZPL_XATTR], &xattr, sizeof (uint64_t)) == 0) (void) printf("\txattr %llu\n", (u_longlong_t)xattr); @@ -1942,8 +1949,8 @@ static object_viewer_t *object_viewer[DMU_OT_NUMTYPES + 1] = { dump_packed_nvlist, /* FUID nvlist size */ dump_zap, /* DSL dataset next clones */ dump_zap, /* DSL scrub queue */ - dump_zap, /* ZFS user/group used */ - dump_zap, /* ZFS user/group quota */ + dump_zap, /* ZFS user/group/project used */ + dump_zap, /* ZFS user/group/project quota */ dump_zap, /* snapshot refcount tags */ dump_ddt_zap, /* DDT ZAP object */ dump_zap, /* DDT statistics */ @@ -2218,6 +2225,11 @@ dump_dir(objset_t *os) NULL); } + if (DMU_PROJECTUSED_DNODE(os) != NULL && + DMU_PROJECTUSED_DNODE(os)->dn_type != 0) + dump_object(os, DMU_PROJECTUSED_OBJECT, verbosity, + &print_header, NULL); + object = 0; while ((error = dmu_object_next(os, &object, B_FALSE, 0)) == 0) { dump_object(os, object, verbosity, &print_header, &dnode_slots); diff --git a/cmd/zfs/Makefile.am b/cmd/zfs/Makefile.am index 4e689d1ee5e5..8b6ddaa20010 100644 --- a/cmd/zfs/Makefile.am +++ b/cmd/zfs/Makefile.am @@ -10,7 +10,9 @@ zfs_SOURCES = \ zfs_iter.c \ zfs_iter.h \ zfs_main.c \ - zfs_util.h + zfs_util.h \ + zfs_project.c \ + zfs_projectutil.h zfs_LDADD = \ $(top_builddir)/lib/libnvpair/libnvpair.la \ diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 991dd4444586..16410d2f2311 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -59,6 +59,7 @@ #include <sys/systeminfo.h> #include <sys/types.h> #include <time.h> +#include <sys/zfs_project.h> #include <libzfs.h> #include <libzfs_core.h> @@ -74,6 +75,7 @@ #include "zfs_util.h" #include "zfs_comutil.h" #include "libzfs_impl.h" +#include "zfs_projectutil.h" libzfs_handle_t *g_zfs; @@ -111,6 +113,7 @@ static int zfs_do_channel_program(int argc, char **argv); static int zfs_do_load_key(int argc, char **argv); static int zfs_do_unload_key(int argc, char **argv); static int zfs_do_change_key(int argc, char **argv); +static int zfs_do_project(int argc, char **argv); /* * Enable a reasonable set of defaults for libumem debugging on DEBUG builds. @@ -153,6 +156,8 @@ typedef enum { HELP_UNALLOW, HELP_USERSPACE, HELP_GROUPSPACE, + HELP_PROJECTSPACE, + HELP_PROJECT, HELP_HOLD, HELP_HOLDS, HELP_RELEASE, @@ -197,8 +202,12 @@ static zfs_command_t command_table[] = { { "get", zfs_do_get, HELP_GET }, { "inherit", zfs_do_inherit, HELP_INHERIT }, { "upgrade", zfs_do_upgrade, HELP_UPGRADE }, + { NULL }, { "userspace", zfs_do_userspace, HELP_USERSPACE }, { "groupspace", zfs_do_userspace, HELP_GROUPSPACE }, + { "projectspace", zfs_do_userspace, HELP_PROJECTSPACE }, + { NULL }, + { "project", zfs_do_project, HELP_PROJECT }, { NULL }, { "mount", zfs_do_mount, HELP_MOUNT }, { "unmount", zfs_do_unmount, HELP_UNMOUNT }, @@ -328,6 +337,15 @@ get_usage(zfs_help_t idx) "[-s field] ...\n" "\t [-S field] ... [-t type[,...]] " "<filesystem|snapshot>\n")); + case HELP_PROJECTSPACE: + return (gettext("\tprojectspace [-Hp] [-o field[,...]] " + "[-s field] ... \n" + "\t [-S field] ... <filesystem|snapshot>\n")); + case HELP_PROJECT: + return (gettext("\tproject [-d|-r] <directory|file ...>\n" + "\tproject -c [-0] [-d|-r] [-p id] <directory|file ...>\n" + "\tproject -C [-k] [-r] <directory ...>\n" + "\tproject [-p id] [-r] [-s] <directory ...>\n")); case HELP_HOLD: return (gettext("\thold [-r] <tag> <snapshot> ...\n")); case HELP_HOLDS: @@ -489,10 +507,26 @@ usage(boolean_t requested) (void) fprintf(fp, " NO NO <size>\n"); (void) fprintf(fp, "\t%-15s ", "groupused@..."); (void) fprintf(fp, " NO NO <size>\n"); + (void) fprintf(fp, "\t%-15s ", "projectused@..."); + (void) fprintf(fp, " NO NO <size>\n"); + (void) fprintf(fp, "\t%-15s ", "userobjused@..."); + (void) fprintf(fp, " NO NO <size>\n"); + (void) fprintf(fp, "\t%-15s ", "groupobjused@..."); + (void) fprintf(fp, " NO NO <size>\n"); + (void) fprintf(fp, "\t%-15s ", "projectobjused@..."); + (void) fprintf(fp, " NO NO <size>\n"); (void) fprintf(fp, "\t%-15s ", "userquota@..."); (void) fprintf(fp, "YES NO <size> | none\n"); (void) fprintf(fp, "\t%-15s ", "groupquota@..."); (void) fprintf(fp, "YES NO <size> | none\n"); + (void) fprintf(fp, "\t%-15s ", "projectquota@..."); + (void) fprintf(fp, "YES NO <size> | none\n"); + (void) fprintf(fp, "\t%-15s ", "userobjquota@..."); + (void) fprintf(fp, "YES NO <size> | none\n"); + (void) fprintf(fp, "\t%-15s ", "groupobjquota@..."); + (void) fprintf(fp, "YES NO <size> | none\n"); + (void) fprintf(fp, "\t%-15s ", "projectobjquota@..."); + (void) fprintf(fp, "YES NO <size> | none\n"); (void) fprintf(fp, "\t%-15s ", "written@<snap>"); (void) fprintf(fp, " NO NO <size>\n"); @@ -500,9 +534,9 @@ usage(boolean_t requested) "with standard units such as K, M, G, etc.\n")); (void) fprintf(fp, gettext("\nUser-defined properties can " "be specified by using a name containing a colon (:).\n")); - (void) fprintf(fp, gettext("\nThe {user|group}{used|quota}@ " - "properties must be appended with\n" - "a user or group specifier of one of these forms:\n" + (void) fprintf(fp, gettext("\nThe {user|group|project}" + "[obj]{used|quota}@ properties must be appended with\n" + "a user|group|project specifier of one of these forms:\n" " POSIX name (eg: \"matt\")\n" " POSIX id (eg: \"126829\")\n" " SMB name@domain (eg: \"matt@sun\")\n" @@ -2270,6 +2304,8 @@ zfs_do_upgrade(int argc, char **argv) * [-S field [-S field]...] [-t type[,...]] filesystem | snapshot * zfs groupspace [-Hinp] [-o field[,...]] [-s field [-s field]...] * [-S field [-S field]...] [-t type[,...]] filesystem | snapshot + * zfs projectspace [-Hp] [-o field[,...]] [-s field [-s field]...] + * [-S field [-S field]...] filesystem | snapshot * * -H Scripted mode; elide headers and separate columns by tabs. * -i Translate SID to POSIX ID. @@ -2303,8 +2339,10 @@ static char *us_field_names[] = { "type", "name", "used", "quota", #define USTYPE_PSX_USR (1 << 1) #define USTYPE_SMB_GRP (1 << 2) #define USTYPE_SMB_USR (1 << 3) +#define USTYPE_PROJ (1 << 4) #define USTYPE_ALL \ - (USTYPE_PSX_GRP | USTYPE_PSX_USR | USTYPE_SMB_GRP | USTYPE_SMB_USR) + (USTYPE_PSX_GRP | USTYPE_PSX_USR | USTYPE_SMB_GRP | USTYPE_SMB_USR | \ + USTYPE_PROJ) static int us_type_bits[] = { USTYPE_PSX_GRP, @@ -2459,6 +2497,13 @@ zfs_prop_is_group(unsigned p) p == ZFS_PROP_GROUPOBJUSED || p == ZFS_PROP_GROUPOBJQUOTA); } +static boolean_t +zfs_prop_is_project(unsigned p) +{ + return (p == ZFS_PROP_PROJECTUSED || p == ZFS_PROP_PROJECTQUOTA || + p == ZFS_PROP_PROJECTOBJUSED || p == ZFS_PROP_PROJECTOBJQUOTA); +} + static inline const char * us_type2str(unsigned field_type) { @@ -2471,6 +2516,8 @@ us_type2str(unsigned field_type) return ("SMB User"); case USTYPE_SMB_GRP: return ("SMB Group"); + case USTYPE_PROJ: + return ("Project"); default: return ("Undefined"); } @@ -2556,7 +2603,7 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) if ((g = getgrgid(rid)) != NULL) name = g->gr_name; } - } else { + } else if (zfs_prop_is_user(prop)) { type = USTYPE_PSX_USR; if (!cb->cb_numname) { struct passwd *p; @@ -2564,6 +2611,8 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) if ((p = getpwuid(rid)) != NULL) name = p->pw_name; } + } else { + type = USTYPE_PROJ; } } @@ -2615,7 +2664,9 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) /* Calculate/update width of USED/QUOTA fields */ if (cb->cb_nicenum) { if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED || - prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA) { + prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA || + prop == ZFS_PROP_PROJECTUSED || + prop == ZFS_PROP_PROJECTQUOTA) { zfs_nicebytes(space, sizebuf, sizeof (sizebuf)); } else { zfs_nicenum(space, sizebuf, sizeof (sizebuf)); @@ -2625,21 +2676,24 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) (u_longlong_t)space); } sizelen = strlen(sizebuf); - if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED) { + if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED || + prop == ZFS_PROP_PROJECTUSED) { propname = "used"; if (!nvlist_exists(props, "quota")) (void) nvlist_add_uint64(props, "quota", 0); - } else if (prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA) { + } else if (prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA || + prop == ZFS_PROP_PROJECTQUOTA) { propname = "quota"; if (!nvlist_exists(props, "used")) (void) nvlist_add_uint64(props, "used", 0); } else if (prop == ZFS_PROP_USEROBJUSED || - prop == ZFS_PROP_GROUPOBJUSED) { + prop == ZFS_PROP_GROUPOBJUSED || prop == ZFS_PROP_PROJECTOBJUSED) { propname = "objused"; if (!nvlist_exists(props, "objquota")) (void) nvlist_add_uint64(props, "objquota", 0); } else if (prop == ZFS_PROP_USEROBJQUOTA || - prop == ZFS_PROP_GROUPOBJQUOTA) { + prop == ZFS_PROP_GROUPOBJQUOTA || + prop == ZFS_PROP_PROJECTOBJQUOTA) { propname = "objquota"; if (!nvlist_exists(props, "objused")) (void) nvlist_add_uint64(props, "objused", 0); @@ -2838,13 +2892,22 @@ zfs_do_userspace(int argc, char **argv) if (argc < 2) usage(B_FALSE); - if (strcmp(argv[0], "groupspace") == 0) + if (strcmp(argv[0], "groupspace") == 0) { /* Toggle default group types */ types = USTYPE_PSX_GRP | USTYPE_SMB_GRP; + } else if (strcmp(argv[0], "projectspace") == 0) { + types = USTYPE_PROJ; + prtnum = B_TRUE; + } while ((c = getopt(argc, argv, "nHpo:s:S:t:i")) != -1) { switch (c) { case 'n': + if (types == USTYPE_PROJ) { + (void) fprintf(stderr, + gettext("invalid option 'n'\n")); + usage(B_FALSE); + } prtnum = B_TRUE; break; case 'H': @@ -2866,9 +2929,19 @@ zfs_do_userspace(int argc, char **argv) } break; case 't': + if (types == USTYPE_PROJ) { + (void) fprintf(stderr, + gettext("invalid option 't'\n")); + usage(B_FALSE); + } tfield = optarg; break; case 'i': + if (types == USTYPE_PROJ) { + (void) fprintf(stderr, + gettext("invalid option 'i'\n")); + usage(B_FALSE); + } sid2posix = B_TRUE; break; case ':': @@ -2965,7 +3038,8 @@ zfs_do_userspace(int argc, char **argv) if ((zfs_prop_is_user(p) && !(types & (USTYPE_PSX_USR | USTYPE_SMB_USR))) || (zfs_prop_is_group(p) && - !(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP)))) + !(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP))) || + (zfs_prop_is_project(p) && types != USTYPE_PROJ)) continue; cb.cb_prop = p; @@ -4276,6 +4350,11 @@ zfs_do_receive(int argc, char **argv) #define ZFS_DELEG_PERM_LOAD_KEY "load-key" #define ZFS_DELEG_PERM_CHANGE_KEY "change-key" +#define ZFS_DELEG_PERM_PROJECTUSED "projectused" +#define ZFS_DELEG_PERM_PROJECTQUOTA "projectquota" +#define ZFS_DELEG_PERM_PROJECTOBJUSED "projectobjused" +#define ZFS_DELEG_PERM_PROJECTOBJQUOTA "projectobjquota" + #define ZFS_NUM_DELEG_NOTES ZFS_DELEG_NOTE_NONE static zfs_deleg_perm_tab_t zfs_deleg_perm_tbl[] = { @@ -4307,6 +4386,10 @@ static zfs_deleg_perm_tab_t zfs_deleg_perm_tbl[] = { { ZFS_DELEG_PERM_USEROBJUSED, ZFS_DELEG_NOTE_USEROBJUSED }, { ZFS_DELEG_PERM_GROUPOBJQUOTA, ZFS_DELEG_NOTE_GROUPOBJQUOTA }, { ZFS_DELEG_PERM_GROUPOBJUSED, ZFS_DELEG_NOTE_GROUPOBJUSED }, + { ZFS_DELEG_PERM_PROJECTUSED, ZFS_DELEG_NOTE_PROJECTUSED }, + { ZFS_DELEG_PERM_PROJECTQUOTA, ZFS_DELEG_NOTE_PROJECTQUOTA }, + { ZFS_DELEG_PERM_PROJECTOBJUSED, ZFS_DELEG_NOTE_PROJECTOBJUSED }, + { ZFS_DELEG_PERM_PROJECTOBJQUOTA, ZFS_DELEG_NOTE_PROJECTOBJQUOTA }, { NULL, ZFS_DELEG_NOTE_NONE } }; @@ -4388,6 +4471,10 @@ deleg_perm_type(zfs_deleg_note_t note) case ZFS_DELEG_NOTE_USEROBJUSED: case ZFS_DELEG_NOTE_GROUPOBJQUOTA: case ZFS_DELEG_NOTE_GROUPOBJUSED: + case ZFS_DELEG_NOTE_PROJECTUSED: + case ZFS_DELEG_NOTE_PROJECTQUOTA: + case ZFS_DELEG_NOTE_PROJECTOBJUSED: + case ZFS_DELEG_NOTE_PROJECTOBJQUOTA: /* other */ return (gettext("other")); default: @@ -4912,6 +4999,20 @@ deleg_perm_comment(zfs_deleg_note_t note) case ZFS_DELEG_NOTE_USEROBJUSED: str = gettext("Allows reading any userobjused@... property"); break; + case ZFS_DELEG_NOTE_PROJECTQUOTA: + str = gettext("Allows accessing any projectquota@... property"); + break; + case ZFS_DELEG_NOTE_PROJECTOBJQUOTA: + str = gettext("Allows accessing any \n\t\t\t\t" + "projectobjquota@... property"); + break; + case ZFS_DELEG_NOTE_PROJECTUSED: + str = gettext("Allows reading any projectused@... property"); + break; + case ZFS_DELEG_NOTE_PROJECTOBJUSED: + str = gettext("Allows accessing any \n\t\t\t\t" + "projectobjused@... property"); + break; /* other */ default: str = ""; @@ -7513,6 +7614,211 @@ zfs_do_change_key(int argc, char **argv) return (0); } +/* + * 1) zfs project [-d|-r] <file|directory ...> + * List project ID and inherit flag of file(s) or directories. + * -d: List the directory itself, not its children. + * -r: List subdirectories recursively. + * + * 2) zfs project -C [-k] [-r] <file|directory ...> + * Clear project inherit flag and/or ID on the file(s) or directories. + * -k: Keep the project ID unchanged. If not specified, the project ID + * will be reset as zero. + * -r: Clear on subdirectories recursively. + * + * 3) zfs project -c [-0] [-d|-r] [-p id] <file|directory ...> + * Check project ID and inherit flag on the file(s) or directories, + * report the outliers. + * -0: Print file name followed by a NUL instead of newline. + * -d: Check the directory itself, not its children. + * -p: Specify the referenced ID for comparing with the target file(s) + * or directories' project IDs. If not specified, the target (top) + * directory's project ID will be used as the referenced one. + * -r: Check subdirectories recursively. + * + * 4) zfs project [-p id] [-r] [-s] <file|directory ...> + * Set project ID and/or inherit flag on the file(s) or directories. + * -p: Set the project ID as the given id. + * -r: Set on subdirectorie recursively. If not specify "-p" option, + * it will use top-level directory's project ID as the given id, + * then set both project ID and inherit flag on all descendants + * of the top-level directory. + * -s: Set project inherit flag. + */ +static int +zfs_do_project(int argc, char **argv) +{ + zfs_project_control_t zpc = { + .zpc_expected_projid = ZFS_INVALID_PROJID, + .zpc_op = ZFS_PROJECT_OP_DEFAULT, + .zpc_dironly = B_FALSE, + .zpc_keep_projid = B_FALSE, + .zpc_newline = B_TRUE, + .zpc_recursive = B_FALSE, + .zpc_set_flag = B_FALSE, + }; + int ret = 0, c; + + if (argc < 2) + usage(B_FALSE); + + while ((c = getopt(argc, argv, "0Ccdkp:rs")) != -1) { + switch (c) { + case '0': + zpc.zpc_newline = B_FALSE; + break; + case 'C': + if (zpc.zpc_op != ZFS_PROJECT_OP_DEFAULT) { + (void) fprintf(stderr, gettext("cannot " + "specify '-C' '-c' '-s' together\n")); + usage(B_FALSE); + } + + zpc.zpc_op = ZFS_PROJECT_OP_CLEAR; + break; + case 'c': + if (zpc.zpc_op != ZFS_PROJECT_OP_DEFAULT) { + (void) fprintf(stderr, gettext("cannot " + "specify '-C' '-c' '-s' together\n")); + usage(B_FALSE); + } + + zpc.zpc_op = ZFS_PROJECT_OP_CHECK; + break; + case 'd': + zpc.zpc_dironly = B_TRUE; + /* overwrite "-r" option */ + zpc.zpc_recursive = B_FALSE; + break; + case 'k': + zpc.zpc_keep_projid = B_TRUE; + break; + case 'p': { + char *endptr; + + errno = 0; + zpc.zpc_expected_projid = strtoull(optarg, &endptr, 0); + if (errno != 0 || *endptr != '\0') { + (void) fprintf(stderr, + gettext("project ID must be less than " + "%u\n"), UINT32_MAX); + usage(B_FALSE); + } + if (zpc.zpc_expected_projid >= UINT32_MAX) { + (void) fprintf(stderr, + gettext("invalid project ID\n")); + usage(B_FALSE); + } + break; + } + case 'r': + zpc.zpc_recursive = B_TRUE; + /* overwrite "-d" option */ + zpc.zpc_dironly = B_FALSE; + break; + case 's': + if (zpc.zpc_op != ZFS_PROJECT_OP_DEFAULT) { + (void) fprintf(stderr, gettext("cannot " + "specify '-C' '-c' '-s' together\n")); + usage(B_FALSE); + } + + zpc.zpc_set_flag = B_TRUE; + zpc.zpc_op = ZFS_PROJECT_OP_SET; + break; + default: + (void) fprintf(stderr, gettext("invalid option '%c'\n"), + optopt); + usage(B_FALSE); + } + } + + if (zpc.zpc_op == ZFS_PROJECT_OP_DEFAULT) { + if (zpc.zpc_expected_projid != ZFS_INVALID_PROJID) + zpc.zpc_op = ZFS_PROJECT_OP_SET; + else + zpc.zpc_op = ZFS_PROJECT_OP_LIST; + } + + switch (zpc.zpc_op) { + case ZFS_PROJECT_OP_LIST: + if (zpc.zpc_keep_projid) { + (void) fprintf(stderr, + gettext("'-k' is only valid together with '-C'\n")); + usage(B_FALSE); + } + if (!zpc.zpc_newline) { + (void) fprintf(stderr, + gettext("'-0' is only valid together with '-c'\n")); + usage(B_FALSE); + } + break; + case ZFS_PROJECT_OP_CHECK: + if (zpc.zpc_keep_projid) { + (void) fprintf(stderr, + gettext("'-k' is only valid together with '-C'\n")); + usage(B_FALSE); + } + break; + case ZFS_PROJECT_OP_CLEAR: + if (zpc.zpc_dironly) { + (void) fprintf(stderr, + gettext("'-d' is useless together with '-C'\n")); + usage(B_FALSE); + } + if (!zpc.zpc_newline) { + (void) fprintf(stderr, + gettext("'-0' is only valid together with '-c'\n")); + usage(B_FALSE); + } + if (zpc.zpc_expected_projid != ZFS_INVALID_PROJID) { + (void) fprintf(stderr, + gettext("'-p' is useless together with '-C'\n")); + usage(B_FALSE); + } + break; + case ZFS_PROJECT_OP_SET: + if (zpc.zpc_dironly) { + (void) fprintf(stderr, + gettext("'-d' is useless for set project ID and/or " + "inherit flag\n")); + usage(B_FALSE); + } + if (zpc.zpc_keep_projid) { + (void) fprintf(stderr, + gettext("'-k' is only valid together with '-C'\n")); + usage(B_FALSE); + } + if (!zpc.zpc_newline) { + (void) fprintf(stderr, + gettext("'-0' is only valid together with '-c'\n")); + usage(B_FALSE); + } + break; + default: + ASSERT(0); + break; + } + + argv += optind; + argc -= optind; + if (argc == 0) { + (void) fprintf(stderr, + gettext("missing file or directory target(s)\n")); + usage(B_FALSE); + } + + for (int i = 0; i < argc; i++) { + int err; + + err = zfs_project_handle(argv[i], &zpc); + if (err && !ret) + ret = err; + } + + return (ret); +} + int main(int argc, char **argv) { diff --git a/cmd/zfs/zfs_project.c b/cmd/zfs/zfs_project.c new file mode 100644 index 000000000000..5ac88f279b5b --- /dev/null +++ b/cmd/zfs/zfs_project.c @@ -0,0 +1,295 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ + +/* + * Copyright (c) 2017, Intle Corporation. All rights reserved. + */ + +#include <errno.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <stddef.h> +#include <libintl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/list.h> +#include <sys/zfs_project.h> + +#include "zfs_util.h" +#include "zfs_projectutil.h" + +typedef struct zfs_project_item { + list_node_t zpi_list; + char zpi_name[PATH_MAX]; +} zfs_project_item_t; + +static void +zfs_project_item_alloc(list_t *head, const char *name) +{ + zfs_project_item_t *zpi; + + zpi = safe_malloc(sizeof (zfs_project_item_t)); + strcpy(zpi->zpi_name, name); + list_insert_tail(head, zpi); +} + +static int +zfs_project_sanity_check(const char *name, zfs_project_control_t *zpc, + struct stat *st) +{ + int ret; + + ret = stat(name, st); + if (ret) { + (void) fprintf(stderr, gettext("failed to stat %s: %s\n"), + name, strerror(errno)); + return (ret); + } + + if (!S_ISREG(st->st_mode) && !S_ISDIR(st->st_mode)) { + (void) fprintf(stderr, gettext("only support project quota on " + "regular file or directory\n")); + return (-1); + } + + if (!S_ISDIR(st->st_mode)) { + if (zpc->zpc_dironly) { + (void) fprintf(stderr, gettext( + "'-d' option on non-dir target %s\n"), name); + return (-1); + } + + if (zpc->zpc_recursive) { + (void) fprintf(stderr, gettext( + "'-r' option on non-dir target %s\n"), name); + return (-1); + } + } + + return (0); +} + +static int +zfs_project_load_projid(const char *name, zfs_project_control_t *zpc) +{ + zfsxattr_t fsx; + int ret, fd; + + fd = open(name, O_RDONLY | O_NOCTTY); + if (fd < 0) { + (void) fprintf(stderr, gettext("failed to open %s: %s\n"), + name, strerror(errno)); + return (fd); + } + + ret = ioctl(fd, ZFS_IOC_FSGETXATTR, &fsx); + if (ret) + (void) fprintf(stderr, + gettext("failed to get xattr for %s: %s\n"), + name, strerror(errno)); + else + zpc->zpc_expected_projid = fsx.fsx_projid; + + close(fd); + return (ret); +} + +static int +zfs_project_handle_one(const char *name, zfs_project_control_t *zpc) +{ + zfsxattr_t fsx; + int ret, fd; + + fd = open(name, O_RDONLY | O_NOCTTY); + if (fd < 0) { + if (errno == ENOENT && zpc->zpc_ignore_noent) + return (0); + + (void) fprintf(stderr, gettext("failed to open %s: %s\n"), + name, strerror(errno)); + return (fd); + } + + ret = ioctl(fd, ZFS_IOC_FSGETXATTR, &fsx); + if (ret) { + (void) fprintf(stderr, + gettext("failed to get xattr for %s: %s\n"), + name, strerror(errno)); + goto out; + } + + switch (zpc->zpc_op) { + case ZFS_PROJECT_OP_LIST: + (void) printf("%5u %c %s\n", fsx.fsx_projid, + (fsx.fsx_xflags & ZFS_PROJINHERIT_FL) ? 'P' : '-', name); + goto out; + case ZFS_PROJECT_OP_CHECK: + if (fsx.fsx_projid == zpc->zpc_expected_projid && + fsx.fsx_xflags & ZFS_PROJINHERIT_FL) + goto out; + + if (!zpc->zpc_newline) { + char c = '\0'; + + (void) printf("%s%c", name, c); + goto out; + } + + if (fsx.fsx_projid != zpc->zpc_expected_projid) + (void) printf("%s - project ID is not set properly " + "(%u/%u)\n", name, fsx.fsx_projid, + (uint32_t)zpc->zpc_expected_projid); + + if (!(fsx.fsx_xflags & ZFS_PROJINHERIT_FL)) + (void) printf("%s - project inherit flag is not set\n", + name); + + goto out; + case ZFS_PROJECT_OP_CLEAR: + if (!(fsx.fsx_xflags & ZFS_PROJINHERIT_FL) && + (zpc->zpc_keep_projid || + fsx.fsx_projid == ZFS_DEFAULT_PROJID)) + goto out; + + fsx.fsx_xflags &= ~ZFS_PROJINHERIT_FL; + if (!zpc->zpc_keep_projid) + fsx.fsx_projid = ZFS_DEFAULT_PROJID; + break; + case ZFS_PROJECT_OP_SET: + if (fsx.fsx_projid == zpc->zpc_expected_projid && + (!zpc->zpc_set_flag || fsx.fsx_xflags & ZFS_PROJINHERIT_FL)) + goto out; + + fsx.fsx_projid = zpc->zpc_expected_projid; + if (zpc->zpc_set_flag) + fsx.fsx_xflags |= ZFS_PROJINHERIT_FL; + break; + default: + ASSERT(0); + break; + } + + ret = ioctl(fd, ZFS_IOC_FSSETXATTR, &fsx); + if (ret) + (void) fprintf(stderr, + gettext("failed to set xattr for %s: %s\n"), + name, strerror(errno)); + +out: + close(fd); + return (ret); +} + +static int +zfs_project_handle_dir(const char *name, zfs_project_control_t *zpc, + list_t *head) +{ + char fullname[PATH_MAX]; + struct dirent *ent; + DIR *dir; + int ret = 0; + + dir = opendir(name); + if (dir == NULL) { + if (errno == ENOENT && zpc->zpc_ignore_noent) + return (0); + + ret = -errno; + (void) fprintf(stderr, gettext("failed to opendir %s: %s\n"), + name, strerror(errno)); + return (ret); + } + + /* Non-top item, ignore the case of being removed or renamed by race. */ + zpc->zpc_ignore_noent = B_TRUE; + errno = 0; + while (!ret && (ent = readdir(dir)) != NULL) { + /* skip "." and ".." */ + if (strcmp(ent->d_name, ".") == 0 || + strcmp(ent->d_name, "..") == 0) + continue; + + if (strlen(ent->d_name) + strlen(name) >= + sizeof (fullname) + 1) { + errno = ENAMETOOLONG; + break; + } + + sprintf(fullname, "%s/%s", name, ent->d_name); + ret = zfs_project_handle_one(fullname, zpc); + if (!ret && zpc->zpc_recursive && ent->d_type == DT_DIR) + zfs_project_item_alloc(head, fullname); + } + + if (errno && !ret) { + ret = -errno; + (void) fprintf(stderr, gettext("failed to readdir %s: %s\n"), + name, strerror(errno)); + } + + closedir(dir); + return (ret); +} + +int +zfs_project_handle(const char *name, zfs_project_control_t *zpc) +{ + zfs_project_item_t *zpi; + struct stat st; + list_t head; + int ret; + + ret = zfs_project_sanity_check(name, zpc, &st); + if (ret) + return (ret); + + if ((zpc->zpc_op == ZFS_PROJECT_OP_SET || + zpc->zpc_op == ZFS_PROJECT_OP_CHECK) && + zpc->zpc_expected_projid == ZFS_INVALID_PROJID) { + ret = zfs_project_load_projid(name, zpc); + if (ret) + return (ret); + } + + zpc->zpc_ignore_noent = B_FALSE; + ret = zfs_project_handle_one(name, zpc); + if (ret || !S_ISDIR(st.st_mode) || zpc->zpc_dironly || + (!zpc->zpc_recursive && + zpc->zpc_op != ZFS_PROJECT_OP_LIST && + zpc->zpc_op != ZFS_PROJECT_OP_CHECK)) + return (ret); + + list_create(&head, sizeof (zfs_project_item_t), + offsetof(zfs_project_item_t, zpi_list)); + zfs_project_item_alloc(&head, name); + while ((zpi = list_remove_head(&head)) != NULL) { + if (!ret) + ret = zfs_project_handle_dir(zpi->zpi_name, zpc, &head); + free(zpi); + } + + return (ret); +} diff --git a/cmd/zfs/zfs_projectutil.h b/cmd/zfs/zfs_projectutil.h new file mode 100644 index 000000000000..1792a3383a03 --- /dev/null +++ b/cmd/zfs/zfs_projectutil.h @@ -0,0 +1,49 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2017, Intel Corporation. All rights reserved. + */ + +#ifndef _ZFS_PROJECTUTIL_H +#define _ZFS_PROJECTUTIL_H + +typedef enum { + ZFS_PROJECT_OP_DEFAULT = 0, + ZFS_PROJECT_OP_LIST = 1, + ZFS_PROJECT_OP_CHECK = 2, + ZFS_PROJECT_OP_CLEAR = 3, + ZFS_PROJECT_OP_SET = 4, +} zfs_project_ops_t; + +typedef struct zfs_project_control { + uint64_t zpc_expected_projid; + zfs_project_ops_t zpc_op; + boolean_t zpc_dironly; + boolean_t zpc_ignore_noent; + boolean_t zpc_keep_projid; + boolean_t zpc_newline; + boolean_t zpc_recursive; + boolean_t zpc_set_flag; +} zfs_project_control_t; + +int zfs_project_handle(const char *name, zfs_project_control_t *zpc); + +#endif /* _ZFS_PROJECTUTIL_H */ diff --git a/cmd/zhack/zhack.c b/cmd/zhack/zhack.c index e15af8f4ea02..296a7fe75825 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 6dd6834a3ca1..5ad82b4b73c8 100644 --- a/configure.ac +++ b/configure.ac @@ -280,6 +280,7 @@ AC_CONFIG_FILES([ tests/zfs-tests/tests/functional/pool_names/Makefile tests/zfs-tests/tests/functional/poolversion/Makefile tests/zfs-tests/tests/functional/privilege/Makefile + tests/zfs-tests/tests/functional/projectquota/Makefile tests/zfs-tests/tests/functional/quota/Makefile tests/zfs-tests/tests/functional/raidz/Makefile tests/zfs-tests/tests/functional/redundancy/Makefile diff --git a/include/sys/Makefile.am b/include/sys/Makefile.am index 348e6584f83d..8e18a87904a8 100644 --- a/include/sys/Makefile.am +++ b/include/sys/Makefile.am @@ -105,6 +105,7 @@ COMMON_H = \ $(top_srcdir)/include/sys/zfs_delay.h \ $(top_srcdir)/include/sys/zfs_dir.h \ $(top_srcdir)/include/sys/zfs_fuid.h \ + $(top_srcdir)/include/sys/zfs_project.h \ $(top_srcdir)/include/sys/zfs_ratelimit.h \ $(top_srcdir)/include/sys/zfs_rlock.h \ $(top_srcdir)/include/sys/zfs_sa.h \ diff --git a/include/sys/dmu.h b/include/sys/dmu.h index 5553667c3774..cf9cbaa645e6 100644 --- a/include/sys/dmu.h +++ b/include/sys/dmu.h @@ -276,9 +276,10 @@ void zfs_znode_byteswap(void *buf, size_t size); #define DMU_USERUSED_OBJECT (-1ULL) #define DMU_GROUPUSED_OBJECT (-2ULL) +#define DMU_PROJECTUSED_OBJECT (-3ULL) /* - * Zap prefix for object accounting in DMU_{USER,GROUP}USED_OBJECT. + * Zap prefix for object accounting in DMU_{USER,GROUP,PROJECT}USED_OBJECT. */ #define DMU_OBJACCT_PREFIX "obj-" #define DMU_OBJACCT_PREFIX_LEN 4 @@ -971,7 +972,7 @@ extern int dmu_dir_list_next(objset_t *os, int namelen, char *name, uint64_t *idp, uint64_t *offp); typedef int objset_used_cb_t(dmu_object_type_t bonustype, - void *bonus, uint64_t *userp, uint64_t *groupp); + void *bonus, uint64_t *userp, uint64_t *groupp, uint64_t *projectp); extern void dmu_objset_register_type(dmu_objset_type_t ost, objset_used_cb_t *cb); extern void dmu_objset_set_user(objset_t *os, void *user_ptr); diff --git a/include/sys/dmu_objset.h b/include/sys/dmu_objset.h index 7ee992f3116e..df9b1a73ac2f 100644 --- a/include/sys/dmu_objset.h +++ b/include/sys/dmu_objset.h @@ -49,14 +49,18 @@ struct dsl_pool; struct dsl_dataset; struct dmu_tx; -#define OBJSET_PHYS_SIZE 2048 -#define OBJSET_OLD_PHYS_SIZE 1024 +#define OBJSET_PHYS_SIZE_V1 1024 +#define OBJSET_PHYS_SIZE_V2 2048 +#define OBJSET_PHYS_SIZE_V3 4096 #define OBJSET_BUF_HAS_USERUSED(buf) \ - (arc_buf_size(buf) > OBJSET_OLD_PHYS_SIZE) + (arc_buf_size(buf) >= OBJSET_PHYS_SIZE_V2) +#define OBJSET_BUF_HAS_PROJECTUSED(buf) \ + (arc_buf_size(buf) >= OBJSET_PHYS_SIZE_V3) -#define OBJSET_FLAG_USERACCOUNTING_COMPLETE (1ULL<<0) -#define OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE (1ULL<<1) +#define OBJSET_FLAG_USERACCOUNTING_COMPLETE (1ULL << 0) +#define OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE (1ULL << 1) +#define OBJSET_FLAG_PROJECTQUOTA_COMPLETE (1ULL << 2) /* all flags are currently non-portable */ #define OBJSET_CRYPT_PORTABLE_FLAGS_MASK (0) @@ -68,11 +72,14 @@ typedef struct objset_phys { uint64_t os_flags; uint8_t os_portable_mac[ZIO_OBJSET_MAC_LEN]; uint8_t os_local_mac[ZIO_OBJSET_MAC_LEN]; - char os_pad[OBJSET_PHYS_SIZE - sizeof (dnode_phys_t)*3 - + char os_pad0[OBJSET_PHYS_SIZE_V2 - sizeof (dnode_phys_t)*3 - sizeof (zil_header_t) - sizeof (uint64_t)*2 - 2*ZIO_OBJSET_MAC_LEN]; dnode_phys_t os_userused_dnode; dnode_phys_t os_groupused_dnode; + dnode_phys_t os_projectused_dnode; + char os_pad1[OBJSET_PHYS_SIZE_V3 - OBJSET_PHYS_SIZE_V2 - + sizeof (dnode_phys_t)]; } objset_phys_t; typedef int (*dmu_objset_upgrade_cb_t)(objset_t *); @@ -94,6 +101,7 @@ struct objset { dnode_handle_t os_meta_dnode; dnode_handle_t os_userused_dnode; dnode_handle_t os_groupused_dnode; + dnode_handle_t os_projectused_dnode; zilog_t *os_zil; list_node_t os_evicting_node; @@ -143,7 +151,7 @@ struct objset { list_t os_dnodes; list_t os_downgraded_dbufs; - /* Protects changes to DMU_{USER,GROUP}USED_OBJECT */ + /* Protects changes to DMU_{USER,GROUP,PROJECT}USED_OBJECT */ kmutex_t os_userused_lock; /* stuff we store for the user */ @@ -165,6 +173,7 @@ struct objset { #define DMU_META_DNODE(os) ((os)->os_meta_dnode.dnh_dnode) #define DMU_USERUSED_DNODE(os) ((os)->os_userused_dnode.dnh_dnode) #define DMU_GROUPUSED_DNODE(os) ((os)->os_groupused_dnode.dnh_dnode) +#define DMU_PROJECTUSED_DNODE(os) ((os)->os_projectused_dnode.dnh_dnode) #define DMU_OS_IS_L2CACHEABLE(os) \ ((os)->os_secondary_cache == ZFS_CACHE_ALL || \ @@ -215,9 +224,12 @@ int dmu_objset_userspace_upgrade(objset_t *os); boolean_t dmu_objset_userspace_present(objset_t *os); boolean_t dmu_objset_userobjused_enabled(objset_t *os); boolean_t dmu_objset_userobjspace_upgradable(objset_t *os); -void dmu_objset_userobjspace_upgrade(objset_t *os); boolean_t dmu_objset_userobjspace_present(objset_t *os); boolean_t dmu_objset_incompatible_encryption_version(objset_t *os); +boolean_t dmu_objset_projectquota_enabled(objset_t *os); +boolean_t dmu_objset_projectquota_present(objset_t *os); +boolean_t dmu_objset_projectquota_upgradable(objset_t *os); +void dmu_objset_id_quota_upgrade(objset_t *os); int dmu_fsname(const char *snapname, char *buf); diff --git a/include/sys/dnode.h b/include/sys/dnode.h index 691fd443a260..9c44a2232403 100644 --- a/include/sys/dnode.h +++ b/include/sys/dnode.h @@ -147,7 +147,7 @@ enum dnode_dirtycontext { /* Does dnode have a SA spill blkptr in bonus? */ #define DNODE_FLAG_SPILL_BLKPTR (1 << 2) -/* User/Group dnode accounting */ +/* User/Group/Project dnode accounting */ #define DNODE_FLAG_USEROBJUSED_ACCOUNTED (1 << 3) #define DNODE_CRYPT_PORTABLE_FLAGS_MASK (DNODE_FLAG_SPILL_BLKPTR) @@ -356,8 +356,8 @@ struct dnode { /* used in syncing context */ uint64_t dn_oldused; /* old phys used bytes */ uint64_t dn_oldflags; /* old phys dn_flags */ - uint64_t dn_olduid, dn_oldgid; - uint64_t dn_newuid, dn_newgid; + uint64_t dn_olduid, dn_oldgid, dn_oldprojid; + uint64_t dn_newuid, dn_newgid, dn_newprojid; int dn_id_flags; /* holds prefetch structure */ diff --git a/include/sys/dsl_deleg.h b/include/sys/dsl_deleg.h index 153c08f93493..eb95c68e8a9d 100644 --- a/include/sys/dsl_deleg.h +++ b/include/sys/dsl_deleg.h @@ -63,6 +63,10 @@ extern "C" { #define ZFS_DELEG_PERM_BOOKMARK "bookmark" #define ZFS_DELEG_PERM_LOAD_KEY "load-key" #define ZFS_DELEG_PERM_CHANGE_KEY "change-key" +#define ZFS_DELEG_PERM_PROJECTUSED "projectused" +#define ZFS_DELEG_PERM_PROJECTQUOTA "projectquota" +#define ZFS_DELEG_PERM_PROJECTOBJUSED "projectobjused" +#define ZFS_DELEG_PERM_PROJECTOBJQUOTA "projectobjquota" /* * Note: the names of properties that are marked delegatable are also diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 7b86f6631097..88f590276fed 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -192,6 +192,10 @@ typedef enum { ZFS_PROP_USEROBJQUOTA, ZFS_PROP_GROUPOBJUSED, ZFS_PROP_GROUPOBJQUOTA, + ZFS_PROP_PROJECTUSED, + ZFS_PROP_PROJECTQUOTA, + ZFS_PROP_PROJECTOBJUSED, + ZFS_PROP_PROJECTOBJQUOTA, ZFS_NUM_USERQUOTA_PROPS } zfs_userquota_prop_t; diff --git a/include/sys/sa.h b/include/sys/sa.h index b7ed9fe38cbd..50b90622164b 100644 --- a/include/sys/sa.h +++ b/include/sys/sa.h @@ -159,6 +159,7 @@ void sa_handle_unlock(sa_handle_t *); #ifdef _KERNEL int sa_lookup_uio(sa_handle_t *, sa_attr_type_t, uio_t *); +int sa_add_projid(sa_handle_t *, dmu_tx_t *, uint64_t); #endif #ifdef __cplusplus diff --git a/include/sys/xvattr.h b/include/sys/xvattr.h index 4779b632163f..1c919454d8eb 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; + uint64_t xoa_projid; } xoptattr_t; /* @@ -169,11 +171,14 @@ typedef struct xvattr { #define XAT0_GEN 0x00004000 /* object generation number */ #define XAT0_OFFLINE 0x00008000 /* offline */ #define XAT0_SPARSE 0x00010000 /* sparse */ +#define XAT0_PROJINHERIT 0x00020000 /* Create with parent projid */ +#define XAT0_PROJID 0x00040000 /* Project ID */ #define XAT0_ALL_ATTRS (XAT0_CREATETIME|XAT0_ARCHIVE|XAT0_SYSTEM| \ XAT0_READONLY|XAT0_HIDDEN|XAT0_NOUNLINK|XAT0_IMMUTABLE|XAT0_APPENDONLY| \ XAT0_NODUMP|XAT0_OPAQUE|XAT0_AV_QUARANTINED| XAT0_AV_MODIFIED| \ - XAT0_AV_SCANSTAMP|XAT0_REPARSE|XATO_GEN|XAT0_OFFLINE|XAT0_SPARSE) + XAT0_AV_SCANSTAMP|XAT0_REPARSE|XATO_GEN|XAT0_OFFLINE|XAT0_SPARSE| \ + XAT0_PROJINHERIT | XAT0_PROJID) /* Support for XAT_* optional attributes */ #define XVA_MASK 0xffffffff /* Used to mask off 32 bits */ @@ -210,6 +215,8 @@ typedef struct xvattr { #define XAT_GEN ((XAT0_INDEX << XVA_SHFT) | XAT0_GEN) #define XAT_OFFLINE ((XAT0_INDEX << XVA_SHFT) | XAT0_OFFLINE) #define XAT_SPARSE ((XAT0_INDEX << XVA_SHFT) | XAT0_SPARSE) +#define XAT_PROJINHERIT ((XAT0_INDEX << XVA_SHFT) | XAT0_PROJINHERIT) +#define XAT_PROJID ((XAT0_INDEX << XVA_SHFT) | XAT0_PROJID) /* * The returned attribute map array (xva_rtnattrmap[]) is located past the diff --git a/include/sys/zfs_acl.h b/include/sys/zfs_acl.h index 2572fee86306..6d3db5041608 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 *, uint64_t); int zfs_getacl(struct znode *, vsecattr_t *, boolean_t, cred_t *); int zfs_setacl(struct znode *, vsecattr_t *, boolean_t, cred_t *); void zfs_acl_rele(void *); @@ -237,6 +237,7 @@ void zfs_acl_xform(struct znode *, zfs_acl_t *, cred_t *); void zfs_acl_data_locator(void **, uint32_t *, uint32_t, boolean_t, void *); uint64_t zfs_mode_compute(uint64_t, zfs_acl_t *, uint64_t *, uint64_t, uint64_t); +int zfs_acl_node_read(struct znode *, boolean_t, zfs_acl_t **, boolean_t); int zfs_acl_chown_setattr(struct znode *); #endif diff --git a/include/sys/zfs_project.h b/include/sys/zfs_project.h new file mode 100644 index 000000000000..52d5204a692a --- /dev/null +++ b/include/sys/zfs_project.h @@ -0,0 +1,83 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright (c) 2017, Intel Corporation. All rights reserved. + */ + +#ifndef _SYS_ZFS_PROJECT_H +#define _SYS_ZFS_PROJECT_H + +#ifndef _KERNEL +#ifndef _SYS_MOUNT_H +/* XXX: some hack to avoid include sys/mount.h */ +#define _SYS_MOUNT_H +#endif +#endif + +#include <linux/fs.h> + +#ifdef FS_PROJINHERIT_FL +#define ZFS_PROJINHERIT_FL FS_PROJINHERIT_FL +#else +#define ZFS_PROJINHERIT_FL 0x20000000 +#endif + +#ifdef FS_IOC_FSGETXATTR +typedef struct fsxattr zfsxattr_t; + +#define ZFS_IOC_FSGETXATTR FS_IOC_FSGETXATTR +#define ZFS_IOC_FSSETXATTR FS_IOC_FSSETXATTR +#else +struct zfsxattr { + uint32_t fsx_xflags; /* xflags field value (get/set) */ + uint32_t fsx_extsize; /* extsize field value (get/set) */ + uint32_t fsx_nextents; /* nextents field value (get) */ + uint32_t fsx_projid; /* project identifier (get/set) */ + uint32_t fsx_cowextsize; + unsigned char fsx_pad[8]; +}; +typedef struct zfsxattr zfsxattr_t; + +#define ZFS_IOC_FSGETXATTR _IOR('X', 31, zfsxattr_t) +#define ZFS_IOC_FSSETXATTR _IOW('X', 32, zfsxattr_t) +#endif + +#define ZFS_DEFAULT_PROJID (0ULL) +/* + * It is NOT ondisk project ID value. Just means either the object has + * no project ID or the operation does not touch project ID attribute. + */ +#define ZFS_INVALID_PROJID (-1ULL) + +static inline boolean_t +zpl_is_valid_projid(uint32_t projid) +{ + /* + * zfsxattr::fsx_projid is 32-bits, when convert to uint64_t, + * the higher 32-bits will be set as zero, so cannot directly + * compare with ZFS_INVALID_PROJID (-1ULL) + */ + if ((uint32_t)ZFS_INVALID_PROJID == projid) + return (B_FALSE); + return (B_TRUE); +} + +#endif /* _SYS_ZFS_PROJECT_H */ diff --git a/include/sys/zfs_sa.h b/include/sys/zfs_sa.h index 06c4d589aa79..4e6d28638ef6 100644 --- a/include/sys/zfs_sa.h +++ b/include/sys/zfs_sa.h @@ -74,6 +74,7 @@ typedef enum zpl_attr { ZPL_SCANSTAMP, ZPL_DACL_ACES, ZPL_DXATTR, + ZPL_PROJID, ZPL_END } zpl_attr_t; @@ -87,6 +88,8 @@ typedef enum zpl_attr { #define SA_UID_OFFSET 24 #define SA_GID_OFFSET 32 #define SA_PARENT_OFFSET 40 +#define SA_FLAGS_OFFSET 48 +#define SA_PROJID_OFFSET 128 extern sa_attr_reg_t zfs_attr_table[ZPL_END + 1]; extern sa_attr_reg_t zfs_legacy_attr_table[ZPL_END + 1]; diff --git a/include/sys/zfs_vfsops.h b/include/sys/zfs_vfsops.h index 7dbdfd718396..70f0cd50defc 100644 --- a/include/sys/zfs_vfsops.h +++ b/include/sys/zfs_vfsops.h @@ -121,6 +121,8 @@ struct zfsvfs { uint64_t z_groupquota_obj; uint64_t z_userobjquota_obj; uint64_t z_groupobjquota_obj; + uint64_t z_projectquota_obj; + uint64_t z_projectobjquota_obj; uint64_t z_replay_eof; /* New end of file - replay only */ sa_attr_type_t *z_attr_table; /* SA attr mapping->id */ uint64_t z_hold_size; /* znode hold array size */ @@ -195,12 +197,12 @@ extern int zfs_userspace_many(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, uint64_t *cookiep, void *vbuf, uint64_t *bufsizep); extern int zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, const char *domain, uint64_t rid, uint64_t quota); -extern boolean_t zfs_owner_overquota(zfsvfs_t *zfsvfs, struct znode *, - boolean_t isgroup); -extern boolean_t zfs_fuid_overquota(zfsvfs_t *zfsvfs, boolean_t isgroup, - uint64_t fuid); -extern boolean_t zfs_fuid_overobjquota(zfsvfs_t *zfsvfs, boolean_t isgroup, - uint64_t fuid); +extern boolean_t zfs_id_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, + uint64_t id); +extern boolean_t zfs_id_overobjquota(zfsvfs_t *zfsvfs, uint64_t usedobj, + uint64_t id); +extern boolean_t zfs_id_overquota(zfsvfs_t *zfsvfs, uint64_t usedobj, + uint64_t id); extern int zfs_set_version(zfsvfs_t *zfsvfs, uint64_t newvers); extern int zfsvfs_create(const char *name, zfsvfs_t **zfvp); extern int zfsvfs_create_impl(zfsvfs_t **zfvp, zfsvfs_t *zfsvfs, objset_t *os); diff --git a/include/sys/zfs_znode.h b/include/sys/zfs_znode.h index 6a3a3b23313b..311babe59151 100644 --- a/include/sys/zfs_znode.h +++ b/include/sys/zfs_znode.h @@ -42,6 +42,7 @@ #endif #include <sys/zfs_acl.h> #include <sys/zil.h> +#include <sys/zfs_project.h> #ifdef __cplusplus extern "C" { @@ -66,6 +67,18 @@ extern "C" { #define ZFS_OFFLINE 0x0000100000000000ull #define ZFS_SPARSE 0x0000200000000000ull +/* + * PROJINHERIT attribute is used to indicate that the child object under the + * directory which has the PROJINHERIT attribute needs to inherit its parent + * project ID that is used by project quota. + */ +#define ZFS_PROJINHERIT 0x0000400000000000ull + +/* + * PROJID attr is used internally to indicate that the object has project ID. + */ +#define ZFS_PROJID 0x0000800000000000ull + #define ZFS_ATTR_SET(zp, attr, value, pflags, tx) \ { \ if (value) \ @@ -110,6 +123,7 @@ extern "C" { #define SA_ZPL_ZNODE_ACL(z) z->z_attr_table[ZPL_ZNODE_ACL] #define SA_ZPL_DXATTR(z) z->z_attr_table[ZPL_DXATTR] #define SA_ZPL_PAD(z) z->z_attr_table[ZPL_PAD] +#define SA_ZPL_PROJID(z) z->z_attr_table[ZPL_PROJID] /* * Is ID ephemeral? @@ -128,7 +142,7 @@ extern "C" { /* * Special attributes for master node. - * "userquota@" and "groupquota@" are also valid (from + * "userquota@", "groupquota@" and "projectquota@" are also valid (from * zfs_userquota_prop_prefixes[]). */ #define ZFS_FSID "FSID" @@ -196,6 +210,7 @@ typedef struct znode { krwlock_t z_xattr_lock; /* xattr data lock */ nvlist_t *z_xattr_cached; /* cached xattrs */ uint64_t z_xattr_parent; /* parent obj for this xattr */ + uint64_t z_projid; /* project ID */ list_node_t z_link_node; /* all znodes in fs link */ sa_handle_t *z_sa_hdl; /* handle to sa data */ boolean_t z_is_sa; /* are we native sa? */ @@ -212,6 +227,13 @@ typedef struct znode_hold { refcount_t zh_refcount; /* active consumer reference count */ } znode_hold_t; +static inline uint64_t +zfs_inherit_projid(znode_t *dzp) +{ + return ((dzp->z_pflags & ZFS_PROJINHERIT) ? dzp->z_projid : + ZFS_DEFAULT_PROJID); +} + /* * Range locking rules * -------------------- diff --git a/include/zfeature_common.h b/include/zfeature_common.h index d55b46a2297a..3afa64b11dbc 100644 --- a/include/zfeature_common.h +++ b/include/zfeature_common.h @@ -58,6 +58,7 @@ typedef enum spa_feature { SPA_FEATURE_EDONR, SPA_FEATURE_USEROBJ_ACCOUNTING, SPA_FEATURE_ENCRYPTION, + SPA_FEATURE_PROJECT_QUOTA, SPA_FEATURES } spa_feature_t; diff --git a/include/zfs_deleg.h b/include/zfs_deleg.h index deab01131bc4..e18849ebbaf8 100644 --- a/include/zfs_deleg.h +++ b/include/zfs_deleg.h @@ -73,6 +73,10 @@ typedef enum { ZFS_DELEG_NOTE_BOOKMARK, ZFS_DELEG_NOTE_LOAD_KEY, ZFS_DELEG_NOTE_CHANGE_KEY, + ZFS_DELEG_NOTE_PROJECTUSED, + ZFS_DELEG_NOTE_PROJECTQUOTA, + ZFS_DELEG_NOTE_PROJECTOBJUSED, + ZFS_DELEG_NOTE_PROJECTOBJQUOTA, ZFS_DELEG_NOTE_NONE } zfs_deleg_note_t; diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c index 3d47180257b7..1879eb757f33 100644 --- a/lib/libzfs/libzfs_dataset.c +++ b/lib/libzfs/libzfs_dataset.c @@ -1050,7 +1050,9 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, if (uqtype != ZFS_PROP_USERQUOTA && uqtype != ZFS_PROP_GROUPQUOTA && uqtype != ZFS_PROP_USEROBJQUOTA && - uqtype != ZFS_PROP_GROUPOBJQUOTA) { + uqtype != ZFS_PROP_GROUPOBJQUOTA && + uqtype != ZFS_PROP_PROJECTQUOTA && + uqtype != ZFS_PROP_PROJECTOBJQUOTA) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "'%s' is readonly"), propname); @@ -1075,7 +1077,7 @@ zfs_valid_proplist(libzfs_handle_t *hdl, zfs_type_t type, nvlist_t *nvl, if (intval == 0) { zfs_error_aux(hdl, dgettext(TEXT_DOMAIN, "use 'none' to disable " - "userquota/groupquota")); + "{user|group|project}quota")); goto error; } } else { @@ -3007,6 +3009,8 @@ 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, @@ -3016,12 +3020,13 @@ userquota_propname_decode(const char *propname, boolean_t zoned, char *cp; boolean_t isuser; boolean_t isgroup; + boolean_t isproject; struct passwd *pw; struct group *gr; domain[0] = '\0'; - /* Figure out the property type ({user|group}{quota|space}) */ + /* Figure out the property type ({user|group|project}{quota|space}) */ for (type = 0; type < ZFS_NUM_USERQUOTA_PROPS; type++) { if (strncmp(propname, zfs_userquota_prop_prefixes[type], strlen(zfs_userquota_prop_prefixes[type])) == 0) @@ -3037,6 +3042,9 @@ userquota_propname_decode(const char *propname, boolean_t zoned, isgroup = (type == ZFS_PROP_GROUPQUOTA || type == ZFS_PROP_GROUPUSED || type == ZFS_PROP_GROUPOBJQUOTA || type == ZFS_PROP_GROUPOBJUSED); + isproject = (type == ZFS_PROP_PROJECTQUOTA || + type == ZFS_PROP_PROJECTUSED || type == ZFS_PROP_PROJECTOBJQUOTA || + type == ZFS_PROP_PROJECTOBJUSED); cp = strchr(propname, '@') + 1; @@ -3048,7 +3056,7 @@ userquota_propname_decode(const char *propname, boolean_t zoned, if (zoned && getzoneid() == GLOBAL_ZONEID) return (ENOENT); *ridp = gr->gr_gid; - } else if (strchr(cp, '@')) { + } else if (!isproject && strchr(cp, '@')) { #ifdef HAVE_IDMAP /* * It's a SID name (eg "user@domain") that needs to be @@ -3089,13 +3097,13 @@ userquota_propname_decode(const char *propname, boolean_t zoned, return (ENOSYS); #endif /* HAVE_IDMAP */ } else { - /* It's a user/group ID (eg "12345"). */ + /* It's a user/group/project ID (eg "12345"). */ uid_t id; char *end; id = strtoul(cp, &end, 10); if (*end != '\0') return (EINVAL); - if (id > MAXUID) { + if (id > MAXUID && !isproject) { #ifdef HAVE_IDMAP /* It's an ephemeral ID. */ idmap_rid_t rid; @@ -3170,10 +3178,12 @@ zfs_prop_get_userquota(zfs_handle_t *zhp, const char *propname, (u_longlong_t)propvalue); } else if (propvalue == 0 && (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_GROUPQUOTA || - type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA)) { + type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA || + type == ZFS_PROP_PROJECTQUOTA || ZFS_PROP_PROJECTOBJQUOTA)) { (void) strlcpy(propbuf, "none", proplen); } else if (type == ZFS_PROP_USERQUOTA || type == ZFS_PROP_GROUPQUOTA || - type == ZFS_PROP_USERUSED || type == ZFS_PROP_GROUPUSED) { + type == ZFS_PROP_USERUSED || type == ZFS_PROP_GROUPUSED || + type == ZFS_PROP_PROJECTUSED || type == ZFS_PROP_PROJECTQUOTA) { zfs_nicebytes(propvalue, propbuf, proplen); } else { zfs_nicenum(propvalue, propbuf, proplen); @@ -4728,7 +4738,11 @@ zfs_userspace(zfs_handle_t *zhp, zfs_userquota_prop_t type, (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED || type == ZFS_PROP_USEROBJQUOTA || - type == ZFS_PROP_GROUPOBJQUOTA))) + type == ZFS_PROP_GROUPOBJQUOTA || + type == ZFS_PROP_PROJECTOBJUSED || + type == ZFS_PROP_PROJECTOBJQUOTA || + type == ZFS_PROP_PROJECTUSED || + type == ZFS_PROP_PROJECTQUOTA))) break; (void) snprintf(errbuf, sizeof (errbuf), diff --git a/man/man5/zpool-features.5 b/man/man5/zpool-features.5 index 72a6c57b127a..523fd1fd09cc 100644 --- a/man/man5/zpool-features.5 +++ b/man/man5/zpool-features.5 @@ -640,5 +640,39 @@ are destroyed. .RE +.sp +.ne 2 +.na +\fB\fBproject_quota\fR\fR +.ad +.RS 4n +.TS +l l . +GUID org.zfsonlinux:project_quota +READ\-ONLY COMPATIBLE yes +DEPENDENCIES extensible_dataset +.TE + +This feature allows administrators to account the spaces and objects usage +information against the project identifier (ID). + +The project ID is new object-based attribute. When upgrading an existing +filesystem, object without project ID attribute will be assigned a zero +project ID. After this feature is enabled, newly created object will inherit +its parent directory's project ID if the parent inherit flag is set (via +\fBchattr +/-P\fR or \fBzfs project [-s|-C]\fR). Otherwise, the new object's +project ID will be set as zero. An object's project ID can be changed at +anytime by the owner (or privileged user) via \fBchattr -p $prjid\fR or +\fBzfs project -p $prjid\fR. + +This feature will become \fBactive\fR as soon as it is enabled and will never +return to being \fBdisabled\fR. Each filesystem will be upgraded automatically +when remounted or when new file is created under that filesystem. The upgrade +can also be triggered on filesystems via `zfs set version=current <pool/fs>`. +The upgrade process runs in the background and may take a while to complete +for the filesystems containing a large number of files. + +.RE + .SH "SEE ALSO" \fBzpool\fR(8) diff --git a/man/man8/zfs.8 b/man/man8/zfs.8 index 2e1ffc6ea728..f42851328506 100644 --- a/man/man8/zfs.8 +++ b/man/man8/zfs.8 @@ -145,6 +145,34 @@ .Oo Fl t Ar type Ns Oo , Ns Ar type Oc Ns ... Oc .Ar filesystem Ns | Ns Ar snapshot .Nm +.Cm projectspace +.Op Fl Hp +.Oo Fl o Ar field Ns Oo , Ns Ar field Oc Ns ... Oc +.Oo Fl s Ar field Oc Ns ... +.Oo Fl S Ar field Oc Ns ... +.Ar filesystem Ns | Ns Ar snapshot +.Nm +.Cm project +.Oo Fl d Ns | Ns Fl r Ns Oc +.Ar file Ns | Ns Ar directory Ns ... +.Nm +.Cm project +.Fl C +.Oo Fl kr Ns Oc +.Ar file Ns | Ns Ar directory Ns ... +.Nm +.Cm project +.Fl c +.Oo Fl 0 Ns Oc +.Oo Fl d Ns | Ns Fl r Ns Oc +.Op Fl p Ar id +.Ar file Ns | Ns Ar directory Ns ... +.Nm +.Cm project +.Op Fl p Ar id +.Oo Fl rs Ns Oc +.Ar file Ns | Ns Ar directory Ns ... +.Nm .Cm mount .Nm .Cm mount @@ -905,6 +933,56 @@ The root user, or a user who has been granted the privilege with .Nm zfs Cm allow , can access all groups' usage. +.It Sy projectused Ns @ Ns Em project +The amount of space consumed by the specified project in this dataset. Project +is identified via the project identifier (ID) that is object-based numeral +attribute. An object can inherit the project ID from its parent object (if the +parent has the flag of inherit project ID that can be set and changed via +.Nm chattr Fl /+P +or +.Nm zfs project Fl s ) +when being created. The privileged user can set and change object's project +ID via +.Nm chattr Fl p +or +.Nm zfs project Fl s +anytime. Space is charged to the project of each file, as displayed by +.Nm lsattr Fl p +or +.Nm zfs project . +See the +.Sy userused Ns @ Ns Em user +property for more information. +.Pp +The root user, or a user who has been granted the +.Sy projectused +privilege with +.Nm zfs allow , +can access all projects' usage. +.It Sy projectobjused Ns @ Ns Em project +The +.Sy projectobjused +is similar to +.Sy projectused +but instead it counts the number of objects consumed by project. When the +property +.Sy xattr=on +is set on a fileset, ZFS will create additional objects per-file to store +extended attributes. These additional objects are reflected in the +.Sy projectobjused +value and are counted against the project's +.Sy projectobjquota . +When a filesystem is configured to use +.Sy xattr=sa +no additional internal objects are required. See the +.Sy userobjused Ns @ Ns Em user +property for more information. +.Pp +The root user, or a user who has been granted the +.Sy projectobjused +privilege with +.Nm zfs allow , +can access all projects' objects usage. .It Sy volblocksize For volumes, specifies the block size of the volume. The @@ -1566,6 +1644,27 @@ is similar to but it limits number of objects a group can consume. Please refer to .Sy userobjused for more information about how objects are counted. +.It Sy projectquota@ Ns Em project Ns = Ns Em size Ns | Ns Sy none +Limits the amount of space consumed by the specified project. Project +space consumption is identified by the +.Sy projectused@ Ns Em project +property. Please refer to +.Sy projectused +for more information about how project is identified and set/changed. +.Pp +The root user, or a user who has been granted the +.Sy projectquota +privilege with +.Nm zfs allow , +can access all projects' quota. +.It Sy projectobjquota@ Ns Em project Ns = Ns Em size Ns | Ns Sy none +The +.Sy projectobjquota +is similar to +.Sy projectquota +but it limits number of objects a project can consume. Please refer to +.Sy userobjused +for more information about how objects are counted. .It Sy readonly Ns = Ns Sy on Ns | Ns Sy off Controls whether this dataset can be modified. The default value is @@ -3000,6 +3099,114 @@ except that the default types to display are .Fl t Sy posixgroup Ns \&, Ns Sy smbgroup . .It Xo .Nm +.Cm projectspace +.Op Fl Hp +.Oo Fl o Ar field Ns Oo , Ns Ar field Oc Ns ... Oc +.Oo Fl s Ar field Oc Ns ... +.Oo Fl S Ar field Oc Ns ... +.Ar filesystem Ns | Ns Ar snapshot +.Xc +Displays space consumed by, and quotas on, each project in the specified +filesystem or snapshot. This subcommand is identical to +.Nm zfs Cm userspace , +except that the project identifier is numeral, not name. So need neither +the option +.Sy -i +for SID to POSIX ID nor +.Sy -n +for numeric ID, nor +.Sy -t +for types. +.It Xo +.Nm +.Cm project +.Oo Fl d Ns | Ns Fl r Ns Oc +.Ar file Ns | Ns Ar directory Ns ... +.Xc +List project identifier (ID) and inherit flag of file(s) or directories. +.Bl -tag -width "-d" +.It Fl d +Show the directory project ID and inherit flag, not its childrens. It will +overwrite the former specified +.Fl r +option. +.It Fl r +Show on subdirectories recursively. It will overwrite the former specified +.Fl d +option. +.El +.It Xo +.Nm +.Cm project +.Fl C +.Oo Fl kr Ns Oc +.Ar file Ns | Ns Ar directory Ns ... +.Xc +Clear project inherit flag and/or ID on the file(s) or directories. +.Bl -tag -width "-k" +.It Fl k +Keep the project ID unchanged. If not specified, the project ID will be reset +as zero. +.It Fl r +Clear on subdirectories recursively. +.El +.It Xo +.Nm +.Cm project +.Fl c +.Oo Fl 0 Ns Oc +.Oo Fl d Ns | Ns Fl r Ns Oc +.Op Fl p Ar id +.Ar file Ns | Ns Ar directory Ns ... +.Xc +Check project ID and inherit flag on the file(s) or directories, report the +entries without project inherit flag or with different project IDs from the +specified (via +.Fl p +option) value or the target directory's project ID. +.Bl -tag -width "-0" +.It Fl 0 +Print file name with a trailing NUL instead of newline (by default), like +"find -print0". +.It Fl d +Check the directory project ID and inherit flag, not its childrens. It will +overwrite the former specified +.Fl r +option. +.It Fl p +Specify the referenced ID for comparing with the target file(s) or directories' +project IDs. If not specified, the target (top) directory's project ID will be +used as the referenced one. +.It Fl r +Check on subdirectories recursively. It will overwrite the former specified +.Fl d +option. +.El +.It Xo +.Nm +.Cm project +.Op Fl p Ar id +.Oo Fl rs Ns Oc +.Ar file Ns | Ns Ar directory Ns ... +.Xc +.Bl -tag -width "-p" +Set project ID and/or inherit flag on the file(s) or directories. +.It Fl p +Set the file(s)' or directories' project ID with the given value. +.It Fl r +Set on subdirectories recursively. +.It Fl s +Set project inherit flag on the given file(s) or directories. It is usually used +for setup tree quota on the directory target with +.Fl r +option specified together. When setup tree quota, by default the directory's +project ID will be set to all its descendants unless you specify the project +ID via +.Fl p +option explicitly. +.El +.It Xo +.Nm .Cm mount .Xc Displays all ZFS file systems currently mounted. @@ -3812,6 +4019,11 @@ userprop other Allows changing any user property userquota other Allows accessing any userquota@... property userused other Allows reading any userused@... property +projectobjquota other Allows accessing any projectobjquota@... + property +projectquota other Allows accessing any projectquota@... property +projectobjused other Allows reading any projectobjused@... property +projectused other Allows reading any projectused@... property aclinherit property acltype property diff --git a/module/zcommon/zfeature_common.c b/module/zcommon/zfeature_common.c index 7b782b45d614..36d0d9613fc7 100644 --- a/module/zcommon/zfeature_common.c +++ b/module/zcommon/zfeature_common.c @@ -321,6 +321,18 @@ zpool_feature_init(void) "Support for dataset level encryption", ZFEATURE_FLAG_PER_DATASET, encryption_deps); } + + { + static const spa_feature_t project_quota_deps[] = { + SPA_FEATURE_EXTENSIBLE_DATASET, + SPA_FEATURE_NONE + }; + zfeature_register(SPA_FEATURE_PROJECT_QUOTA, + "org.zfsonlinux:project_quota", "project_quota", + "space/object accounting based on project ID.", + ZFEATURE_FLAG_READONLY_COMPAT | ZFEATURE_FLAG_PER_DATASET, + project_quota_deps); + } } #if defined(_KERNEL) && defined(HAVE_SPL) diff --git a/module/zcommon/zfs_deleg.c b/module/zcommon/zfs_deleg.c index 18e5c11cce03..3a51bc49aa9c 100644 --- a/module/zcommon/zfs_deleg.c +++ b/module/zcommon/zfs_deleg.c @@ -71,6 +71,10 @@ zfs_deleg_perm_tab_t zfs_deleg_perm_tab[] = { {ZFS_DELEG_PERM_RELEASE}, {ZFS_DELEG_PERM_LOAD_KEY}, {ZFS_DELEG_PERM_CHANGE_KEY}, + {ZFS_DELEG_PERM_PROJECTUSED}, + {ZFS_DELEG_PERM_PROJECTQUOTA}, + {ZFS_DELEG_PERM_PROJECTOBJUSED}, + {ZFS_DELEG_PERM_PROJECTOBJQUOTA}, {NULL} }; diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c index 42af9468c0e2..0d44fd139b7a 100644 --- a/module/zcommon/zfs_prop.c +++ b/module/zcommon/zfs_prop.c @@ -58,7 +58,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 3668ea31533c..51cb0c982f49 100644 --- a/module/zfs/dbuf.c +++ b/module/zfs/dbuf.c @@ -2456,7 +2456,7 @@ dbuf_destroy(dmu_buf_impl_t *db) /* * Note: While bpp will always be updated if the function returns success, * parentp will not be updated if the dnode does not have dn_dbuf filled in; - * this happens when the dnode is the meta-dnode, or a userused or groupused + * this happens when the dnode is the meta-dnode, or {user|group|project}used * object. */ __attribute__((always_inline)) diff --git a/module/zfs/dmu.c b/module/zfs/dmu.c index 20ed3ebffcac..cb86800f4bb3 100644 --- a/module/zfs/dmu.c +++ b/module/zfs/dmu.c @@ -113,8 +113,8 @@ const dmu_object_type_info_t dmu_ot[DMU_OT_NUMTYPES] = { { DMU_BSWAP_UINT64, TRUE, FALSE, "FUID table size" }, { DMU_BSWAP_ZAP, TRUE, FALSE, "DSL dataset next clones"}, { DMU_BSWAP_ZAP, TRUE, FALSE, "scan work queue" }, - { DMU_BSWAP_ZAP, TRUE, TRUE, "ZFS user/group used" }, - { DMU_BSWAP_ZAP, TRUE, TRUE, "ZFS user/group quota" }, + { DMU_BSWAP_ZAP, TRUE, TRUE, "ZFS user/group/project used" }, + { DMU_BSWAP_ZAP, TRUE, TRUE, "ZFS user/group/project quota"}, { DMU_BSWAP_ZAP, TRUE, FALSE, "snapshot refcount tags"}, { DMU_BSWAP_ZAP, TRUE, FALSE, "DDT ZAP algorithm" }, { DMU_BSWAP_ZAP, TRUE, FALSE, "DDT statistics" }, diff --git a/module/zfs/dmu_objset.c b/module/zfs/dmu_objset.c index befce9be6bc8..0d9273fbbfb1 100644 --- a/module/zfs/dmu_objset.c +++ b/module/zfs/dmu_objset.c @@ -58,6 +58,7 @@ #include <sys/policy.h> #include <sys/spa_impl.h> #include <sys/dmu_send.h> +#include <sys/zfs_project.h> /* * Needed to close a window in dnode_move() that allows the objset to be freed @@ -336,14 +337,17 @@ dmu_objset_byteswap(void *buf, size_t size) { objset_phys_t *osp = buf; - ASSERT(size == OBJSET_OLD_PHYS_SIZE || size == sizeof (objset_phys_t)); + ASSERT(size == OBJSET_PHYS_SIZE_V1 || size == OBJSET_PHYS_SIZE_V2 || + size == sizeof (objset_phys_t)); dnode_byteswap(&osp->os_meta_dnode); byteswap_uint64_array(&osp->os_zil_header, sizeof (zil_header_t)); osp->os_type = BSWAP_64(osp->os_type); osp->os_flags = BSWAP_64(osp->os_flags); - if (size == sizeof (objset_phys_t)) { + if (size >= OBJSET_PHYS_SIZE_V2) { dnode_byteswap(&osp->os_userused_dnode); dnode_byteswap(&osp->os_groupused_dnode); + if (size >= sizeof (objset_phys_t)) + dnode_byteswap(&osp->os_projectused_dnode); } } @@ -395,6 +399,7 @@ dmu_objset_open_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, if (!BP_IS_HOLE(os->os_rootbp)) { arc_flags_t aflags = ARC_FLAG_WAIT; zbookmark_phys_t zb; + int size; enum zio_flag zio_flags = ZIO_FLAG_CANFAIL; SET_BOOKMARK(&zb, ds ? ds->ds_object : DMU_META_OBJSET, ZB_ROOT_OBJECT, ZB_ROOT_LEVEL, ZB_ROOT_BLKID); @@ -420,12 +425,19 @@ dmu_objset_open_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, return (err); } + if (spa_version(spa) < SPA_VERSION_USERSPACE) + size = OBJSET_PHYS_SIZE_V1; + else if (!spa_feature_is_enabled(spa, + SPA_FEATURE_PROJECT_QUOTA)) + size = OBJSET_PHYS_SIZE_V2; + else + size = sizeof (objset_phys_t); + /* Increase the blocksize if we are permitted. */ - if (spa_version(spa) >= SPA_VERSION_USERSPACE && - arc_buf_size(os->os_phys_buf) < sizeof (objset_phys_t)) { + if (arc_buf_size(os->os_phys_buf) < size) { arc_buf_t *buf = arc_alloc_buf(spa, &os->os_phys_buf, - ARC_BUFC_METADATA, sizeof (objset_phys_t)); - bzero(buf->b_data, sizeof (objset_phys_t)); + ARC_BUFC_METADATA, size); + bzero(buf->b_data, size); bcopy(os->os_phys_buf->b_data, buf->b_data, arc_buf_size(os->os_phys_buf)); arc_buf_destroy(os->os_phys_buf, &os->os_phys_buf); @@ -436,7 +448,7 @@ dmu_objset_open_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, os->os_flags = os->os_phys->os_flags; } else { int size = spa_version(spa) >= SPA_VERSION_USERSPACE ? - sizeof (objset_phys_t) : OBJSET_OLD_PHYS_SIZE; + sizeof (objset_phys_t) : OBJSET_PHYS_SIZE_V1; os->os_phys_buf = arc_alloc_buf(spa, &os->os_phys_buf, ARC_BUFC_METADATA, size); os->os_phys = os->os_phys_buf->b_data; @@ -568,11 +580,15 @@ dmu_objset_open_impl(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, dnode_special_open(os, &os->os_phys->os_meta_dnode, DMU_META_DNODE_OBJECT, &os->os_meta_dnode); - if (arc_buf_size(os->os_phys_buf) >= sizeof (objset_phys_t)) { + if (OBJSET_BUF_HAS_USERUSED(os->os_phys_buf)) { dnode_special_open(os, &os->os_phys->os_userused_dnode, DMU_USERUSED_OBJECT, &os->os_userused_dnode); dnode_special_open(os, &os->os_phys->os_groupused_dnode, DMU_GROUPUSED_OBJECT, &os->os_groupused_dnode); + if (OBJSET_BUF_HAS_PROJECTUSED(os->os_phys_buf)) + dnode_special_open(os, + &os->os_phys->os_projectused_dnode, + DMU_PROJECTUSED_OBJECT, &os->os_projectused_dnode); } mutex_init(&os->os_upgrade_lock, NULL, MUTEX_DEFAULT, NULL); @@ -711,9 +727,10 @@ dmu_objset_own(const char *name, dmu_objset_type_t type, } /* user accounting requires the dataset to be decrypted */ - if (dmu_objset_userobjspace_upgradable(*osp) && + if ((dmu_objset_userobjspace_upgradable(*osp) || + dmu_objset_projectquota_upgradable(*osp)) && (ds->ds_dir->dd_crypto_obj == 0 || decrypt)) - dmu_objset_userobjspace_upgrade(*osp); + dmu_objset_id_quota_upgrade(*osp); dsl_pool_rele(dp, FTAG); return (0); @@ -835,6 +852,8 @@ dmu_objset_evict_dbufs(objset_t *os) kmem_free(dn_marker, sizeof (dnode_t)); if (DMU_USERUSED_DNODE(os) != NULL) { + if (DMU_PROJECTUSED_DNODE(os) != NULL) + dnode_evict_dbufs(DMU_PROJECTUSED_DNODE(os)); dnode_evict_dbufs(DMU_GROUPUSED_DNODE(os)); dnode_evict_dbufs(DMU_USERUSED_DNODE(os)); } @@ -889,6 +908,8 @@ dmu_objset_evict_done(objset_t *os) dnode_special_close(&os->os_meta_dnode); if (DMU_USERUSED_DNODE(os)) { + if (DMU_PROJECTUSED_DNODE(os)) + dnode_special_close(&os->os_projectused_dnode); dnode_special_close(&os->os_userused_dnode); dnode_special_close(&os->os_groupused_dnode); } @@ -1004,6 +1025,12 @@ dmu_objset_create_impl_dnstats(spa_t *spa, dsl_dataset_t *ds, blkptr_t *bp, os->os_phys->os_flags |= OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE; } + if (dmu_objset_projectquota_enabled(os)) { + ds->ds_feature_activation_needed[ + SPA_FEATURE_PROJECT_QUOTA] = B_TRUE; + os->os_phys->os_flags |= + OBJSET_FLAG_PROJECTQUOTA_COMPLETE; + } os->os_flags = os->os_phys->os_flags; } @@ -1408,7 +1435,7 @@ dmu_objset_write_ready(zio_t *zio, arc_buf_t *abuf, void *arg) * Update rootbp fill count: it should be the number of objects * allocated in the object set (not counting the "special" * objects that are stored in the objset_phys_t -- the meta - * dnode and user/group accounting objects). + * dnode and user/group/project accounting objects). */ for (int i = 0; i < dnp->dn_nblkptr; i++) fill += BP_GET_FILL(&dnp->dn_blkptr[i]); @@ -1537,6 +1564,12 @@ dmu_objset_sync(objset_t *os, zio_t *pio, dmu_tx_t *tx) dnode_sync(DMU_GROUPUSED_DNODE(os), tx); } + if (DMU_PROJECTUSED_DNODE(os) && + DMU_PROJECTUSED_DNODE(os)->dn_type != DMU_OT_NONE) { + DMU_PROJECTUSED_DNODE(os)->dn_zio = zio; + dnode_sync(DMU_PROJECTUSED_DNODE(os), tx); + } + txgoff = tx->tx_txg & TXG_MASK; if (dmu_objset_userused_enabled(os) && @@ -1620,6 +1653,14 @@ dmu_objset_userobjused_enabled(objset_t *os) spa_feature_is_enabled(os->os_spa, SPA_FEATURE_USEROBJ_ACCOUNTING)); } +boolean_t +dmu_objset_projectquota_enabled(objset_t *os) +{ + return (used_cbs[os->os_phys->os_type] != NULL && + DMU_PROJECTUSED_DNODE(os) != NULL && + spa_feature_is_enabled(os->os_spa, SPA_FEATURE_PROJECT_QUOTA)); +} + typedef struct userquota_node { /* must be in the first filed, see userquota_update_cache() */ char uqn_id[20 + DMU_OBJACCT_PREFIX_LEN]; @@ -1630,6 +1671,7 @@ typedef struct userquota_node { typedef struct userquota_cache { avl_tree_t uqc_user_deltas; avl_tree_t uqc_group_deltas; + avl_tree_t uqc_project_deltas; } userquota_cache_t; static int @@ -1682,6 +1724,19 @@ do_userquota_cacheflush(objset_t *os, userquota_cache_t *cache, dmu_tx_t *tx) kmem_free(uqn, sizeof (*uqn)); } avl_destroy(&cache->uqc_group_deltas); + + if (dmu_objset_projectquota_enabled(os)) { + cookie = NULL; + while ((uqn = avl_destroy_nodes(&cache->uqc_project_deltas, + &cookie)) != NULL) { + mutex_enter(&os->os_userused_lock); + VERIFY0(zap_increment(os, DMU_PROJECTUSED_OBJECT, + uqn->uqn_id, uqn->uqn_delta, tx)); + mutex_exit(&os->os_userused_lock); + kmem_free(uqn, sizeof (*uqn)); + } + avl_destroy(&cache->uqc_project_deltas); + } } static void @@ -1706,10 +1761,11 @@ userquota_update_cache(avl_tree_t *avl, const char *id, int64_t delta) } static void -do_userquota_update(userquota_cache_t *cache, uint64_t used, uint64_t flags, - uint64_t user, uint64_t group, boolean_t subtract) +do_userquota_update(objset_t *os, userquota_cache_t *cache, uint64_t used, + uint64_t flags, uint64_t user, uint64_t group, uint64_t project, + boolean_t subtract) { - if ((flags & DNODE_FLAG_USERUSED_ACCOUNTED)) { + if (flags & DNODE_FLAG_USERUSED_ACCOUNTED) { int64_t delta = DNODE_MIN_SIZE + used; char name[20]; @@ -1721,12 +1777,18 @@ do_userquota_update(userquota_cache_t *cache, uint64_t used, uint64_t flags, (void) sprintf(name, "%llx", (longlong_t)group); userquota_update_cache(&cache->uqc_group_deltas, name, delta); + + if (dmu_objset_projectquota_enabled(os)) { + (void) sprintf(name, "%llx", (longlong_t)project); + userquota_update_cache(&cache->uqc_project_deltas, + name, delta); + } } } static void -do_userobjquota_update(userquota_cache_t *cache, uint64_t flags, - uint64_t user, uint64_t group, boolean_t subtract) +do_userobjquota_update(objset_t *os, userquota_cache_t *cache, uint64_t flags, + uint64_t user, uint64_t group, uint64_t project, boolean_t subtract) { if (flags & DNODE_FLAG_USEROBJUSED_ACCOUNTED) { char name[20 + DMU_OBJACCT_PREFIX_LEN]; @@ -1739,6 +1801,13 @@ do_userobjquota_update(userquota_cache_t *cache, uint64_t flags, (void) snprintf(name, sizeof (name), DMU_OBJACCT_PREFIX "%llx", (longlong_t)group); userquota_update_cache(&cache->uqc_group_deltas, name, delta); + + if (dmu_objset_projectquota_enabled(os)) { + (void) snprintf(name, sizeof (name), + DMU_OBJACCT_PREFIX "%llx", (longlong_t)project); + userquota_update_cache(&cache->uqc_project_deltas, + name, delta); + } } } @@ -1766,6 +1835,10 @@ userquota_updates_task(void *arg) sizeof (userquota_node_t), offsetof(userquota_node_t, uqn_node)); avl_create(&cache.uqc_group_deltas, userquota_compare, sizeof (userquota_node_t), offsetof(userquota_node_t, uqn_node)); + if (dmu_objset_projectquota_enabled(os)) + avl_create(&cache.uqc_project_deltas, userquota_compare, + sizeof (userquota_node_t), offsetof(userquota_node_t, + uqn_node)); while ((dn = multilist_sublist_head(list)) != NULL) { int flags; @@ -1777,18 +1850,21 @@ userquota_updates_task(void *arg) flags = dn->dn_id_flags; ASSERT(flags); if (flags & DN_ID_OLD_EXIST) { - do_userquota_update(&cache, - dn->dn_oldused, dn->dn_oldflags, - dn->dn_olduid, dn->dn_oldgid, B_TRUE); - do_userobjquota_update(&cache, dn->dn_oldflags, - dn->dn_olduid, dn->dn_oldgid, B_TRUE); + do_userquota_update(os, &cache, dn->dn_oldused, + dn->dn_oldflags, dn->dn_olduid, dn->dn_oldgid, + dn->dn_oldprojid, B_TRUE); + do_userobjquota_update(os, &cache, dn->dn_oldflags, + dn->dn_olduid, dn->dn_oldgid, + dn->dn_oldprojid, B_TRUE); } if (flags & DN_ID_NEW_EXIST) { - do_userquota_update(&cache, + do_userquota_update(os, &cache, DN_USED_BYTES(dn->dn_phys), dn->dn_phys->dn_flags, - dn->dn_newuid, dn->dn_newgid, B_FALSE); - do_userobjquota_update(&cache, dn->dn_phys->dn_flags, - dn->dn_newuid, dn->dn_newgid, B_FALSE); + dn->dn_newuid, dn->dn_newgid, + dn->dn_newprojid, B_FALSE); + do_userobjquota_update(os, &cache, + dn->dn_phys->dn_flags, dn->dn_newuid, dn->dn_newgid, + dn->dn_newprojid, B_FALSE); } mutex_enter(&dn->dn_mtx); @@ -1797,6 +1873,7 @@ userquota_updates_task(void *arg) if (dn->dn_id_flags & DN_ID_NEW_EXIST) { dn->dn_olduid = dn->dn_newuid; dn->dn_oldgid = dn->dn_newgid; + dn->dn_oldprojid = dn->dn_newprojid; dn->dn_id_flags |= DN_ID_OLD_EXIST; if (dn->dn_bonuslen == 0) dn->dn_id_flags |= DN_ID_CHKED_SPILL; @@ -1824,7 +1901,7 @@ dmu_objset_do_userquota_updates(objset_t *os, dmu_tx_t *tx) if (os->os_encrypted && dmu_objset_is_receiving(os)) return; - /* Allocate the user/groupused objects if necessary. */ + /* Allocate the user/group/project used objects if necessary. */ if (DMU_USERUSED_DNODE(os)->dn_type == DMU_OT_NONE) { VERIFY0(zap_create_claim(os, DMU_USERUSED_OBJECT, @@ -1834,6 +1911,12 @@ dmu_objset_do_userquota_updates(objset_t *os, dmu_tx_t *tx) DMU_OT_USERGROUP_USED, DMU_OT_NONE, 0, tx)); } + if (dmu_objset_projectquota_enabled(os) && + DMU_PROJECTUSED_DNODE(os)->dn_type == DMU_OT_NONE) { + VERIFY0(zap_create_claim(os, DMU_PROJECTUSED_OBJECT, + DMU_OT_USERGROUP_USED, DMU_OT_NONE, 0, tx)); + } + for (int i = 0; i < multilist_get_num_sublists(os->os_synced_dnodes); i++) { userquota_updates_arg_t *uua = @@ -1896,6 +1979,7 @@ dmu_objset_userquota_get_ids(dnode_t *dn, boolean_t before, dmu_tx_t *tx) dmu_buf_impl_t *db = NULL; uint64_t *user = NULL; uint64_t *group = NULL; + uint64_t *project = NULL; int flags = dn->dn_id_flags; int error; boolean_t have_spill = B_FALSE; @@ -1953,9 +2037,11 @@ dmu_objset_userquota_get_ids(dnode_t *dn, boolean_t before, dmu_tx_t *tx) ASSERT(data); user = &dn->dn_olduid; group = &dn->dn_oldgid; + project = &dn->dn_oldprojid; } else if (data) { user = &dn->dn_newuid; group = &dn->dn_newgid; + project = &dn->dn_newprojid; } /* @@ -1963,7 +2049,7 @@ dmu_objset_userquota_get_ids(dnode_t *dn, boolean_t before, dmu_tx_t *tx) * type has changed and that type isn't an object type to track */ error = used_cbs[os->os_phys->os_type](dn->dn_bonustype, data, - user, group); + user, group, project); /* * Preserve existing uid/gid when the callback can't determine @@ -1976,9 +2062,11 @@ dmu_objset_userquota_get_ids(dnode_t *dn, boolean_t before, dmu_tx_t *tx) if (flags & DN_ID_OLD_EXIST) { dn->dn_newuid = dn->dn_olduid; dn->dn_newgid = dn->dn_oldgid; + dn->dn_newgid = dn->dn_oldprojid; } else { dn->dn_newuid = 0; dn->dn_newgid = 0; + dn->dn_newprojid = ZFS_DEFAULT_PROJID; } error = 0; } @@ -2016,6 +2104,13 @@ dmu_objset_userobjspace_present(objset_t *os) OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE); } +boolean_t +dmu_objset_projectquota_present(objset_t *os) +{ + return (os->os_phys->os_flags & + OBJSET_FLAG_PROJECTQUOTA_COMPLETE); +} + static int dmu_objset_space_upgrade(objset_t *os) { @@ -2085,33 +2180,43 @@ dmu_objset_userspace_upgrade(objset_t *os) } static int -dmu_objset_userobjspace_upgrade_cb(objset_t *os) +dmu_objset_id_quota_upgrade_cb(objset_t *os) { int err = 0; - if (dmu_objset_userobjspace_present(os)) + if (dmu_objset_userobjspace_present(os) && + dmu_objset_projectquota_present(os)) return (0); if (dmu_objset_is_snapshot(os)) return (SET_ERROR(EINVAL)); if (!dmu_objset_userobjused_enabled(os)) return (SET_ERROR(ENOTSUP)); + if (!dmu_objset_projectquota_enabled(os) && + dmu_objset_userobjspace_present(os)) + return (SET_ERROR(ENOTSUP)); dmu_objset_ds(os)->ds_feature_activation_needed[ SPA_FEATURE_USEROBJ_ACCOUNTING] = B_TRUE; + if (dmu_objset_projectquota_enabled(os)) + dmu_objset_ds(os)->ds_feature_activation_needed[ + SPA_FEATURE_PROJECT_QUOTA] = B_TRUE; err = dmu_objset_space_upgrade(os); if (err) return (err); os->os_flags |= OBJSET_FLAG_USEROBJACCOUNTING_COMPLETE; + if (dmu_objset_projectquota_enabled(os)) + os->os_flags |= OBJSET_FLAG_PROJECTQUOTA_COMPLETE; + txg_wait_synced(dmu_objset_pool(os), 0); return (0); } void -dmu_objset_userobjspace_upgrade(objset_t *os) +dmu_objset_id_quota_upgrade(objset_t *os) { - dmu_objset_upgrade(os, dmu_objset_userobjspace_upgrade_cb); + dmu_objset_upgrade(os, dmu_objset_id_quota_upgrade_cb); } boolean_t @@ -2123,6 +2228,15 @@ dmu_objset_userobjspace_upgradable(objset_t *os) !dmu_objset_userobjspace_present(os)); } +boolean_t +dmu_objset_projectquota_upgradable(objset_t *os) +{ + return (dmu_objset_type(os) == DMU_OST_ZFS && + !dmu_objset_is_snapshot(os) && + dmu_objset_projectquota_enabled(os) && + !dmu_objset_projectquota_present(os)); +} + void dmu_objset_space(objset_t *os, uint64_t *refdbytesp, uint64_t *availbytesp, uint64_t *usedobjsp, uint64_t *availobjsp) @@ -2731,7 +2845,10 @@ EXPORT_SYMBOL(dmu_objset_userused_enabled); EXPORT_SYMBOL(dmu_objset_userspace_upgrade); EXPORT_SYMBOL(dmu_objset_userspace_present); EXPORT_SYMBOL(dmu_objset_userobjused_enabled); -EXPORT_SYMBOL(dmu_objset_userobjspace_upgrade); EXPORT_SYMBOL(dmu_objset_userobjspace_upgradable); EXPORT_SYMBOL(dmu_objset_userobjspace_present); +EXPORT_SYMBOL(dmu_objset_projectquota_enabled); +EXPORT_SYMBOL(dmu_objset_projectquota_present); +EXPORT_SYMBOL(dmu_objset_projectquota_upgradable); +EXPORT_SYMBOL(dmu_objset_id_quota_upgrade); #endif diff --git a/module/zfs/dmu_traverse.c b/module/zfs/dmu_traverse.c index 15d29198fb7e..5407e4817292 100644 --- a/module/zfs/dmu_traverse.c +++ b/module/zfs/dmu_traverse.c @@ -386,7 +386,11 @@ traverse_visitbp(traverse_data_t *td, const dnode_phys_t *dnp, if (osp->os_meta_dnode.dn_maxblkid == 0) td->td_realloc_possible = B_FALSE; - if (arc_buf_size(buf) >= sizeof (objset_phys_t)) { + if (OBJSET_BUF_HAS_USERUSED(buf)) { + if (OBJSET_BUF_HAS_PROJECTUSED(buf)) + prefetch_dnode_metadata(td, + &osp->os_projectused_dnode, + zb->zb_objset, DMU_PROJECTUSED_OBJECT); prefetch_dnode_metadata(td, &osp->os_groupused_dnode, zb->zb_objset, DMU_GROUPUSED_OBJECT); prefetch_dnode_metadata(td, &osp->os_userused_dnode, @@ -395,13 +399,19 @@ traverse_visitbp(traverse_data_t *td, const dnode_phys_t *dnp, err = traverse_dnode(td, &osp->os_meta_dnode, zb->zb_objset, DMU_META_DNODE_OBJECT); - if (err == 0 && arc_buf_size(buf) >= sizeof (objset_phys_t)) { - err = traverse_dnode(td, &osp->os_groupused_dnode, - zb->zb_objset, DMU_GROUPUSED_OBJECT); - } - if (err == 0 && arc_buf_size(buf) >= sizeof (objset_phys_t)) { - err = traverse_dnode(td, &osp->os_userused_dnode, - zb->zb_objset, DMU_USERUSED_OBJECT); + if (err == 0 && OBJSET_BUF_HAS_USERUSED(buf)) { + if (OBJSET_BUF_HAS_PROJECTUSED(buf)) + err = traverse_dnode(td, + &osp->os_projectused_dnode, zb->zb_objset, + DMU_PROJECTUSED_OBJECT); + if (err == 0) + err = traverse_dnode(td, + &osp->os_groupused_dnode, zb->zb_objset, + DMU_GROUPUSED_OBJECT); + if (err == 0) + err = traverse_dnode(td, + &osp->os_userused_dnode, zb->zb_objset, + DMU_USERUSED_OBJECT); } } diff --git a/module/zfs/dnode.c b/module/zfs/dnode.c index b4c131e98323..596983b47acd 100644 --- a/module/zfs/dnode.c +++ b/module/zfs/dnode.c @@ -38,6 +38,7 @@ #include <sys/dmu_zfetch.h> #include <sys/range_tree.h> #include <sys/trace_dnode.h> +#include <sys/zfs_project.h> dnode_stats_t dnode_stats = { { "dnode_hold_dbuf_hold", KSTAT_DATA_UINT64 }, @@ -157,8 +158,10 @@ dnode_cons(void *arg, void *unused, int kmflag) dn->dn_oldflags = 0; dn->dn_olduid = 0; dn->dn_oldgid = 0; + dn->dn_oldprojid = ZFS_DEFAULT_PROJID; dn->dn_newuid = 0; dn->dn_newgid = 0; + dn->dn_newprojid = ZFS_DEFAULT_PROJID; dn->dn_id_flags = 0; dn->dn_dbufs_count = 0; @@ -210,8 +213,10 @@ dnode_dest(void *arg, void *unused) ASSERT0(dn->dn_oldflags); ASSERT0(dn->dn_olduid); ASSERT0(dn->dn_oldgid); + ASSERT0(dn->dn_oldprojid); ASSERT0(dn->dn_newuid); ASSERT0(dn->dn_newgid); + ASSERT0(dn->dn_newprojid); ASSERT0(dn->dn_id_flags); ASSERT0(dn->dn_dbufs_count); @@ -543,8 +548,10 @@ dnode_destroy(dnode_t *dn) dn->dn_oldflags = 0; dn->dn_olduid = 0; dn->dn_oldgid = 0; + dn->dn_oldprojid = ZFS_DEFAULT_PROJID; dn->dn_newuid = 0; dn->dn_newgid = 0; + dn->dn_newprojid = ZFS_DEFAULT_PROJID; dn->dn_id_flags = 0; dmu_zfetch_fini(&dn->dn_zfetch); @@ -799,8 +806,10 @@ dnode_move_impl(dnode_t *odn, dnode_t *ndn) ndn->dn_oldflags = odn->dn_oldflags; ndn->dn_olduid = odn->dn_olduid; ndn->dn_oldgid = odn->dn_oldgid; + ndn->dn_oldprojid = odn->dn_oldprojid; ndn->dn_newuid = odn->dn_newuid; ndn->dn_newgid = odn->dn_newgid; + ndn->dn_newprojid = odn->dn_newprojid; ndn->dn_id_flags = odn->dn_id_flags; dmu_zfetch_init(&ndn->dn_zfetch, NULL); list_move_tail(&ndn->dn_zfetch.zf_stream, &odn->dn_zfetch.zf_stream); @@ -859,8 +868,10 @@ dnode_move_impl(dnode_t *odn, dnode_t *ndn) odn->dn_oldflags = 0; odn->dn_olduid = 0; odn->dn_oldgid = 0; + odn->dn_oldprojid = ZFS_DEFAULT_PROJID; odn->dn_newuid = 0; odn->dn_newgid = 0; + odn->dn_newprojid = ZFS_DEFAULT_PROJID; odn->dn_id_flags = 0; /* @@ -1265,9 +1276,14 @@ dnode_hold_impl(objset_t *os, uint64_t object, int flag, int slots, (spa_is_root(os->os_spa) && spa_config_held(os->os_spa, SCL_STATE, RW_WRITER))); - if (object == DMU_USERUSED_OBJECT || object == DMU_GROUPUSED_OBJECT) { - dn = (object == DMU_USERUSED_OBJECT) ? - DMU_USERUSED_DNODE(os) : DMU_GROUPUSED_DNODE(os); + if (object == DMU_USERUSED_OBJECT || object == DMU_GROUPUSED_OBJECT || + object == DMU_PROJECTUSED_OBJECT) { + if (object == DMU_USERUSED_OBJECT) + dn = DMU_USERUSED_DNODE(os); + else if (object == DMU_GROUPUSED_OBJECT) + dn = DMU_GROUPUSED_DNODE(os); + else + dn = DMU_PROJECTUSED_DNODE(os); if (dn == NULL) return (SET_ERROR(ENOENT)); type = dn->dn_type; diff --git a/module/zfs/dsl_pool.c b/module/zfs/dsl_pool.c index 86863fad8719..db2e67742e3c 100644 --- a/module/zfs/dsl_pool.c +++ b/module/zfs/dsl_pool.c @@ -580,7 +580,7 @@ dsl_pool_sync(dsl_pool_t *dp, uint64_t txg) /* * After the data blocks have been written (ensured by the zio_wait() - * above), update the user/group space accounting. This happens + * above), update the user/group/project space accounting. This happens * in tasks dispatched to dp_sync_taskq, so wait for them before * continuing. */ diff --git a/module/zfs/dsl_scan.c b/module/zfs/dsl_scan.c index fc0c24e1c315..776f8f239566 100644 --- a/module/zfs/dsl_scan.c +++ b/module/zfs/dsl_scan.c @@ -1684,11 +1684,15 @@ dsl_scan_recurse(dsl_scan_t *scn, dsl_dataset_t *ds, dmu_objset_type_t ostype, if (OBJSET_BUF_HAS_USERUSED(buf)) { /* - * We also always visit user/group accounting + * We also always visit user/group/project accounting * objects, and never skip them, even if we are * suspending. This is necessary so that the * space deltas from this txg get integrated. */ + if (OBJSET_BUF_HAS_PROJECTUSED(buf)) + dsl_scan_visitdnode(scn, ds, osp->os_type, + &osp->os_projectused_dnode, + DMU_PROJECTUSED_OBJECT, tx); dsl_scan_visitdnode(scn, ds, osp->os_type, &osp->os_groupused_dnode, DMU_GROUPUSED_OBJECT, tx); diff --git a/module/zfs/sa.c b/module/zfs/sa.c index f0a18bad8dc4..4a863f9a5645 100644 --- a/module/zfs/sa.c +++ b/module/zfs/sa.c @@ -44,6 +44,10 @@ #include <sys/errno.h> #include <sys/zfs_context.h> +#ifdef _KERNEL +#include <sys/zfs_znode.h> +#endif + /* * ZFS System attributes: * @@ -1456,8 +1460,9 @@ sa_lookup_impl(sa_handle_t *hdl, sa_bulk_attr_t *bulk, int count) return (sa_attr_op(hdl, bulk, count, SA_LOOKUP, NULL)); } -int -sa_lookup(sa_handle_t *hdl, sa_attr_type_t attr, void *buf, uint32_t buflen) +static int +sa_lookup_locked(sa_handle_t *hdl, sa_attr_type_t attr, void *buf, + uint32_t buflen) { int error; sa_bulk_attr_t bulk; @@ -1470,9 +1475,19 @@ sa_lookup(sa_handle_t *hdl, sa_attr_type_t attr, void *buf, uint32_t buflen) bulk.sa_data_func = NULL; ASSERT(hdl); - mutex_enter(&hdl->sa_lock); error = sa_lookup_impl(hdl, &bulk, 1); + return (error); +} + +int +sa_lookup(sa_handle_t *hdl, sa_attr_type_t attr, void *buf, uint32_t buflen) +{ + int error; + + mutex_enter(&hdl->sa_lock); + error = sa_lookup_locked(hdl, attr, buf, buflen); mutex_exit(&hdl->sa_lock); + return (error); } @@ -1497,6 +1512,173 @@ sa_lookup_uio(sa_handle_t *hdl, sa_attr_type_t attr, uio_t *uio) mutex_exit(&hdl->sa_lock); return (error); } + +/* + * For the existed object that is upgraded from old system, its ondisk layout + * has no slot for the project ID attribute. But quota accounting logic needs + * to access related slots by offset directly. So we need to adjust these old + * objects' layout to make the project ID to some unified and fixed offset. + */ +int +sa_add_projid(sa_handle_t *hdl, dmu_tx_t *tx, uint64_t projid) +{ + znode_t *zp = sa_get_userdata(hdl); + dmu_buf_t *db = sa_get_db(hdl); + zfsvfs_t *zfsvfs = ZTOZSB(zp); + int count = 0, err = 0; + sa_bulk_attr_t *bulk, *attrs; + zfs_acl_locator_cb_t locate = { 0 }; + uint64_t uid, gid, mode, rdev, xattr = 0, parent, gen, links; + uint64_t crtime[2], mtime[2], ctime[2], atime[2]; + zfs_acl_phys_t znode_acl = { 0 }; + char scanstamp[AV_SCANSTAMP_SZ]; + + if (zp->z_acl_cached == NULL) { + zfs_acl_t *aclp; + + mutex_enter(&zp->z_acl_lock); + err = zfs_acl_node_read(zp, B_FALSE, &aclp, B_FALSE); + mutex_exit(&zp->z_acl_lock); + if (err != 0 && err != ENOENT) + return (err); + } + + bulk = kmem_zalloc(sizeof (sa_bulk_attr_t) * ZPL_END, KM_SLEEP); + attrs = kmem_zalloc(sizeof (sa_bulk_attr_t) * ZPL_END, KM_SLEEP); + mutex_enter(&hdl->sa_lock); + mutex_enter(&zp->z_lock); + + err = sa_lookup_locked(hdl, SA_ZPL_PROJID(zfsvfs), &projid, + sizeof (uint64_t)); + if (unlikely(err == 0)) + /* Someone has added project ID attr by race. */ + err = EEXIST; + if (err != ENOENT) + goto out; + + /* First do a bulk query of the attributes that aren't cached */ + if (zp->z_is_sa) { + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MODE(zfsvfs), NULL, + &mode, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GEN(zfsvfs), NULL, + &gen, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_UID(zfsvfs), NULL, + &uid, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GID(zfsvfs), NULL, + &gid, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_PARENT(zfsvfs), NULL, + &parent, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ATIME(zfsvfs), NULL, + &atime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, + &mtime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, + &ctime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CRTIME(zfsvfs), NULL, + &crtime, 16); + if (S_ISBLK(ZTOI(zp)->i_mode) || S_ISCHR(ZTOI(zp)->i_mode)) + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_RDEV(zfsvfs), NULL, + &rdev, 8); + } else { + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ATIME(zfsvfs), NULL, + &atime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, + &mtime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, + &ctime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CRTIME(zfsvfs), NULL, + &crtime, 16); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GEN(zfsvfs), NULL, + &gen, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MODE(zfsvfs), NULL, + &mode, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_PARENT(zfsvfs), NULL, + &parent, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_XATTR(zfsvfs), NULL, + &xattr, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_RDEV(zfsvfs), NULL, + &rdev, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_UID(zfsvfs), NULL, + &uid, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GID(zfsvfs), NULL, + &gid, 8); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ZNODE_ACL(zfsvfs), NULL, + &znode_acl, 88); + } + err = sa_bulk_lookup_locked(hdl, bulk, count); + if (err != 0) + goto out; + + err = sa_lookup_locked(hdl, SA_ZPL_XATTR(zfsvfs), &xattr, 8); + if (err != 0 && err != ENOENT) + goto out; + + zp->z_projid = projid; + zp->z_pflags |= ZFS_PROJID; + links = ZTOI(zp)->i_nlink; + count = 0; + err = 0; + + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_MODE(zfsvfs), NULL, &mode, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_SIZE(zfsvfs), NULL, + &zp->z_size, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_GEN(zfsvfs), NULL, &gen, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_UID(zfsvfs), NULL, &uid, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_GID(zfsvfs), NULL, &gid, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_PARENT(zfsvfs), NULL, &parent, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_FLAGS(zfsvfs), NULL, + &zp->z_pflags, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_ATIME(zfsvfs), NULL, &atime, 16); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_MTIME(zfsvfs), NULL, &mtime, 16); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_CTIME(zfsvfs), NULL, &ctime, 16); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_CRTIME(zfsvfs), NULL, + &crtime, 16); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_LINKS(zfsvfs), NULL, &links, 8); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_PROJID(zfsvfs), NULL, &projid, 8); + + if (S_ISBLK(ZTOI(zp)->i_mode) || S_ISCHR(ZTOI(zp)->i_mode)) + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_RDEV(zfsvfs), NULL, + &rdev, 8); + + if (zp->z_acl_cached != NULL) { + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_DACL_COUNT(zfsvfs), NULL, + &zp->z_acl_cached->z_acl_count, 8); + if (zp->z_acl_cached->z_version < ZFS_ACL_VERSION_FUID) + zfs_acl_xform(zp, zp->z_acl_cached, CRED()); + locate.cb_aclp = zp->z_acl_cached; + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_DACL_ACES(zfsvfs), + zfs_acl_data_locator, &locate, + zp->z_acl_cached->z_acl_bytes); + } + + if (xattr) + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_XATTR(zfsvfs), NULL, + &xattr, 8); + + if (zp->z_pflags & ZFS_BONUS_SCANSTAMP) { + bcopy((caddr_t)db->db_data + ZFS_OLD_ZNODE_PHYS_SIZE, + scanstamp, AV_SCANSTAMP_SZ); + SA_ADD_BULK_ATTR(attrs, count, SA_ZPL_SCANSTAMP(zfsvfs), NULL, + scanstamp, AV_SCANSTAMP_SZ); + zp->z_pflags &= ~ZFS_BONUS_SCANSTAMP; + } + + VERIFY(dmu_set_bonustype(db, DMU_OT_SA, tx) == 0); + VERIFY(sa_replace_all_by_template_locked(hdl, attrs, count, tx) == 0); + if (znode_acl.z_acl_extern_obj) { + VERIFY(0 == dmu_object_free(zfsvfs->z_os, + znode_acl.z_acl_extern_obj, tx)); + } + + zp->z_is_sa = B_TRUE; + +out: + mutex_exit(&zp->z_lock); + mutex_exit(&hdl->sa_lock); + kmem_free(attrs, sizeof (sa_bulk_attr_t) * ZPL_END); + kmem_free(bulk, sizeof (sa_bulk_attr_t) * ZPL_END); + return (err); +} #endif static sa_idx_tab_t * @@ -2062,4 +2244,5 @@ EXPORT_SYMBOL(sa_hdrsize); EXPORT_SYMBOL(sa_handle_lock); EXPORT_SYMBOL(sa_handle_unlock); EXPORT_SYMBOL(sa_lookup_uio); +EXPORT_SYMBOL(sa_add_projid); #endif /* _KERNEL */ diff --git a/module/zfs/spa.c b/module/zfs/spa.c index dac04246460f..736b51feae1e 100644 --- a/module/zfs/spa.c +++ b/module/zfs/spa.c @@ -1188,7 +1188,7 @@ spa_activate(spa_t *spa, int mode) /* * The taskq to upgrade datasets in this pool. Currently used by - * feature SPA_FEATURE_USEROBJ_ACCOUNTING. + * feature SPA_FEATURE_USEROBJ_ACCOUNTING/SPA_FEATURE_PROJECT_QUOTA. */ spa->spa_upgrade_taskq = taskq_create("z_upgrade", boot_ncpus, defclsyspri, 1, INT_MAX, TASKQ_DYNAMIC); diff --git a/module/zfs/zfs_acl.c b/module/zfs/zfs_acl.c index 5ef20f08826d..b366e8f1cbee 100644 --- a/module/zfs/zfs_acl.c +++ b/module/zfs/zfs_acl.c @@ -1054,8 +1054,8 @@ zfs_mode_compute(uint64_t fmode, zfs_acl_t *aclp, * Read an external acl object. If the intent is to modify, always * create a new acl and leave any cached acl in place. */ -static int -zfs_acl_node_read(znode_t *zp, boolean_t have_lock, zfs_acl_t **aclpp, +int +zfs_acl_node_read(struct znode *zp, boolean_t have_lock, zfs_acl_t **aclpp, boolean_t will_modify) { zfs_acl_t *aclp; @@ -1883,12 +1883,12 @@ zfs_acl_ids_free(zfs_acl_ids_t *acl_ids) } boolean_t -zfs_acl_ids_overquota(zfsvfs_t *zfsvfs, zfs_acl_ids_t *acl_ids) +zfs_acl_ids_overquota(zfsvfs_t *zv, zfs_acl_ids_t *acl_ids, uint64_t projid) { - return (zfs_fuid_overquota(zfsvfs, B_FALSE, acl_ids->z_fuid) || - zfs_fuid_overquota(zfsvfs, B_TRUE, acl_ids->z_fgid) || - zfs_fuid_overobjquota(zfsvfs, B_FALSE, acl_ids->z_fuid) || - zfs_fuid_overobjquota(zfsvfs, B_TRUE, acl_ids->z_fgid)); + return (zfs_id_overquota(zv, DMU_USERUSED_OBJECT, acl_ids->z_fuid) || + zfs_id_overquota(zv, DMU_GROUPUSED_OBJECT, acl_ids->z_fgid) || + (projid != ZFS_DEFAULT_PROJID && projid != ZFS_INVALID_PROJID && + zfs_id_overquota(zv, DMU_PROJECTUSED_OBJECT, projid))); } /* diff --git a/module/zfs/zfs_dir.c b/module/zfs/zfs_dir.c index 6398a1d155e2..7eb426b78119 100644 --- a/module/zfs/zfs_dir.c +++ b/module/zfs/zfs_dir.c @@ -1036,7 +1036,7 @@ zfs_make_xattrdir(znode_t *zp, vattr_t *vap, struct inode **xipp, cred_t *cr) if ((error = zfs_acl_ids_create(zp, IS_XATTR, vap, cr, NULL, &acl_ids)) != 0) return (error); - if (zfs_acl_ids_overquota(zfsvfs, &acl_ids)) { + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, zp->z_projid)) { zfs_acl_ids_free(&acl_ids); return (SET_ERROR(EDQUOT)); } diff --git a/module/zfs/zfs_ioctl.c b/module/zfs/zfs_ioctl.c index fcd2fca128d3..6bee042a05a6 100644 --- a/module/zfs/zfs_ioctl.c +++ b/module/zfs/zfs_ioctl.c @@ -260,10 +260,14 @@ static const char *userquota_perms[] = { ZFS_DELEG_PERM_USEROBJQUOTA, ZFS_DELEG_PERM_GROUPOBJUSED, ZFS_DELEG_PERM_GROUPOBJQUOTA, + ZFS_DELEG_PERM_PROJECTUSED, + ZFS_DELEG_PERM_PROJECTQUOTA, + ZFS_DELEG_PERM_PROJECTOBJUSED, + ZFS_DELEG_PERM_PROJECTOBJQUOTA, }; static int zfs_ioc_userspace_upgrade(zfs_cmd_t *zc); -static int zfs_ioc_userobjspace_upgrade(zfs_cmd_t *zc); +static int zfs_ioc_id_quota_upgrade(zfs_cmd_t *zc); static int zfs_check_settable(const char *name, nvpair_t *property, cred_t *cr); static int zfs_check_clearable(char *dataset, nvlist_t *props, @@ -1200,10 +1204,14 @@ zfs_secpolicy_userspace_one(zfs_cmd_t *zc, nvlist_t *innvl, cred_t *cr) zc->zc_objset_type == ZFS_PROP_USEROBJQUOTA) { if (zc->zc_guid == crgetuid(cr)) return (0); - } else { + } else if (zc->zc_objset_type == ZFS_PROP_GROUPUSED || + zc->zc_objset_type == ZFS_PROP_GROUPQUOTA || + zc->zc_objset_type == ZFS_PROP_GROUPOBJUSED || + zc->zc_objset_type == ZFS_PROP_GROUPOBJQUOTA) { if (groupmember(zc->zc_guid, cr)) return (0); } + /* else is for project quota/used */ } return (zfs_secpolicy_write_perms(zc->zc_name, @@ -2516,7 +2524,7 @@ zfs_prop_set_special(const char *dsname, zprop_source_t source, zc = kmem_zalloc(sizeof (zfs_cmd_t), KM_SLEEP); (void) strcpy(zc->zc_name, dsname); (void) zfs_ioc_userspace_upgrade(zc); - (void) zfs_ioc_userobjspace_upgrade(zc); + (void) zfs_ioc_id_quota_upgrade(zc); kmem_free(zc, sizeof (zfs_cmd_t)); } break; @@ -3897,6 +3905,10 @@ zfs_check_settable(const char *dsname, nvpair_t *pair, cred_t *cr) zfs_userquota_prop_prefixes[ZFS_PROP_USEROBJQUOTA]; const char *giq_prefix = zfs_userquota_prop_prefixes[ZFS_PROP_GROUPOBJQUOTA]; + const char *pq_prefix = + zfs_userquota_prop_prefixes[ZFS_PROP_PROJECTQUOTA]; + const char *piq_prefix = zfs_userquota_prop_prefixes[\ + ZFS_PROP_PROJECTOBJQUOTA]; if (strncmp(propname, uq_prefix, strlen(uq_prefix)) == 0) { @@ -3910,8 +3922,14 @@ zfs_check_settable(const char *dsname, nvpair_t *pair, cred_t *cr) } else if (strncmp(propname, giq_prefix, strlen(giq_prefix)) == 0) { perm = ZFS_DELEG_PERM_GROUPOBJQUOTA; + } else if (strncmp(propname, pq_prefix, + strlen(pq_prefix)) == 0) { + perm = ZFS_DELEG_PERM_PROJECTQUOTA; + } else if (strncmp(propname, piq_prefix, + strlen(piq_prefix)) == 0) { + perm = ZFS_DELEG_PERM_PROJECTOBJQUOTA; } else { - /* USERUSED and GROUPUSED are read-only */ + /* {USER|GROUP|PROJECT}USED are read-only */ return (SET_ERROR(EINVAL)); } @@ -5180,7 +5198,7 @@ zfs_ioc_promote(zfs_cmd_t *zc) } /* - * Retrieve a single {user|group}{used|quota}@... property. + * Retrieve a single {user|group|project}{used|quota}@... property. * * inputs: * zc_name name of filesystem @@ -5306,7 +5324,7 @@ zfs_ioc_userspace_upgrade(zfs_cmd_t *zc) * none */ static int -zfs_ioc_userobjspace_upgrade(zfs_cmd_t *zc) +zfs_ioc_id_quota_upgrade(zfs_cmd_t *zc) { objset_t *os; int error; @@ -5315,14 +5333,15 @@ zfs_ioc_userobjspace_upgrade(zfs_cmd_t *zc) if (error != 0) return (error); - if (dmu_objset_userobjspace_upgradable(os)) { + if (dmu_objset_userobjspace_upgradable(os) || + dmu_objset_projectquota_upgradable(os)) { mutex_enter(&os->os_upgrade_lock); if (os->os_upgrade_id == 0) { /* clear potential error code and retry */ os->os_upgrade_status = 0; mutex_exit(&os->os_upgrade_lock); - dmu_objset_userobjspace_upgrade(os); + dmu_objset_id_quota_upgrade(os); } else { mutex_exit(&os->os_upgrade_lock); } diff --git a/module/zfs/zfs_log.c b/module/zfs/zfs_log.c index 8887f037aa34..ce7b84927e1e 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 (uint64_t)); + } if (XVA_ISSET_REQ(xvap, XAT_REPARSE)) *attrs |= (xoap->xoa_reparse == 0) ? 0 : XAT0_REPARSE; @@ -177,6 +186,9 @@ zfs_log_xvattr(lr_attr_t *lrattr, xvattr_t *xvap) if (XVA_ISSET_REQ(xvap, XAT_SPARSE)) *attrs |= (xoap->xoa_sparse == 0) ? 0 : XAT0_SPARSE; + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) + *attrs |= (xoap->xoa_projinherit == 0) ? 0 : + XAT0_PROJINHERIT; } static void * diff --git a/module/zfs/zfs_replay.c b/module/zfs/zfs_replay.c index bfba2fea0292..e2ff0078935f 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 (uint64_t)); + } if (XVA_ISSET_REQ(xvap, XAT_REPARSE)) xoap->xoa_reparse = ((*attrs & XAT0_REPARSE) != 0); if (XVA_ISSET_REQ(xvap, XAT_OFFLINE)) xoap->xoa_offline = ((*attrs & XAT0_OFFLINE) != 0); if (XVA_ISSET_REQ(xvap, XAT_SPARSE)) xoap->xoa_sparse = ((*attrs & XAT0_SPARSE) != 0); + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) + xoap->xoa_projinherit = ((*attrs & XAT0_PROJINHERIT) != 0); } static int diff --git a/module/zfs/zfs_sa.c b/module/zfs/zfs_sa.c index 3eff6acc6f3e..bd21ba896cc3 100644 --- a/module/zfs/zfs_sa.c +++ b/module/zfs/zfs_sa.c @@ -27,6 +27,8 @@ #include <sys/sa.h> #include <sys/zfs_acl.h> #include <sys/zfs_sa.h> +#include <sys/dmu_objset.h> +#include <sys/sa_impl.h> /* * ZPL attribute registration table. @@ -63,6 +65,7 @@ sa_attr_reg_t zfs_attr_table[ZPL_END+1] = { {"ZPL_SCANSTAMP", 32, SA_UINT8_ARRAY, 0}, {"ZPL_DACL_ACES", 0, SA_ACL, 0}, {"ZPL_DXATTR", 0, SA_UINT8_ARRAY, 0}, + {"ZPL_PROJID", sizeof (uint64_t), SA_UINT64_ARRAY, 0}, {NULL, 0, 0, 0} }; @@ -317,7 +320,7 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) } /* First do a bulk query of the attributes that aren't cached */ - bulk = kmem_alloc(sizeof (sa_bulk_attr_t) * 20, KM_SLEEP); + bulk = kmem_alloc(sizeof (sa_bulk_attr_t) * ZPL_END, KM_SLEEP); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ATIME(zfsvfs), NULL, &atime, 16); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, &mtime, 16); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, &ctime, 16); @@ -332,9 +335,13 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_ZNODE_ACL(zfsvfs), NULL, &znode_acl, 88); - if (sa_bulk_lookup_locked(hdl, bulk, count) != 0) { - kmem_free(bulk, sizeof (sa_bulk_attr_t) * 20); + if (sa_bulk_lookup_locked(hdl, bulk, count) != 0) goto done; + + if (dmu_objset_projectquota_enabled(hdl->sa_os) && + !(zp->z_pflags & ZFS_PROJID)) { + zp->z_pflags |= ZFS_PROJID; + zp->z_projid = ZFS_DEFAULT_PROJID; } /* @@ -342,7 +349,7 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) * it is such a way to pick up an already existing layout number */ count = 0; - sa_attrs = kmem_zalloc(sizeof (sa_bulk_attr_t) * 20, KM_SLEEP); + sa_attrs = kmem_zalloc(sizeof (sa_bulk_attr_t) * ZPL_END, KM_SLEEP); SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_MODE(zfsvfs), NULL, &mode, 8); SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_SIZE(zfsvfs), NULL, &zp->z_size, 8); @@ -365,6 +372,9 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) links = ZTOI(zp)->i_nlink; SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_LINKS(zfsvfs), NULL, &links, 8); + if (dmu_objset_projectquota_enabled(hdl->sa_os)) + SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_PROJID(zfsvfs), NULL, + &zp->z_projid, 8); if (S_ISBLK(ZTOI(zp)->i_mode) || S_ISCHR(ZTOI(zp)->i_mode)) SA_ADD_BULK_ATTR(sa_attrs, count, SA_ZPL_RDEV(zfsvfs), NULL, &rdev, 8); @@ -400,9 +410,9 @@ zfs_sa_upgrade(sa_handle_t *hdl, dmu_tx_t *tx) znode_acl.z_acl_extern_obj, tx)); zp->z_is_sa = B_TRUE; - kmem_free(sa_attrs, sizeof (sa_bulk_attr_t) * 20); - kmem_free(bulk, sizeof (sa_bulk_attr_t) * 20); + kmem_free(sa_attrs, sizeof (sa_bulk_attr_t) * ZPL_END); done: + kmem_free(bulk, sizeof (sa_bulk_attr_t) * ZPL_END); if (drop_lock) mutex_exit(&zp->z_lock); } diff --git a/module/zfs/zfs_vfsops.c b/module/zfs/zfs_vfsops.c index bb380c920cbc..971fd54bc9dd 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,49 @@ zfs_space_delta_cb(dmu_object_type_t bonustype, void *data, znode_phys_t *znp = data; *userp = znp->zp_uid; *groupp = znp->zp_gid; + *projectp = ZFS_DEFAULT_PROJID; + return (0); + } + + if (sap->sa_magic == 0) { + /* + * This should only happen for newly created files + * that haven't had the znode data filled in yet. + */ + *userp = 0; + *groupp = 0; + *projectp = ZFS_DEFAULT_PROJID; + return (0); + } + + sa = *sap; + if (sa.sa_magic == BSWAP_32(SA_MAGIC)) { + sa.sa_magic = SA_MAGIC; + sa.sa_layout_info = BSWAP_16(sa.sa_layout_info); + swap = B_TRUE; } else { - int hdrsize; - sa_hdr_phys_t *sap = data; - sa_hdr_phys_t sa = *sap; - boolean_t swap = B_FALSE; - - ASSERT(bonustype == DMU_OT_SA); - - if (sa.sa_magic == 0) { - /* - * This should only happen for newly created - * files that haven't had the znode data filled - * in yet. - */ - *userp = 0; - *groupp = 0; - return (0); - } - if (sa.sa_magic == BSWAP_32(SA_MAGIC)) { - sa.sa_magic = SA_MAGIC; - sa.sa_layout_info = BSWAP_16(sa.sa_layout_info); - swap = B_TRUE; - } else { - VERIFY3U(sa.sa_magic, ==, SA_MAGIC); - } + VERIFY3U(sa.sa_magic, ==, SA_MAGIC); + } - hdrsize = sa_hdrsize(&sa); - VERIFY3U(hdrsize, >=, sizeof (sa_hdr_phys_t)); - *userp = *((uint64_t *)((uintptr_t)data + hdrsize + - SA_UID_OFFSET)); - *groupp = *((uint64_t *)((uintptr_t)data + hdrsize + - SA_GID_OFFSET)); - if (swap) { - *userp = BSWAP_64(*userp); - *groupp = BSWAP_64(*groupp); - } + hdrsize = sa_hdrsize(&sa); + VERIFY3U(hdrsize, >=, sizeof (sa_hdr_phys_t)); + + *userp = *((uint64_t *)((uintptr_t)data + hdrsize + SA_UID_OFFSET)); + *groupp = *((uint64_t *)((uintptr_t)data + hdrsize + SA_GID_OFFSET)); + flags = *((uint64_t *)((uintptr_t)data + hdrsize + SA_FLAGS_OFFSET)); + if (swap) + flags = BSWAP_64(flags); + + if (flags & ZFS_PROJID) + *projectp = *((uint64_t *)((uintptr_t)data + hdrsize + + SA_PROJID_OFFSET)); + else + *projectp = ZFS_DEFAULT_PROJID; + + if (swap) { + *userp = BSWAP_64(*userp); + *groupp = BSWAP_64(*groupp); + *projectp = BSWAP_64(*projectp); } return (0); } @@ -624,6 +637,9 @@ zfs_userquota_prop_to_obj(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type) case ZFS_PROP_GROUPUSED: case ZFS_PROP_GROUPOBJUSED: return (DMU_GROUPUSED_OBJECT); + case ZFS_PROP_PROJECTUSED: + case ZFS_PROP_PROJECTOBJUSED: + return (DMU_PROJECTUSED_OBJECT); case ZFS_PROP_USERQUOTA: return (zfsvfs->z_userquota_obj); case ZFS_PROP_GROUPQUOTA: @@ -632,6 +648,10 @@ zfs_userquota_prop_to_obj(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type) return (zfsvfs->z_userobjquota_obj); case ZFS_PROP_GROUPOBJQUOTA: return (zfsvfs->z_groupobjquota_obj); + case ZFS_PROP_PROJECTQUOTA: + return (zfsvfs->z_projectquota_obj); + case ZFS_PROP_PROJECTOBJQUOTA: + return (zfsvfs->z_projectobjquota_obj); default: return (ZFS_NO_OBJECT); } @@ -651,8 +671,16 @@ zfs_userspace_many(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, if (!dmu_objset_userspace_present(zfsvfs->z_os)) return (SET_ERROR(ENOTSUP)); + if ((type == ZFS_PROP_PROJECTQUOTA || type == ZFS_PROP_PROJECTUSED || + type == ZFS_PROP_PROJECTOBJQUOTA || + type == ZFS_PROP_PROJECTOBJUSED) && + !dmu_objset_projectquota_present(zfsvfs->z_os)) + return (SET_ERROR(ENOTSUP)); + if ((type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED || - type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA) && + type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA || + type == ZFS_PROP_PROJECTOBJUSED || + type == ZFS_PROP_PROJECTOBJQUOTA) && !dmu_objset_userobjspace_present(zfsvfs->z_os)) return (SET_ERROR(ENOTSUP)); @@ -662,7 +690,8 @@ zfs_userspace_many(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, return (0); } - if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED) + if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED || + type == ZFS_PROP_PROJECTOBJUSED) offset = DMU_OBJACCT_PREFIX_LEN; for (zap_cursor_init_serialized(&zc, zfsvfs->z_os, obj, *cookiep); @@ -731,15 +760,27 @@ zfs_userspace_one(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, return (SET_ERROR(ENOTSUP)); if ((type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED || - type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA) && + type == ZFS_PROP_USEROBJQUOTA || type == ZFS_PROP_GROUPOBJQUOTA || + type == ZFS_PROP_PROJECTOBJUSED || + type == ZFS_PROP_PROJECTOBJQUOTA) && !dmu_objset_userobjspace_present(zfsvfs->z_os)) return (SET_ERROR(ENOTSUP)); + if (type == ZFS_PROP_PROJECTQUOTA || type == ZFS_PROP_PROJECTUSED || + type == ZFS_PROP_PROJECTOBJQUOTA || + type == ZFS_PROP_PROJECTOBJUSED) { + if (!dmu_objset_projectquota_present(zfsvfs->z_os)) + return (SET_ERROR(ENOTSUP)); + if (!zpl_is_valid_projid(rid)) + return (SET_ERROR(EINVAL)); + } + obj = zfs_userquota_prop_to_obj(zfsvfs, type); if (obj == ZFS_NO_OBJECT) return (0); - if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED) { + if (type == ZFS_PROP_USEROBJUSED || type == ZFS_PROP_GROUPOBJUSED || + type == ZFS_PROP_PROJECTOBJUSED) { strlcpy(buf, DMU_OBJACCT_PREFIX, DMU_OBJACCT_PREFIX_LEN + 1); offset = DMU_OBJACCT_PREFIX_LEN; } @@ -780,6 +821,22 @@ zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, case ZFS_PROP_GROUPOBJQUOTA: objp = &zfsvfs->z_groupobjquota_obj; break; + case ZFS_PROP_PROJECTQUOTA: + if (!dmu_objset_projectquota_enabled(zfsvfs->z_os)) + return (SET_ERROR(ENOTSUP)); + if (!zpl_is_valid_projid(rid)) + return (SET_ERROR(EINVAL)); + + objp = &zfsvfs->z_projectquota_obj; + break; + case ZFS_PROP_PROJECTOBJQUOTA: + if (!dmu_objset_projectquota_enabled(zfsvfs->z_os)) + return (SET_ERROR(ENOTSUP)); + if (!zpl_is_valid_projid(rid)) + return (SET_ERROR(EINVAL)); + + objp = &zfsvfs->z_projectobjquota_obj; + break; default: return (SET_ERROR(EINVAL)); } @@ -827,35 +884,51 @@ zfs_set_userquota(zfsvfs_t *zfsvfs, zfs_userquota_prop_t type, } boolean_t -zfs_fuid_overobjquota(zfsvfs_t *zfsvfs, boolean_t isgroup, uint64_t fuid) +zfs_id_overobjquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id) { char buf[20 + DMU_OBJACCT_PREFIX_LEN]; - uint64_t used, quota, usedobj, quotaobj; + uint64_t used, quota, quotaobj; int err; if (!dmu_objset_userobjspace_present(zfsvfs->z_os)) { if (dmu_objset_userobjspace_upgradable(zfsvfs->z_os)) { dsl_pool_config_enter( dmu_objset_pool(zfsvfs->z_os), FTAG); - dmu_objset_userobjspace_upgrade(zfsvfs->z_os); + dmu_objset_id_quota_upgrade(zfsvfs->z_os); dsl_pool_config_exit( dmu_objset_pool(zfsvfs->z_os), FTAG); } return (B_FALSE); } - usedobj = isgroup ? DMU_GROUPUSED_OBJECT : DMU_USERUSED_OBJECT; - quotaobj = isgroup ? zfsvfs->z_groupobjquota_obj : - zfsvfs->z_userobjquota_obj; + if (usedobj == DMU_PROJECTUSED_OBJECT) { + if (!dmu_objset_projectquota_present(zfsvfs->z_os)) { + if (dmu_objset_projectquota_upgradable(zfsvfs->z_os)) { + dsl_pool_config_enter( + dmu_objset_pool(zfsvfs->z_os), FTAG); + dmu_objset_id_quota_upgrade(zfsvfs->z_os); + dsl_pool_config_exit( + dmu_objset_pool(zfsvfs->z_os), FTAG); + } + return (B_FALSE); + } + quotaobj = zfsvfs->z_projectobjquota_obj; + } else if (usedobj == DMU_USERUSED_OBJECT) { + quotaobj = zfsvfs->z_userobjquota_obj; + } else if (usedobj == DMU_GROUPUSED_OBJECT) { + quotaobj = zfsvfs->z_groupobjquota_obj; + } else { + return (B_FALSE); + } if (quotaobj == 0 || zfsvfs->z_replay) return (B_FALSE); - (void) sprintf(buf, "%llx", (longlong_t)fuid); + (void) sprintf(buf, "%llx", (longlong_t)id); err = zap_lookup(zfsvfs->z_os, quotaobj, buf, 8, 1, "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); @@ -863,19 +936,35 @@ zfs_fuid_overobjquota(zfsvfs_t *zfsvfs, boolean_t isgroup, uint64_t fuid) } boolean_t -zfs_fuid_overquota(zfsvfs_t *zfsvfs, boolean_t isgroup, uint64_t fuid) +zfs_id_overblockquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id) { char buf[20]; - uint64_t used, quota, usedobj, quotaobj; + uint64_t used, quota, quotaobj; int err; - usedobj = isgroup ? DMU_GROUPUSED_OBJECT : DMU_USERUSED_OBJECT; - quotaobj = isgroup ? zfsvfs->z_groupquota_obj : zfsvfs->z_userquota_obj; - + if (usedobj == DMU_PROJECTUSED_OBJECT) { + if (!dmu_objset_projectquota_present(zfsvfs->z_os)) { + if (dmu_objset_projectquota_upgradable(zfsvfs->z_os)) { + dsl_pool_config_enter( + dmu_objset_pool(zfsvfs->z_os), FTAG); + dmu_objset_id_quota_upgrade(zfsvfs->z_os); + dsl_pool_config_exit( + dmu_objset_pool(zfsvfs->z_os), FTAG); + } + return (B_FALSE); + } + quotaobj = zfsvfs->z_projectquota_obj; + } else if (usedobj == DMU_USERUSED_OBJECT) { + quotaobj = zfsvfs->z_userquota_obj; + } else if (usedobj == DMU_GROUPUSED_OBJECT) { + quotaobj = zfsvfs->z_groupquota_obj; + } else { + return (B_FALSE); + } if (quotaobj == 0 || zfsvfs->z_replay) return (B_FALSE); - (void) sprintf(buf, "%llx", (longlong_t)fuid); + (void) sprintf(buf, "%llx", (longlong_t)id); err = zap_lookup(zfsvfs->z_os, quotaobj, buf, 8, 1, "a); if (err != 0) return (B_FALSE); @@ -887,20 +976,10 @@ zfs_fuid_overquota(zfsvfs_t *zfsvfs, boolean_t isgroup, uint64_t fuid) } boolean_t -zfs_owner_overquota(zfsvfs_t *zfsvfs, znode_t *zp, boolean_t isgroup) +zfs_id_overquota(zfsvfs_t *zfsvfs, uint64_t usedobj, uint64_t id) { - uint64_t fuid; - uint64_t quotaobj; - struct inode *ip = ZTOI(zp); - - quotaobj = isgroup ? zfsvfs->z_groupquota_obj : zfsvfs->z_userquota_obj; - - fuid = isgroup ? KGID_TO_SGID(ip->i_gid) : KUID_TO_SUID(ip->i_uid); - - if (quotaobj == 0 || zfsvfs->z_replay) - return (B_FALSE); - - return (zfs_fuid_overquota(zfsvfs, isgroup, fuid)); + return (zfs_id_overblockquota(zfsvfs, usedobj, id) || + zfs_id_overobjquota(zfsvfs, usedobj, id)); } /* @@ -1007,6 +1086,14 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os) else if (error != 0) return (error); + error = zap_lookup(os, MASTER_NODE_OBJ, + zfs_userquota_prop_prefixes[ZFS_PROP_PROJECTQUOTA], + 8, 1, &zfsvfs->z_projectquota_obj); + if (error == ENOENT) + zfsvfs->z_projectquota_obj = 0; + else if (error != 0) + return (error); + error = zap_lookup(os, MASTER_NODE_OBJ, zfs_userquota_prop_prefixes[ZFS_PROP_USEROBJQUOTA], 8, 1, &zfsvfs->z_userobjquota_obj); @@ -1023,6 +1110,14 @@ zfsvfs_init(zfsvfs_t *zfsvfs, objset_t *os) else if (error != 0) return (error); + error = zap_lookup(os, MASTER_NODE_OBJ, + zfs_userquota_prop_prefixes[ZFS_PROP_PROJECTOBJQUOTA], + 8, 1, &zfsvfs->z_projectobjquota_obj); + if (error == ENOENT) + zfsvfs->z_projectobjquota_obj = 0; + else if (error != 0) + return (error); + error = zap_lookup(os, MASTER_NODE_OBJ, ZFS_FUID_TABLES, 8, 1, &zfsvfs->z_fuid_obj); if (error == ENOENT) @@ -1264,6 +1359,83 @@ zfs_check_global_label(const char *dsname, const char *hexsl) } #endif /* HAVE_MLSLABEL */ +static int +zfs_statfs_project(zfsvfs_t *zfsvfs, znode_t *zp, struct kstatfs *statp, + uint32_t bshift) +{ + char buf[20 + DMU_OBJACCT_PREFIX_LEN]; + uint64_t offset = DMU_OBJACCT_PREFIX_LEN; + uint64_t quota; + uint64_t used; + int err; + + strlcpy(buf, DMU_OBJACCT_PREFIX, DMU_OBJACCT_PREFIX_LEN + 1); + err = id_to_fuidstr(zfsvfs, NULL, zp->z_projid, buf + offset, B_FALSE); + if (err) + return (err); + + if (zfsvfs->z_projectquota_obj == 0) + goto objs; + + err = zap_lookup(zfsvfs->z_os, zfsvfs->z_projectquota_obj, + buf + offset, 8, 1, "a); + if (err == ENOENT) + goto objs; + else if (err) + return (err); + + err = zap_lookup(zfsvfs->z_os, DMU_PROJECTUSED_OBJECT, + buf + offset, 8, 1, &used); + if (unlikely(err == ENOENT)) { + uint32_t blksize; + u_longlong_t nblocks; + + /* + * Quota accounting is async, so it is possible race case. + * There is at least one object with the given project ID. + */ + sa_object_size(zp->z_sa_hdl, &blksize, &nblocks); + if (unlikely(zp->z_blksz == 0)) + blksize = zfsvfs->z_max_blksz; + + used = blksize * nblocks; + } else if (err) { + return (err); + } + + statp->f_blocks = quota >> bshift; + statp->f_bfree = (quota > used) ? ((quota - used) >> bshift) : 0; + statp->f_bavail = statp->f_bfree; + +objs: + if (zfsvfs->z_projectobjquota_obj == 0) + return (0); + + err = zap_lookup(zfsvfs->z_os, zfsvfs->z_projectobjquota_obj, + buf + offset, 8, 1, "a); + if (err == ENOENT) + return (0); + else if (err) + return (err); + + err = zap_lookup(zfsvfs->z_os, DMU_PROJECTUSED_OBJECT, + buf, 8, 1, &used); + if (unlikely(err == ENOENT)) { + /* + * Quota accounting is async, so it is possible race case. + * There is at least one object with the given project ID. + */ + used = 1; + } else if (err) { + return (err); + } + + statp->f_files = quota; + statp->f_ffree = (quota > used) ? (quota - used) : 0; + + return (0); +} + int zfs_statvfs(struct dentry *dentry, struct kstatfs *statp) { @@ -1271,6 +1443,7 @@ zfs_statvfs(struct dentry *dentry, struct kstatfs *statp) uint64_t refdbytes, availbytes, usedobjs, availobjs; uint64_t fsid; uint32_t bshift; + int err = 0; ZFS_ENTER(zfsvfs); @@ -1322,8 +1495,17 @@ zfs_statvfs(struct dentry *dentry, struct kstatfs *statp) */ bzero(statp->f_spare, sizeof (statp->f_spare)); + if (dmu_objset_projectquota_enabled(zfsvfs->z_os) && + dmu_objset_projectquota_present(zfsvfs->z_os)) { + znode_t *zp = ITOZ(dentry->d_inode); + + if (zp->z_pflags & ZFS_PROJINHERIT && zp->z_projid && + zpl_is_valid_projid(zp->z_projid)) + err = zfs_statfs_project(zfsvfs, zp, statp, bshift); + } + ZFS_EXIT(zfsvfs); - return (0); + return (err); } int @@ -2170,9 +2352,9 @@ EXPORT_SYMBOL(zfs_resume_fs); EXPORT_SYMBOL(zfs_userspace_one); EXPORT_SYMBOL(zfs_userspace_many); EXPORT_SYMBOL(zfs_set_userquota); -EXPORT_SYMBOL(zfs_owner_overquota); -EXPORT_SYMBOL(zfs_fuid_overquota); -EXPORT_SYMBOL(zfs_fuid_overobjquota); +EXPORT_SYMBOL(zfs_id_overblockquota); +EXPORT_SYMBOL(zfs_id_overobjquota); +EXPORT_SYMBOL(zfs_id_overquota); EXPORT_SYMBOL(zfs_set_version); EXPORT_SYMBOL(zfsvfs_create); EXPORT_SYMBOL(zfsvfs_free); diff --git a/module/zfs/zfs_vnops.c b/module/zfs/zfs_vnops.c index e21fe1acae2a..f35165de3e89 100644 --- a/module/zfs/zfs_vnops.c +++ b/module/zfs/zfs_vnops.c @@ -79,6 +79,7 @@ #include <sys/attr.h> #include <sys/zpl.h> #include <sys/zil.h> +#include <sys/sa_impl.h> /* * Programming rules. @@ -728,8 +729,13 @@ zfs_write(struct inode *ip, uio_t *uio, int ioflag, cred_t *cr) while (n > 0) { abuf = NULL; woff = uio->uio_loffset; - if (zfs_owner_overquota(zfsvfs, zp, B_FALSE) || - zfs_owner_overquota(zfsvfs, zp, B_TRUE)) { + if (zfs_id_overblockquota(zfsvfs, DMU_USERUSED_OBJECT, + KUID_TO_SUID(ip->i_uid)) || + zfs_id_overblockquota(zfsvfs, DMU_GROUPUSED_OBJECT, + KGID_TO_SGID(ip->i_gid)) || + (zp->z_projid != ZFS_DEFAULT_PROJID && + zfs_id_overblockquota(zfsvfs, DMU_PROJECTUSED_OBJECT, + zp->z_projid))) { if (abuf != NULL) dmu_return_arcbuf(abuf); error = SET_ERROR(EDQUOT); @@ -1380,6 +1386,7 @@ zfs_create(struct inode *dip, char *name, vattr_t *vap, int excl, if (zp == NULL) { uint64_t txtype; + uint64_t projid = ZFS_DEFAULT_PROJID; /* * Create a new file object and update the directory @@ -1408,7 +1415,9 @@ 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 (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) + projid = zfs_inherit_projid(dzp); + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, projid)) { zfs_acl_ids_free(&acl_ids); error = SET_ERROR(EDQUOT); goto out; @@ -1552,6 +1561,7 @@ zfs_tmpfile(struct inode *dip, vattr_t *vap, int excl, uid_t uid; gid_t gid; zfs_acl_ids_t acl_ids; + uint64_t projid = ZFS_DEFAULT_PROJID; boolean_t fuid_dirtied; boolean_t have_acl = B_FALSE; boolean_t waited = B_FALSE; @@ -1598,7 +1608,9 @@ 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 (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) + projid = zfs_inherit_projid(dzp); + if (zfs_acl_ids_overquota(zfsvfs, &acl_ids, projid)) { zfs_acl_ids_free(&acl_ids); error = SET_ERROR(EDQUOT); goto out; @@ -2009,7 +2021,7 @@ 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, zfs_inherit_projid(dzp))) { zfs_acl_ids_free(&acl_ids); zfs_dirent_unlock(dl); ZFS_EXIT(zfsvfs); @@ -2591,6 +2603,17 @@ zfs_getattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) ((zp->z_pflags & ZFS_SPARSE) != 0); XVA_SET_RTN(xvap, XAT_SPARSE); } + + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) { + xoap->xoa_projinherit = + ((zp->z_pflags & ZFS_PROJINHERIT) != 0); + XVA_SET_RTN(xvap, XAT_PROJINHERIT); + } + + if (XVA_ISSET_REQ(xvap, XAT_PROJID)) { + xoap->xoa_projid = zp->z_projid; + XVA_SET_RTN(xvap, XAT_PROJID); + } } ZFS_TIME_DECODE(&vap->va_atime, atime); @@ -2668,6 +2691,125 @@ zfs_getattr_fast(struct inode *ip, struct kstat *sp) return (0); } +/* + * For the operation of changing file's user/group/project, we need to + * handle not only the main object that is assigned to the file directly, + * but also the ones that are used by the file via hidden xattr directory. + * + * Because the xattr directory may contains many EA entries, as to it may + * be impossible to change all of them via the transaction of changing the + * main object's user/group/project attributes. Then we have to change them + * via other multiple independent transactions one by one. It may be not good + * solution, but we have no better idea yet. + */ +static int +zfs_setattr_dir(znode_t *dzp) +{ + struct inode *dxip = ZTOI(dzp); + struct inode *xip = NULL; + zfsvfs_t *zfsvfs = ITOZSB(dxip); + objset_t *os = zfsvfs->z_os; + zap_cursor_t zc; + zap_attribute_t zap; + zfs_dirlock_t *dl; + znode_t *zp; + dmu_tx_t *tx = NULL; + uint64_t uid, gid; + sa_bulk_attr_t bulk[4]; + int count = 0; + int err; + + zap_cursor_init(&zc, os, dzp->z_id); + while ((err = zap_cursor_retrieve(&zc, &zap)) == 0) { + if (zap.za_integer_length != 8 || zap.za_num_integers != 1) { + err = ENXIO; + break; + } + + err = zfs_dirent_lock(&dl, dzp, (char *)zap.za_name, &zp, + ZEXISTS, NULL, NULL); + if (err == ENOENT) + goto next; + if (err) + break; + + xip = ZTOI(zp); + if (KUID_TO_SUID(xip->i_uid) == KUID_TO_SUID(dxip->i_uid) && + KGID_TO_SGID(xip->i_gid) == KGID_TO_SGID(dxip->i_gid) && + zp->z_projid == dzp->z_projid) + goto next; + + tx = dmu_tx_create(os); + if (!(zp->z_pflags & ZFS_PROJID)) + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE); + else + dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); + + err = dmu_tx_assign(tx, TXG_WAIT); + if (err) + break; + + mutex_enter(&dzp->z_lock); + + if (KUID_TO_SUID(xip->i_uid) != KUID_TO_SUID(dxip->i_uid)) { + xip->i_uid = dxip->i_uid; + uid = zfs_uid_read(dxip); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_UID(zfsvfs), NULL, + &uid, sizeof (uid)); + } + + if (KGID_TO_SGID(xip->i_gid) != KGID_TO_SGID(dxip->i_gid)) { + xip->i_gid = dxip->i_gid; + gid = zfs_gid_read(dxip); + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_GID(zfsvfs), NULL, + &gid, sizeof (gid)); + } + + if (zp->z_projid != dzp->z_projid) { + if (!(zp->z_pflags & ZFS_PROJID)) { + zp->z_pflags |= ZFS_PROJID; + SA_ADD_BULK_ATTR(bulk, count, + SA_ZPL_FLAGS(zfsvfs), NULL, &zp->z_pflags, + sizeof (zp->z_pflags)); + } + + zp->z_projid = dzp->z_projid; + SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_PROJID(zfsvfs), + NULL, &zp->z_projid, sizeof (zp->z_projid)); + } + + mutex_exit(&dzp->z_lock); + + if (likely(count > 0)) { + err = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); + dmu_tx_commit(tx); + } else { + dmu_tx_abort(tx); + } + tx = NULL; + if (err != 0 && err != ENOENT) + break; + +next: + if (xip) { + iput(xip); + xip = NULL; + zfs_dirent_unlock(dl); + } + zap_cursor_advance(&zc); + } + + if (tx) + dmu_tx_abort(tx); + if (xip) { + iput(xip); + zfs_dirent_unlock(dl); + } + zap_cursor_fini(&zc); + + return (err == ENOENT ? 0 : err); +} + /* * Set the file attributes to the values contained in the * vattr structure. @@ -2691,6 +2833,7 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) { znode_t *zp = ITOZ(ip); zfsvfs_t *zfsvfs = ITOZSB(ip); + objset_t *os = zfsvfs->z_os; zilog_t *zilog; dmu_tx_t *tx; vattr_t oldva; @@ -2702,17 +2845,19 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) uint64_t new_kuid = 0, new_kgid = 0, new_uid, new_gid; uint64_t xattr_obj; uint64_t mtime[2], ctime[2], atime[2]; + uint64_t projid = ZFS_INVALID_PROJID; znode_t *attrzp; int need_policy = FALSE; - int err, err2; + int err, err2 = 0; zfs_fuid_info_t *fuidp = NULL; xvattr_t *xvap = (xvattr_t *)vap; /* vap may be an xvattr_t * */ xoptattr_t *xoap; zfs_acl_t *aclp; boolean_t skipaclchk = (flags & ATTR_NOACLCHECK) ? B_TRUE : B_FALSE; boolean_t fuid_dirtied = B_FALSE; + boolean_t handle_eadir = B_FALSE; sa_bulk_attr_t *bulk, *xattr_bulk; - int count = 0, xattr_count = 0; + int count = 0, xattr_count = 0, bulks = 8; if (mask == 0) return (0); @@ -2720,6 +2865,39 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) ZFS_ENTER(zfsvfs); ZFS_VERIFY_ZP(zp); + /* + * If this is a xvattr_t, then get a pointer to the structure of + * optional attributes. If this is NULL, then we have a vattr_t. + */ + xoap = xva_getxoptattr(xvap); + if (xoap != NULL && (mask & ATTR_XVATTR)) { + if (XVA_ISSET_REQ(xvap, XAT_PROJID)) { + if (!dmu_objset_projectquota_enabled(os) || + (!S_ISREG(ip->i_mode) && !S_ISDIR(ip->i_mode))) { + ZFS_EXIT(zfsvfs); + return (SET_ERROR(ENOTSUP)); + } + + projid = xoap->xoa_projid; + if (unlikely(projid == ZFS_INVALID_PROJID)) { + ZFS_EXIT(zfsvfs); + return (SET_ERROR(EINVAL)); + } + + if (projid == zp->z_projid && zp->z_pflags & ZFS_PROJID) + projid = ZFS_INVALID_PROJID; + else + need_policy = TRUE; + } + + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT) && + (!dmu_objset_projectquota_enabled(os) || + (!S_ISREG(ip->i_mode) && !S_ISDIR(ip->i_mode)))) { + ZFS_EXIT(zfsvfs); + return (SET_ERROR(ENOTSUP)); + } + } + zilog = zfsvfs->z_log; /* @@ -2745,17 +2923,11 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) return (SET_ERROR(EINVAL)); } - /* - * If this is an xvattr_t, then get a pointer to the structure of - * optional attributes. If this is NULL, then we have a vattr_t. - */ - xoap = xva_getxoptattr(xvap); - tmpxvattr = kmem_alloc(sizeof (xvattr_t), KM_SLEEP); xva_init(tmpxvattr); - bulk = kmem_alloc(sizeof (sa_bulk_attr_t) * 7, KM_SLEEP); - xattr_bulk = kmem_alloc(sizeof (sa_bulk_attr_t) * 7, KM_SLEEP); + bulk = kmem_alloc(sizeof (sa_bulk_attr_t) * bulks, KM_SLEEP); + xattr_bulk = kmem_alloc(sizeof (sa_bulk_attr_t) * bulks, KM_SLEEP); /* * Immutable files can only alter immutable bit and atime @@ -2901,6 +3073,16 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) } } + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) { + if (xoap->xoa_projinherit != + ((zp->z_pflags & ZFS_PROJINHERIT) != 0)) { + need_policy = TRUE; + } else { + XVA_CLR_REQ(xvap, XAT_PROJINHERIT); + XVA_SET_REQ(tmpxvattr, XAT_PROJINHERIT); + } + } + if (XVA_ISSET_REQ(xvap, XAT_NOUNLINK)) { if (xoap->xoa_nounlink != ((zp->z_pflags & ZFS_NOUNLINK) != 0)) { @@ -3009,7 +3191,8 @@ 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) { + handle_eadir = B_TRUE; err = sa_lookup(zp->z_sa_hdl, SA_ZPL_XATTR(zfsvfs), &xattr_obj, sizeof (xattr_obj)); @@ -3022,7 +3205,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 = SET_ERROR(EDQUOT); @@ -3034,15 +3218,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 = SET_ERROR(EDQUOT); goto out2; } } + + if (projid != ZFS_INVALID_PROJID && + zfs_id_overquota(zfsvfs, DMU_PROJECTUSED_OBJECT, projid)) { + if (attrzp) + iput(ZTOI(attrzp)); + err = EDQUOT; + goto out2; + } } - tx = dmu_tx_create(zfsvfs->z_os); + tx = dmu_tx_create(os); if (mask & ATTR_MODE) { uint64_t pmode = zp->z_mode; @@ -3075,8 +3268,10 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) mutex_exit(&zp->z_lock); dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE); } else { - if ((mask & ATTR_XVATTR) && - XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) + if (((mask & ATTR_XVATTR) && + XVA_ISSET_REQ(xvap, XAT_AV_SCANSTAMP)) || + (projid != ZFS_INVALID_PROJID && + !(zp->z_pflags & ZFS_PROJID))) dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_TRUE); else dmu_tx_hold_sa(tx, zp->z_sa_hdl, B_FALSE); @@ -3105,6 +3300,26 @@ zfs_setattr(struct inode *ip, vattr_t *vap, int flags, cred_t *cr) * updated as a side-effect of calling this function. */ + if (projid != ZFS_INVALID_PROJID && !(zp->z_pflags & ZFS_PROJID)) { + /* + * For the existed object that is upgraded from old system, + * its on-disk layout has no slot for the project ID attribute. + * But quota accounting logic needs to access related slots by + * offset directly. So we need to adjust old objects' layout + * to make the project ID to some unified and fixed offset. + */ + if (attrzp) + err = sa_add_projid(attrzp->z_sa_hdl, tx, projid); + if (err == 0) + err = sa_add_projid(zp->z_sa_hdl, tx, projid); + + if (unlikely(err == EEXIST)) + err = 0; + else if (err != 0) + goto out; + else + projid = ZFS_INVALID_PROJID; + } if (mask & (ATTR_UID|ATTR_GID|ATTR_MODE)) mutex_enter(&zp->z_acl_lock); @@ -3120,6 +3335,12 @@ 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)) { @@ -3199,6 +3420,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, @@ -3235,6 +3463,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)); @@ -3258,7 +3489,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 && xattr_count > 0) { err2 = sa_bulk_update(attrzp->z_sa_hdl, xattr_bulk, xattr_count, tx); ASSERT(err2 == 0); @@ -3279,20 +3510,24 @@ 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 (count > 0) + err2 = sa_bulk_update(zp->z_sa_hdl, bulk, count, tx); dmu_tx_commit(tx); - if (attrzp) + if (attrzp) { + if (err2 == 0 && handle_eadir) + err2 = zfs_setattr_dir(attrzp); iput(ZTOI(attrzp)); + } zfs_inode_update(zp); } out2: - if (zfsvfs->z_os->os_sync == ZFS_SYNC_ALWAYS) + if (os->os_sync == ZFS_SYNC_ALWAYS) zil_commit(zilog, 0); out3: - kmem_free(xattr_bulk, sizeof (sa_bulk_attr_t) * 7); - kmem_free(bulk, sizeof (sa_bulk_attr_t) * 7); + kmem_free(xattr_bulk, sizeof (sa_bulk_attr_t) * bulks); + kmem_free(bulk, sizeof (sa_bulk_attr_t) * bulks); kmem_free(tmpxvattr, sizeof (xvattr_t)); ZFS_EXIT(zfsvfs); return (err); @@ -3585,6 +3820,19 @@ zfs_rename(struct inode *sdip, char *snm, struct inode *tdip, char *tnm, return (terr); } + /* + * If we are using project inheritance, means if the directory has + * ZFS_PROJINHERIT set, then its descendant directories will inherit + * not only the project ID, but also the ZFS_PROJINHERIT flag. Under + * such case, we only allow renames into our tree when the project + * IDs are the same. + */ + if (tdzp->z_pflags & ZFS_PROJINHERIT && + tdzp->z_projid != szp->z_projid) { + error = SET_ERROR(EXDEV); + goto out; + } + /* * Must have write access at the source to remove the old entry * and write access at the target to create the new entry. @@ -3683,6 +3931,8 @@ 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) + szp->z_pflags |= ZFS_PROJINHERIT; error = sa_update(szp->z_sa_hdl, SA_ZPL_FLAGS(zfsvfs), (void *)&szp->z_pflags, sizeof (uint64_t), tx); @@ -3829,7 +4079,7 @@ 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, ZFS_DEFAULT_PROJID)) { zfs_acl_ids_free(&acl_ids); zfs_dirent_unlock(dl); ZFS_EXIT(zfsvfs); @@ -4012,6 +4262,18 @@ zfs_link(struct inode *tdip, struct inode *sip, char *name, cred_t *cr, szp = ITOZ(sip); ZFS_VERIFY_ZP(szp); + /* + * If we are using project inheritance, means if the directory has + * ZFS_PROJINHERIT set, then its descendant directories will inherit + * not only the project ID, but also the ZFS_PROJINHERIT flag. Under + * such case, we only allow hard link creation in our tree when the + * project IDs are the same. + */ + if (dzp->z_pflags & ZFS_PROJINHERIT && dzp->z_projid != szp->z_projid) { + ZFS_EXIT(zfsvfs); + return (SET_ERROR(EXDEV)); + } + /* * We check i_sb because snapshots and the ctldir must have different * super blocks. @@ -4206,8 +4468,13 @@ zfs_putpage(struct inode *ip, struct page *pp, struct writeback_control *wbc) * is to register a page_mkwrite() handler to count the page * against its quota when it is about to be dirtied. */ - if (zfs_owner_overquota(zfsvfs, zp, B_FALSE) || - zfs_owner_overquota(zfsvfs, zp, B_TRUE)) { + if (zfs_id_overblockquota(zfsvfs, DMU_USERUSED_OBJECT, + KUID_TO_SUID(ip->i_uid)) || + zfs_id_overblockquota(zfsvfs, DMU_GROUPUSED_OBJECT, + KGID_TO_SGID(ip->i_gid)) || + (zp->z_projid != ZFS_DEFAULT_PROJID && + zfs_id_overblockquota(zfsvfs, DMU_PROJECTUSED_OBJECT, + zp->z_projid))) { err = EDQUOT; } #endif diff --git a/module/zfs/zfs_znode.c b/module/zfs/zfs_znode.c index d3b68403b320..5288c9c68498 100644 --- a/module/zfs/zfs_znode.c +++ b/module/zfs/zfs_znode.c @@ -328,6 +328,7 @@ zfs_create_share_dir(zfsvfs_t *zfsvfs, dmu_tx_t *tx) sharezp->z_atime_dirty = 0; sharezp->z_zfsvfs = zfsvfs; sharezp->z_is_sa = zfsvfs->z_use_sa; + sharezp->z_pflags = 0; vp = ZTOV(sharezp); vn_reinit(vp); @@ -558,6 +559,7 @@ zfs_znode_alloc(zfsvfs_t *zfsvfs, dmu_buf_t *db, int blksz, uint64_t links; uint64_t z_uid, z_gid; uint64_t atime[2], mtime[2], ctime[2]; + uint64_t projid = ZFS_DEFAULT_PROJID; sa_bulk_attr_t bulk[11]; int count = 0; @@ -604,13 +606,17 @@ zfs_znode_alloc(zfsvfs_t *zfsvfs, dmu_buf_t *db, int blksz, SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_MTIME(zfsvfs), NULL, &mtime, 16); SA_ADD_BULK_ATTR(bulk, count, SA_ZPL_CTIME(zfsvfs), NULL, &ctime, 16); - if (sa_bulk_lookup(zp->z_sa_hdl, bulk, count) != 0 || tmp_gen == 0) { + if (sa_bulk_lookup(zp->z_sa_hdl, bulk, count) != 0 || tmp_gen == 0 || + (dmu_objset_projectquota_enabled(zfsvfs->z_os) && + (zp->z_pflags & ZFS_PROJID) && + sa_lookup(zp->z_sa_hdl, SA_ZPL_PROJID(zfsvfs), &projid, 8) != 0)) { if (hdl == NULL) sa_handle_destroy(zp->z_sa_hdl); zp->z_sa_hdl = NULL; goto error; } + zp->z_projid = projid; zp->z_mode = ip->i_mode = mode; ip->i_generation = (uint32_t)tmp_gen; ip->i_blkbits = SPA_MINBLOCKSHIFT; @@ -696,7 +702,7 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, { uint64_t crtime[2], atime[2], mtime[2], ctime[2]; uint64_t mode, size, links, parent, pflags; - uint64_t dzp_pflags = 0; + uint64_t projid = ZFS_DEFAULT_PROJID; uint64_t rdev = 0; zfsvfs_t *zfsvfs = ZTOZSB(dzp); dmu_buf_t *db; @@ -771,14 +777,12 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, */ if (flag & IS_ROOT_NODE) { dzp->z_id = obj; - } else { - dzp_pflags = dzp->z_pflags; } /* * If parent is an xattr, so am I. */ - if (dzp_pflags & ZFS_XATTR) { + if (dzp->z_pflags & ZFS_XATTR) { flag |= IS_XATTR; } @@ -803,6 +807,23 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, if (flag & IS_XATTR) pflags |= ZFS_XATTR; + if (S_ISREG(vap->va_mode) || S_ISDIR(vap->va_mode)) { + /* + * With ZFS_PROJID flag, we can easily know whether there is + * project ID stored on disk or not. See zfs_space_delta_cb(). + */ + if (obj_type != DMU_OT_ZNODE && + dmu_objset_projectquota_enabled(zfsvfs->z_os)) + pflags |= ZFS_PROJID; + + /* + * Inherit project ID from parent if required. + */ + projid = zfs_inherit_projid(dzp); + if (dzp->z_pflags & ZFS_PROJINHERIT) + pflags |= ZFS_PROJINHERIT; + } + /* * No execs denied will be deterimed when zfs_mode_compute() is called. */ @@ -884,6 +905,10 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, if (obj_type == DMU_OT_ZNODE) { SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_XATTR(zfsvfs), NULL, &empty_xattr, 8); + } else if (dmu_objset_projectquota_enabled(zfsvfs->z_os) && + pflags & ZFS_PROJID) { + SA_ADD_BULK_ATTR(sa_attrs, cnt, SA_ZPL_PROJID(zfsvfs), + NULL, &projid, 8); } if (obj_type == DMU_OT_ZNODE || (S_ISBLK(vap->va_mode) || S_ISCHR(vap->va_mode))) { @@ -942,6 +967,7 @@ zfs_mknode(znode_t *dzp, vattr_t *vap, dmu_tx_t *tx, cred_t *cr, (*zpp)->z_pflags = pflags; (*zpp)->z_mode = ZTOI(*zpp)->i_mode = mode; (*zpp)->z_dnodesize = dnodesize; + (*zpp)->z_projid = projid; if (obj_type == DMU_OT_ZNODE || acl_ids->z_aclp->z_version < ZFS_ACL_VERSION_FUID) { @@ -1049,6 +1075,11 @@ zfs_xvattr_set(znode_t *zp, xvattr_t *xvap, dmu_tx_t *tx) zp->z_pflags, tx); XVA_SET_RTN(xvap, XAT_SPARSE); } + if (XVA_ISSET_REQ(xvap, XAT_PROJINHERIT)) { + ZFS_ATTR_SET(zp, ZFS_PROJINHERIT, xoap->xoa_projinherit, + zp->z_pflags, tx); + XVA_SET_RTN(xvap, XAT_PROJINHERIT); + } if (update_inode) zfs_set_inode_flags(zp, ZTOI(zp)); @@ -1166,6 +1197,7 @@ zfs_rezget(znode_t *zp) uint64_t gen; uint64_t z_uid, z_gid; uint64_t atime[2], mtime[2], ctime[2]; + uint64_t projid = ZFS_DEFAULT_PROJID; znode_hold_t *zh; /* @@ -1241,6 +1273,17 @@ zfs_rezget(znode_t *zp) return (SET_ERROR(EIO)); } + if (dmu_objset_projectquota_enabled(zfsvfs->z_os)) { + err = sa_lookup(zp->z_sa_hdl, SA_ZPL_PROJID(zfsvfs), + &projid, 8); + if (err != 0 && err != ENOENT) { + zfs_znode_dmu_fini(zp); + zfs_znode_hold_exit(zfsvfs, zh); + return (SET_ERROR(err)); + } + } + + zp->z_projid = projid; zp->z_mode = ZTOI(zp)->i_mode = mode; zfs_uid_write(ZTOI(zp), z_uid); zfs_gid_write(ZTOI(zp), z_gid); @@ -1861,6 +1904,7 @@ zfs_create_fs(objset_t *os, cred_t *cr, nvlist_t *zplprops, dmu_tx_t *tx) rootzp->z_unlinked = 0; rootzp->z_atime_dirty = 0; rootzp->z_is_sa = USE_SA(version, os); + rootzp->z_pflags = 0; zfsvfs = kmem_zalloc(sizeof (zfsvfs_t), KM_SLEEP); zfsvfs->z_os = os; diff --git a/module/zfs/zpl_file.c b/module/zfs/zpl_file.c index 4dfa0dea4358..1c5f5e409637 100644 --- a/module/zfs/zpl_file.c +++ b/module/zfs/zpl_file.c @@ -31,7 +31,7 @@ #include <sys/zfs_vfsops.h> #include <sys/zfs_vnops.h> #include <sys/zfs_znode.h> -#include <sys/zpl.h> +#include <sys/zfs_project.h> static int @@ -720,17 +720,14 @@ zpl_fallocate(struct file *filp, int mode, loff_t offset, loff_t len) } #endif /* HAVE_FILE_FALLOCATE */ -/* - * Map zfs file z_pflags (xvattr_t) to linux file attributes. Only file - * attributes common to both Linux and Solaris are mapped. - */ -static int -zpl_ioctl_getflags(struct file *filp, void __user *arg) +#define ZFS_FL_USER_VISIBLE (FS_FL_USER_VISIBLE | ZFS_PROJINHERIT_FL) +#define ZFS_FL_USER_MODIFIABLE (FS_FL_USER_MODIFIABLE | ZFS_PROJINHERIT_FL) + +static uint32_t +__zpl_ioctl_getflags(struct inode *ip) { - struct inode *ip = file_inode(filp); - unsigned int ioctl_flags = 0; uint64_t zfs_flags = ITOZ(ip)->z_pflags; - int error; + uint32_t ioctl_flags = 0; if (zfs_flags & ZFS_IMMUTABLE) ioctl_flags |= FS_IMMUTABLE_FL; @@ -741,11 +738,26 @@ zpl_ioctl_getflags(struct file *filp, void __user *arg) if (zfs_flags & ZFS_NODUMP) ioctl_flags |= FS_NODUMP_FL; - ioctl_flags &= FS_FL_USER_VISIBLE; + if (zfs_flags & ZFS_PROJINHERIT) + ioctl_flags |= ZFS_PROJINHERIT_FL; - error = copy_to_user(arg, &ioctl_flags, sizeof (ioctl_flags)); + return (ioctl_flags & ZFS_FL_USER_VISIBLE); +} - return (error); +/* + * Map zfs file z_pflags (xvattr_t) to linux file attributes. Only file + * attributes common to both Linux and Solaris are mapped. + */ +static int +zpl_ioctl_getflags(struct file *filp, void __user *arg) +{ + uint32_t flags; + int err; + + flags = __zpl_ioctl_getflags(file_inode(filp)); + err = copy_to_user(arg, &flags, sizeof (flags)); + + return (err); } /* @@ -760,24 +772,16 @@ zpl_ioctl_getflags(struct file *filp, void __user *arg) #define fchange(f0, f1, b0, b1) (!((f0) & (b0)) != !((f1) & (b1))) static int -zpl_ioctl_setflags(struct file *filp, void __user *arg) +__zpl_ioctl_setflags(struct inode *ip, uint32_t ioctl_flags, xvattr_t *xva) { - struct inode *ip = file_inode(filp); - uint64_t zfs_flags = ITOZ(ip)->z_pflags; - unsigned int ioctl_flags; - cred_t *cr = CRED(); - xvattr_t xva; - xoptattr_t *xoap; - int error; - fstrans_cookie_t cookie; - - if (copy_from_user(&ioctl_flags, arg, sizeof (ioctl_flags))) - return (-EFAULT); + uint64_t zfs_flags = ITOZ(ip)->z_pflags; + xoptattr_t *xoap; - if ((ioctl_flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL))) + if (ioctl_flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL | + ZFS_PROJINHERIT_FL)) return (-EOPNOTSUPP); - if ((ioctl_flags & ~(FS_FL_USER_MODIFIABLE))) + if (ioctl_flags & ~ZFS_FL_USER_MODIFIABLE) return (-EACCES); if ((fchange(ioctl_flags, zfs_flags, FS_IMMUTABLE_FL, ZFS_IMMUTABLE) || @@ -788,28 +792,100 @@ zpl_ioctl_setflags(struct file *filp, void __user *arg) if (!zpl_inode_owner_or_capable(ip)) return (-EACCES); - xva_init(&xva); - xoap = xva_getxoptattr(&xva); + xva_init(xva); + xoap = xva_getxoptattr(xva); - XVA_SET_REQ(&xva, XAT_IMMUTABLE); + XVA_SET_REQ(xva, XAT_IMMUTABLE); if (ioctl_flags & FS_IMMUTABLE_FL) xoap->xoa_immutable = B_TRUE; - XVA_SET_REQ(&xva, XAT_APPENDONLY); + XVA_SET_REQ(xva, XAT_APPENDONLY); if (ioctl_flags & FS_APPEND_FL) xoap->xoa_appendonly = B_TRUE; - XVA_SET_REQ(&xva, XAT_NODUMP); + XVA_SET_REQ(xva, XAT_NODUMP); if (ioctl_flags & FS_NODUMP_FL) xoap->xoa_nodump = B_TRUE; + XVA_SET_REQ(xva, XAT_PROJINHERIT); + if (ioctl_flags & ZFS_PROJINHERIT_FL) + xoap->xoa_projinherit = B_TRUE; + + return (0); +} + +static int +zpl_ioctl_setflags(struct file *filp, void __user *arg) +{ + struct inode *ip = file_inode(filp); + uint32_t flags; + cred_t *cr = CRED(); + xvattr_t xva; + int err; + fstrans_cookie_t cookie; + + if (copy_from_user(&flags, arg, sizeof (flags))) + return (-EFAULT); + + err = __zpl_ioctl_setflags(ip, flags, &xva); + if (err) + return (err); + crhold(cr); cookie = spl_fstrans_mark(); - error = -zfs_setattr(ip, (vattr_t *)&xva, 0, cr); + err = -zfs_setattr(ip, (vattr_t *)&xva, 0, cr); spl_fstrans_unmark(cookie); crfree(cr); - return (error); + return (err); +} + +static int +zpl_ioctl_getxattr(struct file *filp, void __user *arg) +{ + zfsxattr_t fsx = { 0 }; + struct inode *ip = file_inode(filp); + int err; + + fsx.fsx_xflags = __zpl_ioctl_getflags(ip); + fsx.fsx_projid = ITOZ(ip)->z_projid; + err = copy_to_user(arg, &fsx, sizeof (fsx)); + + return (err); +} + +static int +zpl_ioctl_setxattr(struct file *filp, void __user *arg) +{ + struct inode *ip = file_inode(filp); + zfsxattr_t fsx; + cred_t *cr = CRED(); + xvattr_t xva; + xoptattr_t *xoap; + int err; + fstrans_cookie_t cookie; + + if (copy_from_user(&fsx, arg, sizeof (fsx))) + return (-EFAULT); + + if (!zpl_is_valid_projid(fsx.fsx_projid)) + return (-EINVAL); + + err = __zpl_ioctl_setflags(ip, fsx.fsx_xflags, &xva); + if (err) + return (err); + + xoap = xva_getxoptattr(&xva); + XVA_SET_REQ(&xva, XAT_PROJID); + xoap->xoa_projid = fsx.fsx_projid; + + crhold(cr); + cookie = spl_fstrans_mark(); + err = -zfs_setattr(ip, (vattr_t *)&xva, 0, cr); + spl_fstrans_unmark(cookie); + crfree(cr); + + return (err); } static long @@ -820,6 +896,10 @@ zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return (zpl_ioctl_getflags(filp, (void *)arg)); case FS_IOC_SETFLAGS: return (zpl_ioctl_setflags(filp, (void *)arg)); + case ZFS_IOC_FSGETXATTR: + return (zpl_ioctl_getxattr(filp, (void *)arg)); + case ZFS_IOC_FSSETXATTR: + return (zpl_ioctl_setxattr(filp, (void *)arg)); default: return (-ENOTTY); } diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index 43dd5cb1fb84..da9c791f9b6c 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -616,6 +616,16 @@ tags = ['functional', 'poolversion'] tests = ['privilege_001_pos', 'privilege_002_pos'] tags = ['functional', 'privilege'] +[tests/functional/projectquota] +tests = ['projectid_001_pos', 'projectid_002_pos', 'projectid_003_pos', + 'projectquota_001_pos', 'projectquota_002_pos', 'projectquota_003_pos', + 'projectquota_004_neg', 'projectquota_005_pos', 'projectquota_006_pos', + 'projectquota_007_pos', 'projectquota_008_pos', 'projectquota_009_pos', + 'projectspace_001_pos', 'projectspace_002_pos', 'projectspace_003_pos', + 'projectspace_004_pos', + 'projecttree_001_pos', 'projecttree_002_pos', 'projecttree_003_neg' ] +tags = ['functional', 'projectquota'] + [tests/functional/quota] tests = ['quota_001_pos', 'quota_002_pos', 'quota_003_pos', 'quota_004_pos', 'quota_005_pos', 'quota_006_neg'] @@ -728,7 +738,7 @@ tests = ['truncate_001_pos', 'truncate_002_pos', 'truncate_timestamps'] tags = ['functional', 'truncate'] [tests/functional/upgrade] -tests = [ 'upgrade_userobj_001_pos' ] +tests = ['upgrade_userobj_001_pos', 'upgrade_projectquota_001_pos'] tags = ['functional', 'upgrade'] [tests/functional/userquota] diff --git a/tests/zfs-tests/include/commands.cfg b/tests/zfs-tests/include/commands.cfg index e6f04060ca28..4aede9f09617 100644 --- a/tests/zfs-tests/include/commands.cfg +++ b/tests/zfs-tests/include/commands.cfg @@ -65,6 +65,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 1e9bbd4a484f..9df1d8e3ef0e 100644 --- a/tests/zfs-tests/tests/functional/Makefile.am +++ b/tests/zfs-tests/tests/functional/Makefile.am @@ -43,6 +43,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 1e184db823d8..d5791372d0e4 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 @@ -79,5 +79,6 @@ if is_linux; then "feature@large_dnode" "feature@userobj_accounting" "feature@encryption" + "feature@project_quota" ) fi diff --git a/tests/zfs-tests/tests/functional/privilege/setup.ksh b/tests/zfs-tests/tests/functional/privilege/setup.ksh index d8e79d1a2333..badd83bed45c 100755 --- a/tests/zfs-tests/tests/functional/privilege/setup.ksh +++ b/tests/zfs-tests/tests/functional/privilege/setup.ksh @@ -31,6 +31,10 @@ . $STF_SUITE/include/libtest.shlib +if is_linux; then + log_unsupported "Requires pfexec command" +fi + ZFS_USER=zfsrbac USES_NIS=false diff --git a/tests/zfs-tests/tests/functional/projectquota/Makefile.am b/tests/zfs-tests/tests/functional/projectquota/Makefile.am new file mode 100644 index 000000000000..4abfda0d2a81 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/Makefile.am @@ -0,0 +1,25 @@ +pkgdatadir = $(datadir)/@PACKAGE@/zfs-tests/tests/functional/projectquota +dist_pkgdata_SCRIPTS = \ + projectquota.cfg \ + projectquota_common.kshlib \ + setup.ksh \ + cleanup.ksh \ + projectid_001_pos.ksh \ + projectid_002_pos.ksh \ + projectid_003_pos.ksh \ + projectquota_001_pos.ksh \ + projectquota_002_pos.ksh \ + projectquota_003_pos.ksh \ + projectquota_004_neg.ksh \ + projectquota_005_pos.ksh \ + projectquota_006_pos.ksh \ + projectquota_007_pos.ksh \ + projectquota_008_pos.ksh \ + projectquota_009_pos.ksh \ + projectspace_001_pos.ksh \ + projectspace_002_pos.ksh \ + projectspace_003_pos.ksh \ + projectspace_004_pos.ksh \ + projecttree_001_pos.ksh \ + projecttree_002_pos.ksh \ + projecttree_003_neg.ksh diff --git a/tests/zfs-tests/tests/functional/projectquota/cleanup.ksh b/tests/zfs-tests/tests/functional/projectquota/cleanup.ksh new file mode 100755 index 000000000000..0440e3d8af8c --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/cleanup.ksh @@ -0,0 +1,37 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +log_must cleanup_projectquota +log_must del_user $PUSER +log_must del_group $PGROUP +default_cleanup diff --git a/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..5f56d885caf3 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectid_001_pos.ksh @@ -0,0 +1,103 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# +# DESCRIPTION: +# Check project ID/flags can be set/inherited properly +# +# +# STRATEGY: +# 1. Create a regular file and a directroy. +# 2. Set project ID on both directroy and regular file. +# 3. New created subdir or regular file should inherit its parent's +# project ID if its parent has project inherit flag. +# 4. New created subdir should inherit its parent project's inherit flag. +# + +function cleanup +{ + log_must rm -f $PRJFILE + log_must rm -rf $PRJDIR +} + +if ! lsattr -pd > /dev/null 2>&1; then + log_unsupported "Current e2fsprogs does not support set/show project ID" +fi + +log_onexit cleanup + +log_assert "Check project ID/flags can be set/inherited properly" + +log_must touch $PRJFILE +log_must mkdir $PRJDIR + +log_must chattr -p $PRJID1 $PRJFILE +log_must eval "lsattr -p $PRJFILE | grep $PRJID1 | grep '\- '" +log_must chattr -p $PRJID1 $PRJDIR +log_must eval "lsattr -pd $PRJDIR | grep $PRJID1 | grep '\- '" + +log_must chattr +P $PRJDIR +log_must eval "lsattr -pd $PRJDIR | grep $PRJID1 | grep '\-P '" + +# "-1" is invalid project ID, should be denied +log_mustnot chattr -p -1 $PRJFILE +log_must eval "lsattr -p $PRJFILE | grep $PRJID1 | grep '\- '" + +log_must mkdir $PRJDIR/dchild +log_must eval "lsattr -pd $PRJDIR/dchild | grep $PRJID1 | grep '\-P '" +log_must touch $PRJDIR/fchild +log_must eval "lsattr -p $PRJDIR/fchild | grep $PRJID1" + +log_must touch $PRJDIR/dchild/foo +log_must eval "lsattr -p $PRJDIR/dchild/foo | grep $PRJID1" + +# not support project ID/flag on symlink +log_must ln -s $PRJDIR/dchild/foo $PRJDIR/dchild/s_foo +log_mustnot lsattr -p $PRJDIR/dchild/s_foo +log_mustnot chattr -p 123 $PRJDIR/dchild/s_foo +log_mustnot chattr +P $PRJDIR/dchild/s_foo + +# not support project ID/flag on block special file +log_must mknod $PRJDIR/dchild/b_foo b 124 124 +log_mustnot lsattr -p $PRJDIR/dchild/b_foo +log_mustnot chattr -p 123 $PRJDIR/dchild/b_foo +log_mustnot chattr +P $PRJDIR/dchild/b_foo + +# not support project ID/flag on character special file +log_must mknod $PRJDIR/dchild/c_foo c 125 125 +log_mustnot lsattr -p $PRJDIR/dchild/c_foo +log_mustnot chattr -p 123 $PRJDIR/dchild/c_foo +log_mustnot chattr +P $PRJDIR/dchild/c_foo + +log_pass "Check project ID/flags can be set/inherited properly" diff --git a/tests/zfs-tests/tests/functional/projectquota/projectid_002_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectid_002_pos.ksh new file mode 100755 index 000000000000..1a402e298b99 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectid_002_pos.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/tests/functional/projectquota/projectquota_common.kshlib + +# +# +# DESCRIPTION: +# Project ID affects POSIX behavior +# +# +# STRATEGY: +# 1. Create three directories +# 2. Set tdir1 and tdir3 project ID as PRJID1, +# set tdir2 project ID as PRJID2. +# 3. Create regular file under tdir1. It inherits tdir1 proejct ID. +# 4. Hardlink from tdir1's child to tdir2 should be denied, +# move tdir1's child to tdir2 will be object recreated. +# 5. Hardlink from tdir1's child to tdir3 should succeed. +# + +function cleanup +{ + log_must rm -rf $PRJDIR1 + log_must rm -rf $PRJDIR2 + log_must rm -rf $PRJDIR3 +} + +if ! lsattr -pd > /dev/null 2>&1; then + log_unsupported "Current e2fsprogs does not support set/show project ID" +fi + +log_onexit cleanup + +log_assert "Project ID affects POSIX behavior" + +log_must mkdir $PRJDIR1 +log_must mkdir $PRJDIR2 +log_must mkdir $PRJDIR3 +log_must mkdir $PRJDIR3/dir + +log_must chattr +P -p $PRJID1 $PRJDIR1 +log_must chattr +P -p $PRJID2 $PRJDIR2 + +log_must touch $PRJDIR1/tfile1 +log_must touch $PRJDIR1/tfile2 +log_must eval "lsattr -p $PRJDIR1/tfile1 | grep $PRJID1" + +log_mustnot ln $PRJDIR1/tfile1 $PRJDIR2/tfile2 + +log_must mv $PRJDIR1/tfile1 $PRJDIR2/tfile2 +log_must eval "lsattr -p $PRJDIR2/tfile2 | grep $PRJID2" + +log_must mv $PRJDIR3/dir $PRJDIR2/ +log_must eval "lsattr -dp $PRJDIR2/dir | grep $PRJID2" + +log_must chattr +P -p $PRJID1 $PRJDIR3 +log_must ln $PRJDIR1/tfile2 $PRJDIR3/tfile3 + +log_pass "Project ID affects POSIX behavior" diff --git a/tests/zfs-tests/tests/functional/projectquota/projectid_003_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectid_003_pos.ksh new file mode 100755 index 000000000000..d6dbaafc217a --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectid_003_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/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check changing project ID for the file with directory-based +# extended attributes. +# +# +# STRATEGY: +# 1. create new file with default project ID +# 2. set non-ACL extended attributes on the file +# 3. use zfs projectspace to check the object usage +# 4. change the file's project ID +# 5. use zfs projectspace to check the object usage again +# + +function cleanup +{ + log_must rm -f $PRJGUARD + log_must rm -f $PRJFILE +} + +if ! lsattr -pd > /dev/null 2>&1; then + log_unsupported "Current e2fsprogs does not support set/show project ID" +fi + +log_onexit cleanup + +log_assert "Check changing project ID with directory-based extended attributes" + +log_must zfs set xattr=on $QFS + +log_must touch $PRJGUARD +log_must chattr -p $PRJID1 $PRJGUARD +log_must touch $PRJFILE +log_must setfattr -n trusted.ea1 -v val1 $PRJFILE +log_must setfattr -n trusted.ea2 -v val2 $PRJFILE +log_must setfattr -n trusted.ea3 -v val3 $PRJFILE + +sync_pool +typeset prj_bef=$(project_obj_count $QFS $PRJID1) + +log_must chattr -p $PRJID1 $PRJFILE +sync_pool +typeset prj_aft=$(project_obj_count $QFS $PRJID1) + +[[ $prj_aft -ge $((prj_bef + 5)) ]] || + log_fail "new value ($prj_aft) is NOT 5 largr than old one ($prj_bef)" + +log_pass "Changing project ID with directory-based extended attributes pass" diff --git a/tests/zfs-tests/tests/functional/projectquota/projectquota.cfg b/tests/zfs-tests/tests/functional/projectquota/projectquota.cfg new file mode 100644 index 000000000000..564ab3ef9698 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota.cfg @@ -0,0 +1,46 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +export PUSER=puser +export PGROUP=pgroup + +export PRJID1=1001 +export PRJID2=1002 + +export QFS=$TESTPOOL/$TESTFS +export PRJFILE=$TESTDIR/tfile +export PRJGUARD=$TESTDIR/guard +export PRJDIR=$TESTDIR/tdir +export PRJDIR1=$TESTDIR/tdir1 +export PRJDIR2=$TESTDIR/tdir2 +export PRJDIR3=$TESTDIR/tdir3 + +export PQUOTA_LIMIT=1000000 +export PQUOTA_OBJLIMIT=1000 diff --git a/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..3f8c3d68ce3e --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_001_pos.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/tests/functional/projectquota/projectquota_common.kshlib + +# +# +# DESCRIPTION: +# Check the basic function of the project{obj}quota +# +# +# STRATEGY: +# 1. Set projectquota and overwrite the quota size. +# 2. The write operation should fail with Disc quota exceeded +# 3. Set projectobjquota and overcreate the quota size. +# 4. More create should fail with Disc quota exceeded +# 5. More chattr to such project should fail with Disc quota exceeded +# + +function cleanup +{ + cleanup_projectquota +} + +if ! lsattr -pd > /dev/null 2>&1; then + log_unsupported "Current e2fsprogs does not support set/show project ID" +fi + +log_onexit cleanup + +log_assert "If operation overwrite project{obj}quota size, it will fail" + +mkmount_writable $QFS + +log_note "Check the projectquota@$PRJID1" +log_must user_run $PUSER mkdir $PRJDIR +log_must chattr +P -p $PRJID1 $PRJDIR + +log_must zfs set projectquota@$PRJID1=$PQUOTA_LIMIT $QFS +log_must user_run $PUSER mkfile $PQUOTA_LIMIT $PRJDIR/qf +sync_pool +log_mustnot user_run $PUSER mkfile 1 $PRJDIR/of + +log_must rm -rf $PRJDIR + +log_note "Check the projectobjquota@$PRJID2" +log_must zfs set xattr=sa $QFS +log_must user_run $PUSER mkdir $PRJDIR +log_must chattr +P -p $PRJID2 $PRJDIR + +log_must zfs set projectobjquota@$PRJID2=$PQUOTA_OBJLIMIT $QFS +log_must user_run $PUSER mkfiles $PRJDIR/qf_ $((PQUOTA_OBJLIMIT - 1)) +sync_pool +log_mustnot user_run $PUSER mkfile 1 $PRJDIR/of + +log_must user_run $PUSER touch $PRJFILE +log_must user_run $PUSER chattr -p 123 $PRJFILE +log_mustnot user_run $PUSER chattr -p $PRJID2 $PRJFILE + +log_pass "Operation overwrite project{obj}quota size failed as expect" diff --git a/tests/zfs-tests/tests/functional/projectquota/projectquota_002_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectquota_002_pos.ksh new file mode 100755 index 000000000000..c03619060b0f --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_002_pos.ksh @@ -0,0 +1,86 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# The project{obj}quota can be set during zpool or zfs creation +# +# +# STRATEGY: +# 1. Set project{obj}quota via "zpool -O or zfs create -o" +# + +verify_runnable "global" + +function cleanup +{ + if poolexists $TESTPOOL1; then + log_must zpool destroy $TESTPOOL1 + fi + + if [[ -f $pool_vdev ]]; then + rm -f $pool_vdev + fi +} + +log_onexit cleanup + +log_assert "The project{obj}quota can be set during zpool,zfs creation" + +typeset pool_vdev=/var/tmp/pool_dev.$$ + +log_must mkfile 500m $pool_vdev + +if poolexists $TESTPOOL1; then + zpool destroy $TESTPOOL1 +fi + +log_must zpool create -O projectquota@$PRJID1=$PQUOTA_LIMIT \ + -O projectobjquota@$PRJID2=$PQUOTA_OBJLIMIT $TESTPOOL1 $pool_vdev + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL1 > /dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL1 "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL1 "$PQUOTA_OBJLIMIT" + +log_must zfs create -o projectquota@$PRJID1=$PQUOTA_LIMIT \ + -o projectobjquota@$PRJID2=$PQUOTA_OBJLIMIT $TESTPOOL1/fs + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL1 > /dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL1/fs "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL1/fs "$PQUOTA_OBJLIMIT" + +log_pass "The project{obj}quota can be set during zpool,zfs creation" diff --git a/tests/zfs-tests/tests/functional/projectquota/projectquota_003_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectquota_003_pos.ksh new file mode 100755 index 000000000000..06f360d30b2b --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_003_pos.ksh @@ -0,0 +1,98 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the basic function project{obj}used +# +# +# STRATEGY: +# 1. Write data to fs with some project then check the project{obj}used +# + +function cleanup +{ + cleanup_projectquota +} + +if ! lsattr -pd > /dev/null 2>&1; then + log_unsupported "Current e2fsprogs does not support set/show project ID" +fi + +log_onexit cleanup + +log_assert "Check the basic function of project{obj}used" + +sync_pool +typeset project_used=$(get_value "projectused@$PRJID1" $QFS) +typeset file_size='10m' + +if [[ $project_used != 0 ]]; then + log_fail "FAIL: projectused is $project_used, should be 0" +fi + +mkmount_writable $QFS +log_must user_run $PUSER mkdir $PRJDIR +log_must chattr +P -p $PRJID1 $PRJDIR +log_must user_run $PUSER mkfile $file_size $PRJDIR/qf +sync_pool +project_used=$(get_value "projectused@$PRJID1" $QFS) +# get_value() reads the exact byte value which is slightly more than 10m +if [[ "$(($project_used/1024/1024))m" != "$file_size" ]]; then + log_note "project $PRJID1 used is $project_used" + log_fail "projectused for project $PRJID1 expected to be $file_size, " \ + "not $project_used" +fi + +log_must rm -rf $PRJDIR +typeset project_obj_used=$(get_value "projectobjused@$PRJID2" $QFS) +typeset file_count=100 + +if [[ $project_obj_used != 0 ]]; then + log_fail "FAIL: projectobjused is $project_obj_used, should be 0" +fi + +log_must zfs set xattr=sa $QFS +log_must user_run $PUSER mkdir $PRJDIR +log_must chattr +P -p $PRJID2 $PRJDIR +# $PRJDIR has already used one object with the $PRJID2 +log_must user_run $PUSER mkfiles $PRJDIR/qf_ $((file_count - 1)) +sync_pool +project_obj_used=$(get_value "projectobjused@$PRJID2" $QFS) +if [[ $project_obj_used != $file_count ]]; then + log_note "project $PRJID2 used is $project_obj_used" + log_fail "projectobjused for project $PRJID2 expected to be " \ + "$file_count, not $project_obj_used" +fi + +log_pass "Check the basic function of project{obj}used pass as expect" diff --git a/tests/zfs-tests/tests/functional/projectquota/projectquota_004_neg.ksh b/tests/zfs-tests/tests/functional/projectquota/projectquota_004_neg.ksh new file mode 100755 index 000000000000..df0eda7d770a --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_004_neg.ksh @@ -0,0 +1,87 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the invalid parameter of zfs set project{obj}quota +# +# +# STRATEGY: +# 1. check the invalid zfs set project{obj}quota to fs +# 2. check the valid zfs set project{obj}quota to snapshots +# + +function cleanup +{ + if datasetexists $snap_fs; then + log_must zfs destroy $snap_fs + fi + + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check the invalid parameter of zfs set project{obj}quota" +typeset snap_fs=$QFS@snap + +log_must zfs snapshot $snap_fs + +set -A no_prjs "mms1234" "ss@#" "root-122" "-1" +for prj in "${no_prjs[@]}"; do + log_mustnot zfs set projectquota@$prj=100m $QFS +done + +log_note "can set all 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..b52f302f7892 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_005_pos.ksh @@ -0,0 +1,68 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the invalid parameter of zfs get project{obj}quota +# +# +# STRATEGY: +# 1. check the invalid zfs get project{obj}quota to fs +# 2. check the valid zfs get project{obj}quota to snapshots +# + +function cleanup +{ + if datasetexists $snap_fs; then + log_must zfs destroy $snap_fs + fi + + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check the invalid parameter of zfs get project{obj}quota" +typeset snap_fs=$QFS@snap + +log_must zfs snapshot $snap_fs + +set -A no_prjs "mms1234" "ss@#" "root-122" +for prj in "${no_prjs[@]}"; do + log_must eval "zfs get projectquota@$prj $QFS >/dev/null 2>&1" + log_must eval "zfs get projectquota@$prj $snap_fs >/dev/null 2>&1" + log_must eval "zfs get projectobjquota@$prj $QFS >/dev/null 2>&1" + log_must eval "zfs get projectobjquota@$prj $snap_fs >/dev/null 2>&1" +done + +log_pass "Check the invalid parameter of zfs get project{obj}quota" diff --git a/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..6b375d407e23 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_006_pos.ksh @@ -0,0 +1,75 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Projectquota can be set beyond the fs quota. +# Pprojectquota can be set at a smaller size than its current usage. +# +# STRATEGY: +# 1. set quota to a fs and set a larger size of projectquota +# 2. write some data to the fs and set a smaller projectquota +# + +function cleanup +{ + log_must cleanup_projectquota + log_must zfs set quota=none $QFS +} + +if ! lsattr -pd > /dev/null 2>&1; then + log_unsupported "Current e2fsprogs does not support set/show project ID" +fi + +log_onexit cleanup + +log_assert "Check set projectquota to larger than the quota size of a fs" + +log_must zfs set quota=200m $QFS +log_must zfs set projectquota@$PRJID1=500m $QFS + +log_must zfs get projectquota@$PRJID1 $QFS + +log_note "write some data to the $QFS" +mkmount_writable $QFS +log_must user_run $PUSER mkdir $PRJDIR +log_must chattr +P -p $PRJID1 $PRJDIR +log_must user_run $PUSER mkfile 100m $PRJDIR/qf +sync + +log_note "set projectquota at a smaller size than it current usage" +log_must zfs set projectquota@$PRJID1=90m $QFS + +log_must zfs get projectquota@$PRJID1 $QFS + +log_pass "set projectquota to larger than quota size of a fs" diff --git a/tests/zfs-tests/tests/functional/projectquota/projectquota_007_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectquota_007_pos.ksh new file mode 100755 index 000000000000..3572e0118f45 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_007_pos.ksh @@ -0,0 +1,58 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# zfs get all <fs> does not print out project{obj}quota +# +# STRATEGY: +# 1. set project{obj}quota to a fs +# 2. check zfs get all fs +# + +function cleanup +{ + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check zfs get all will not print out project{obj}quota" + +log_must zfs set projectquota@$PRJID1=50m $QFS +log_must zfs set projectobjquota@$PRJID2=100 $QFS + +log_mustnot eval "zfs get all $QFS | grep projectquota" +log_mustnot eval "zfs get all $QFS | grep projectobjquota" + +log_pass "zfs get all will not print out project{obj}quota" diff --git a/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..365b5627e801 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_008_pos.ksh @@ -0,0 +1,91 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check project{obj}quota to snapshot that: +# 1) can not set project{obj}quota to snapshot directly +# 2) snapshot can inherit the parent fs's project{obj}quota +# 3) the project{obj}quota will not change even the parent quota changed. +# +# +# STRATEGY: +# 1. create a snapshot of a fs +# 2. set the project{obj}quota to snapshot and expect fail +# 3. set project{obj}quota to fs and check the snapshot +# 4. re-set project{obj}quota to fs and check the snapshot's value +# + +function cleanup +{ + if datasetexists $snap_fs; then + log_must zfs destroy $snap_fs + fi + + log_must cleanup_projectquota +} + +log_onexit cleanup + +log_assert "Check the snapshot's project{obj}quota" +typeset snap_fs=$QFS@snap + + +log_must zfs set projectquota@$PRJID1=$PQUOTA_LIMIT $QFS +log_must check_quota "projectquota@$PRJID1" $QFS "$PQUOTA_LIMIT" + +log_must zfs set projectobjquota@$PRJID2=$PQUOTA_OBJLIMIT $QFS +log_must check_quota "projectobjquota@$PRJID2" $QFS "$PQUOTA_OBJLIMIT" + +log_must zfs snapshot $snap_fs + +log_note "check the snapshot $snap_fs project{obj}quota" +log_must check_quota "projectquota@$PRJID1" $snap_fs "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $snap_fs "$PQUOTA_OBJLIMIT" + +log_note "set project{obj}quota to $snap_fs which will fail" +log_mustnot zfs set projectquota@$PRJID1=100m $snap_fs +log_mustnot zfs set projectobjquota@$PRJID2=100 $snap_fs + +log_note "change the parent's project{obj}quota" +log_must zfs set projectquota@$PRJID1=$((PQUOTA_LIMIT * 2)) $QFS +log_must zfs set projectobjquota@$PRJID2=50 $QFS + +log_must check_quota "projectquota@$PRJID1" $QFS $((PQUOTA_LIMIT * 2)) +log_must check_quota "projectobjquota@$PRJID2" $QFS 50 + +log_note "check the snapshot $snap_fs project{obj}quota" +log_must check_quota "projectquota@$PRJID1" $snap_fs "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $snap_fs "$PQUOTA_OBJLIMIT" + +log_pass "Check the snapshot's project{obj}quota" diff --git a/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..a867b538c120 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_009_pos.ksh @@ -0,0 +1,131 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# The project{obj}quota will not change during zfs actions, such as +# snapshot,clone,rename,upgrade,send,receive. +# +# +# STRATEGY: +# 1. Create a pool, and create fs with preset project{obj}quota +# 2. Check set project{obj}quota via zfs snapshot|clone|list -o +# 3. Check the project{obj}quota can not change during zfs +# rename|upgrade|promote +# 4. Check the project{obj}quota can not change during zfs clone +# 5. Check the project{obj}quota can not change during zfs send/receive +# + +function cleanup +{ + for ds in $TESTPOOL/fs $TESTPOOL/fs-rename $TESTPOOL/fs-clone; do + if datasetexists $ds; then + log_must zfs destroy -rRf $ds + fi + done +} + +log_onexit cleanup + +log_assert "the project{obj}quota can't change during zfs actions" + +cleanup + +log_must zfs create -o projectquota@$PRJID1=$PQUOTA_LIMIT \ + -o projectobjquota@$PRJID2=$PQUOTA_OBJLIMIT $TESTPOOL/fs + +log_must zfs snapshot $TESTPOOL/fs@snap +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL >/dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL/fs@snap "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL/fs@snap \ + "$PQUOTA_OBJLIMIT" + + +log_note "clone fs gets its parent's project{obj}quota initially" +log_must zfs clone -o projectquota@$PRJID1=$PQUOTA_LIMIT \ + -o projectobjquota@$PRJID2=$PQUOTA_OBJLIMIT \ + $TESTPOOL/fs@snap $TESTPOOL/fs-clone + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL >/dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL/fs-clone "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL/fs-clone \ + "$PQUOTA_OBJLIMIT" + +log_must eval "zfs list -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL/fs-clone >/dev/null 2>&1" + +log_note "zfs promote can not change the previously set project{obj}quota" +log_must zfs promote $TESTPOOL/fs-clone + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL >/dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL/fs-clone "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL/fs-clone \ + "$PQUOTA_OBJLIMIT" + +log_note "zfs send receive can not change the previously set project{obj}quota" +log_must zfs send $TESTPOOL/fs-clone@snap | zfs receive $TESTPOOL/fs-rev + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL >/dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL/fs-rev "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL/fs-rev \ + "$PQUOTA_OBJLIMIT" + +log_note "zfs rename can not change the previously set project{obj}quota" +log_must zfs rename $TESTPOOL/fs-rev $TESTPOOL/fs-rename + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL >/dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL/fs-rename "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL/fs-rename \ + "$PQUOTA_OBJLIMIT" + +log_note "zfs upgrade can not change the previously set project{obj}quota" +log_must zfs upgrade $TESTPOOL/fs-rename + +log_must eval "zfs list -r -o projectquota@$PRJID1,projectobjquota@$PRJID2 \ + $TESTPOOL >/dev/null 2>&1" + +log_must check_quota "projectquota@$PRJID1" $TESTPOOL/fs-rename "$PQUOTA_LIMIT" +log_must check_quota "projectobjquota@$PRJID2" $TESTPOOL/fs-rename \ + "$PQUOTA_OBJLIMIT" + +log_pass "the project{obj}quota can't change during zfs actions" diff --git a/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..23f7c2a50663 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectquota_common.kshlib @@ -0,0 +1,101 @@ +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib +. $STF_SUITE/tests/functional/projectquota/projectquota.cfg + +# +# reset the projectquota and delete temporary files +# +function cleanup_projectquota +{ + if datasetexists $QFS; then + typeset mntp=$(get_prop mountpoint $QFS) + + log_must zfs set projectquota@$PRJID1=none $QFS + log_must zfs set projectobjquota@$PRJID1=none $QFS + log_must zfs set projectquota@$PRJID2=none $QFS + log_must zfs set projectobjquota@$PRJID2=none $QFS + log_must chmod 0755 $mntp + fi + + [[ -f $PRJFILE ]] && log_must rm -f $PRJFILE + [[ -d $PRJDIR ]] && log_must rm -rf $PRJDIR + [[ -d $PRJDIR1 ]] && log_must rm -rf $PRJDIR1 + [[ -d $PRJDIR2 ]] && log_must rm -rf $PRJDIR2 + [[ -d $PRJDIR3 ]] && log_must rm -rf $PRJDIR3 + sync + + return 0 +} + +function mkmount_writable +{ + typeset fs=$1 + typeset mntp=$(get_prop mountpoint $fs) + log_must chmod 0777 $mntp +} + +function check_quota +{ + typeset fs=$2 + typeset prop=$1 + typeset expected=$3 + typeset value=$(get_prop $prop $fs) + + if (($value != $expected)); then + return 1 + fi +} + +function get_value +{ + typeset prop_val + typeset prop=$1 + typeset dataset=$2 + + prop_val=$(zfs get -H -p -o value $prop $dataset 2>/dev/null) + if [[ $? -ne 0 ]]; then + log_note "Unable to get $prop property for dataset $dataset" + return 1 + fi + + echo $prop_val +} + +function project_obj_count +{ + typeset fs=$1 + typeset prj=$2 + typeset cnt=$(zfs projectspace -oname,objused $fs | + awk /$prj/'{print $2}') + [[ "$cnt" == "-" ]] && cnt=0 || true + echo $cnt +} diff --git a/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..a84ff9f89a0d --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectspace_001_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. Fan rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the zfs projectspace with kinds of parameters +# +# +# STRATEGY: +# 1. set zfs projectspace to a fs +# 2. write some data to the fs with specified project ID +# 3. use zfs projectspace with all possible parameters to check the result +# 4. use zfs projectspace with some bad parameters to check the result +# + +function cleanup +{ + if datasetexists $snap_fs; then + log_must zfs destroy $snap_fs + fi + + log_must cleanup_projectquota +} + +if ! lsattr -pd > /dev/null 2>&1; then + log_unsupported "Current e2fsprogs does not support set/show project ID" +fi + +log_onexit cleanup + +log_assert "Check the zfs projectspace with all possible parameters" + +set -A good_params -- "-H" "-p" "-o type,name,used,quota" "-o name,used,quota" \ + "-o used,quota" "-o objused" "-o quota" "-s type" "-s name" "-s used" \ + "-s quota" "-S type" "-S name" "-S used" "-S quota" + +typeset snap_fs=$QFS@snap + +log_must zfs set projectquota@$PRJID1=100m $QFS +log_must zfs set projectobjquota@$PRJID1=100 $QFS +mkmount_writable $QFS +log_must user_run $PUSER mkdir $PRJDIR +log_must chattr +P -p $PRJID1 $PRJDIR +log_must user_run $PUSER mkfile 50m $PRJDIR/qf +sync + +log_must zfs snapshot $snap_fs + +for param in "${good_params[@]}"; do + log_must eval "zfs projectspace $param $QFS >/dev/null 2>&1" + log_must eval "zfs projectspace $param $snap_fs >/dev/null 2>&1" +done + +log_assert "Check the zfs projectspace with some bad parameters" + +set -A bad_params -- "-i" "-n" "-P" "-t posixuser" + +for param in "${bad_params[@]}"; do + log_mustnot eval "zfs projectspace $param $QFS >/dev/null 2>&1" + log_mustnot eval "zfs projectspace $param $snap_fs >/dev/null 2>&1" +done + +log_pass "zfs projectspace with kinds of parameters pass" diff --git a/tests/zfs-tests/tests/functional/projectquota/projectspace_002_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectspace_002_pos.ksh new file mode 100755 index 000000000000..216855e94dc9 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectspace_002_pos.ksh @@ -0,0 +1,85 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the project used size and quota in zfs projectspace +# +# +# STRATEGY: +# 1. set zfs projectquota to a fs +# 2. write some data to the fs with specified project and size +# 3. use zfs projectspace to check the used size and quota size +# + +function cleanup +{ + if datasetexists $snapfs; then + log_must zfs destroy $snapfs + fi + + log_must cleanup_projectquota +} + +if ! lsattr -pd > /dev/null 2>&1; then + log_unsupported "Current e2fsprogs does not support set/show project ID" +fi + +log_onexit cleanup + +log_assert "Check the zfs projectspace used and quota" + +log_must zfs set projectquota@$PRJID1=100m $QFS + +mkmount_writable $QFS +log_must user_run $PUSER mkdir $PRJDIR +log_must chattr +P -p $PRJID1 $PRJDIR +log_must user_run $PUSER mkfile 50m $PRJDIR/qf +sync + +typeset snapfs=$QFS@snap + +log_must zfs snapshot $snapfs + +log_must eval "zfs projectspace $QFS >/dev/null 2>&1" +log_must eval "zfs projectspace $snapfs >/dev/null 2>&1" + +for fs in "$QFS" "$snapfs"; do + log_note "check the quota size in zfs projectspace $fs" + log_must eval "zfs projectspace $fs | grep $PRJID1 | grep 100M" + + log_note "check the project used size in zfs projectspace $fs" + log_must eval "zfs projectspace $fs | grep $PRJID1 | grep 50\\.\*M" +done + +log_pass "Check the zfs projectspace used and quota" diff --git a/tests/zfs-tests/tests/functional/projectquota/projectspace_003_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectspace_003_pos.ksh new file mode 100755 index 000000000000..629b3b3e57e2 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectspace_003_pos.ksh @@ -0,0 +1,118 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check the project used object accounting in zfs projectspace +# +# +# STRATEGY: +# 1. create a bunch of files by specific project +# 2. use zfs projectspace to check the used objects +# 3. change the project ID of test files and verify object count +# 4. delete files and verify object count +# + +function cleanup +{ + if datasetexists $snapfs; then + log_must zfs destroy $snapfs + fi + + log_must cleanup_projectquota +} + +if ! lsattr -pd > /dev/null 2>&1; then + log_unsupported "Current e2fsprogs does not support set/show project ID" +fi + +log_onexit cleanup + +log_assert "Check the zfs projectspace object used" + +mkmount_writable $QFS +log_must zfs set xattr=sa $QFS +log_must user_run $PUSER mkdir $PRJDIR1 +log_must user_run $PUSER mkdir $PRJDIR2 +log_must chattr +P -p $PRJID1 $PRJDIR1 +log_must chattr +P -p $PRJID2 $PRJDIR2 + +((prj_cnt1 = RANDOM % 100 + 2)) +((prj_cnt2 = RANDOM % 100 + 2)) + +log_must user_run $PUSER mkfiles $PRJDIR1/qf $((prj_cnt1 - 1)) +log_must user_run $PUSER mkfiles $PRJDIR2/qf $((prj_cnt2 - 1)) +sync_pool + +typeset snapfs=$QFS@snap + +log_must zfs snapshot $snapfs + +log_must eval "zfs projectspace $QFS >/dev/null 2>&1" +log_must eval "zfs projectspace $snapfs >/dev/null 2>&1" + +for fs in "$QFS" "$snapfs"; do + log_note "check the project used objects in zfs projectspace $fs" + prjused=$(project_obj_count $fs $PRJID1) + [[ $prjused -eq $prj_cnt1 ]] || + log_fail "($PRJID1) expected $prj_cnt1, got $prjused" + prjused=$(project_obj_count $fs $PRJID2) + [[ $prjused -eq $prj_cnt2 ]] || + log_fail "($PRJID2) expected $prj_cnt2, got $prjused" +done + +log_note "change the project of files" +log_must chattr -p $PRJID2 $PRJDIR1/qf* +sync_pool + +prjused=$(project_obj_count $QFS $PRJID1) +[[ $prjused -eq 1 ]] || + log_fail "expected 1 for project $PRJID1, got $prjused" + +prjused=$(project_obj_count $snapfs $PRJID1) +[[ $prjused -eq $prj_cnt1 ]] || + log_fail "expected $prj_cnt1 for $PRJID1 in snapfs, got $prjused" + +prjused=$(project_obj_count $QFS $PRJID2) +[[ $prjused -eq $((prj_cnt1 + prj_cnt2 - 1)) ]] || + log_fail "($PRJID2) expected $((prj_cnt1 + prj_cnt2 - 1)), got $prjused" + +log_note "file removal" +log_must rm -rf $PRJDIR1 +sync_pool + +prjused=$(project_obj_count $QFS $PRJID1) +[[ $prjused -eq 0 ]] || log_fail "expected 0 for $PRJID1, got $prjused" + +cleanup +log_pass "Check the zfs projectspace object used" diff --git a/tests/zfs-tests/tests/functional/projectquota/projectspace_004_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projectspace_004_pos.ksh new file mode 100755 index 000000000000..494d7f3b7ac0 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projectspace_004_pos.ksh @@ -0,0 +1,76 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. Fan rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# DESCRIPTION: +# Check 'df' command on the directory with INHERIT (project ID) flag +# +# +# STRATEGY: +# 1. set project [obj]quota on the directory +# 2. set project ID and inherit flag on the directoty +# 3. run 'df [-i]' on the directory and check the result +# + +function cleanup +{ + if datasetexists $snap_fs; then + log_must zfs destroy $snap_fs + fi + + log_must cleanup_projectquota +} + +if ! lsattr -pd > /dev/null 2>&1; then + log_unsupported "Current e2fsprogs does not support set/show project ID" +fi + +log_onexit cleanup + +log_assert "Check 'df' on dir with inherit project shows the project quota/used" + +log_must zfs set projectquota@$PRJID1=100m $QFS +log_must zfs set projectobjquota@$PRJID1=100 $QFS +mkmount_writable $QFS +log_must user_run $PUSER mkdir $PRJDIR +log_must chattr +P -p $PRJID1 $PRJDIR +log_must user_run $PUSER mkfile 50m $PRJDIR/qf +sync_pool + +total=$(df $PRJDIR | tail -n 1 | awk '{ print $2 }') +[[ $total -eq 102400 ]] || log_fail "expect '102400' resource, but got '$total'" + +used=$(df -i $PRJDIR | tail -n 1 | awk '{ print $5 }') +[[ "$used" == "2%" ]] || log_fail "expect '2%' used, but got '$used'" + +log_pass "'df' on the directory with inherit project ID flag pass as expect" diff --git a/tests/zfs-tests/tests/functional/projectquota/projecttree_001_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projecttree_001_pos.ksh new file mode 100755 index 000000000000..570e6a8acffa --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projecttree_001_pos.ksh @@ -0,0 +1,98 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# +# DESCRIPTION: +# Check 'zfs project' is compatible with chattr/lsattr +# +# +# STRATEGY: +# Verify the following: +# 1. "zfs project -p" behaviours the same as "chattr -p" +# 2. "zfs project" behaviours the same as "lsattr -p" +# 3. "zfs project -d" behaviours the same as "lsattr -p -d" +# 4. "zfs project -s" behaviours the same as "chattr +P" +# 5. "zfs project -s -p" behaviours the same as "chattr +P -p" +# 6. "zfs project -C" behaviours the same as "chattr -P" +# + +function cleanup +{ + log_must rm -rf $PRJDIR +} + +if ! lsattr -pd > /dev/null 2>&1; then + log_unsupported "Current e2fsprogs does not support set/show project ID" +fi + +log_onexit cleanup + +log_assert "Check 'zfs project' is compatible with chattr/lsattr" + +log_must mkdir $PRJDIR +log_must mkdir $PRJDIR/a1 +log_must mkdir $PRJDIR/a2 +log_must touch $PRJDIR/a3 + +log_must chattr -p $PRJID1 $PRJDIR/a3 +log_must eval "zfs project $PRJDIR/a3 | grep '$PRJID1 \-'" + +log_must zfs project -p $PRJID2 $PRJDIR/a3 +log_must eval "lsattr -p $PRJDIR/a3 | grep $PRJID2 | grep '\- '" + +log_must chattr -p $PRJID1 $PRJDIR/a1 +log_must eval "zfs project -d $PRJDIR/a1 | grep '$PRJID1 \-'" + +log_must zfs project -p $PRJID2 $PRJDIR/a1 +log_must eval "lsattr -pd $PRJDIR/a1 | grep $PRJID2 | grep '\- '" + +log_must chattr +P $PRJDIR/a2 +log_must eval "zfs project -d $PRJDIR/a2 | grep '0 P'" + +log_must zfs project -s $PRJDIR/a2 +log_must eval "lsattr -pd $PRJDIR/a2 | grep 0 | grep '\-P '" + +log_must chattr +P -p $PRJID1 $PRJDIR/a1 +log_must eval "zfs project -d $PRJDIR/a1 | grep '$PRJID1 P'" + +log_must zfs project -s -p $PRJID2 $PRJDIR/a2 +log_must eval "lsattr -pd $PRJDIR/a2 | grep $PRJID2 | grep '\-P '" + +log_must chattr -P $PRJDIR/a1 +log_must eval "zfs project -d $PRJDIR/a1 | grep '$PRJID1 \-'" + +log_must zfs project -C -k $PRJDIR/a2 +log_must eval "lsattr -pd $PRJDIR/a2 | grep $PRJID2 | grep '\- '" + +log_pass "Check 'zfs project' is compatible with chattr/lsattr" diff --git a/tests/zfs-tests/tests/functional/projectquota/projecttree_002_pos.ksh b/tests/zfs-tests/tests/functional/projectquota/projecttree_002_pos.ksh new file mode 100755 index 000000000000..4008811a19e1 --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projecttree_002_pos.ksh @@ -0,0 +1,120 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# +# DESCRIPTION: +# Check project ID/flag can be operated via "zfs project" +# +# +# STRATEGY: +# 1. Create a tree with 4 level directories. +# 2. Set project ID on both directroy and regular file via +# "zfs project -p". +# 3. Check the project ID via "zfs project". +# 4. Set project inherit flag on kinds of level directories (and its +# descendants for some)) via "zfs project -s [-r]". +# 5. Check the project ID and inherit flag via "zfs project -r". +# 6. Clear the project inherit flag from some directories (and its +# descendants for some) via "zfs project -C [-r]". +# 7. Check the project ID and inherit flag via "zfs project -r". +# + +function cleanup +{ + log_must rm -rf $PRJDIR +} + +log_onexit cleanup + +log_assert "Check project ID/flag can be operated via 'zfs project'" + +log_must mkdir $PRJDIR + +log_must mkdir $PRJDIR/a1 +log_must mkdir $PRJDIR/b1 +log_must touch $PRJDIR/c1 + +log_must mkdir $PRJDIR/a1/a2 +log_must mkdir $PRJDIR/a1/b2 +log_must touch $PRJDIR/a1/c2 + +log_must mkdir $PRJDIR/b1/a2 +log_must mkdir $PRJDIR/b1/b2 +log_must touch $PRJDIR/b1/c2 + +log_must mkdir $PRJDIR/a1/a2/a3 +log_must mkdir $PRJDIR/a1/a2/b3 +log_must touch $PRJDIR/a1/a2/c3 + +log_must mkdir $PRJDIR/b1/a2/a3 + +log_must touch $PRJDIR/a1/a2/a3/c4 +log_must touch $PRJDIR/a1/a2/a3/d4 + +log_must zfs project -p $PRJID1 $PRJDIR/a1/c2 +log_must eval "zfs project $PRJDIR/a1/c2 | grep $PRJID1" + +log_must zfs project -p $PRJID2 $PRJDIR/a1/a2/a3 +log_must eval "zfs project -d $PRJDIR/a1/a2/a3 | grep $PRJID2" + +log_must zfs project -s $PRJDIR/b1/a2 +log_must eval "zfs project -d $PRJDIR/b1/a2 | grep ' P '" +log_must eval "zfs project -d $PRJDIR/b1/a2/a3 | grep ' \- '" + +log_must zfs project -s -r -p $PRJID2 $PRJDIR/a1/a2 +log_must zfs project -c -r $PRJDIR/a1/a2 +log_must eval "zfs project -d $PRJDIR/a1/a2/a3 | grep ' P '" +log_must eval "zfs project $PRJDIR/a1/a2/a3/c4 | grep $PRJID2" + +log_must zfs project -C $PRJDIR/a1/a2/a3 +log_must eval "zfs project -cr $PRJDIR/a1/a2 | grep 'inherit flag is not set'" +log_must eval "zfs project $PRJDIR/a1/a2/a3/c4 | grep $PRJID2 | grep -v not" +log_must zfs project -p 123 $PRJDIR/a1/a2/a3/c4 +log_must eval "zfs project -c -r $PRJDIR/a1/a2 | grep 123 | grep 'not set'" +log_mustnot eval "zfs project -cr -p 123 $PRJDIR/a1/a2 | grep c4 | grep -v not" + +log_must zfs project -C -r $PRJDIR/a1/a2/a3 +log_must eval "zfs project -cr $PRJDIR/a1/a2 | grep a3 | grep 'not set'" +log_must eval "zfs project -cr $PRJDIR/a1/a2 | grep d4 | grep 'not set'" +log_must eval "zfs project $PRJDIR/a1/a2/a3/d4 | grep '0 \-'" + +log_must eval \ + "zfs project -cr -0 $PRJDIR/a1/a2 | xargs -0 zfs project -s -p $PRJID2" +log_mustnot eval "zfs project -cr $PRJDIR/a1/a2 | grep a3 | grep 'not set'" +log_mustnot eval "zfs project -cr $PRJDIR/a1/a2 | grep d4 | grep 'not set'" + +log_must zfs project -C -r -k $PRJDIR/a1/a2 +log_must eval "zfs project -d $PRJDIR/a1/a2/b3 | grep '$PRJID2 \- '" + +log_pass "Check project ID/flag can be operated via 'zfs project'" diff --git a/tests/zfs-tests/tests/functional/projectquota/projecttree_003_neg.ksh b/tests/zfs-tests/tests/functional/projectquota/projecttree_003_neg.ksh new file mode 100755 index 000000000000..33382fdbe92d --- /dev/null +++ b/tests/zfs-tests/tests/functional/projectquota/projecttree_003_neg.ksh @@ -0,0 +1,103 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/projectquota/projectquota_common.kshlib + +# +# +# DESCRIPTION: +# Check 'zfs project' invalid options combinations +# +# +# STRATEGY: +# Verify the following: +# 1. "-c" only supports "-d", "-p", "-r" and "-0". +# 2. "-C" only supports "-r" and "-k". +# 3. "-s" only supports "-r" and "-p". +# 4. "-c", "-C" and "-s" can NOT be specified together. +# 5. "-d" can overwirte former "-r". +# 6. "-r" can overwirte former "-d". +# 7. "-0" must be together with "-c". +# 8. "-d" must be on directory. +# 9. "-r" must be on directory. +# 10. "-p" must be together with "-c -r" or "-s". +# + +function cleanup +{ + log_must rm -rf $PRJDIR +} + +log_onexit cleanup + +log_assert "Check 'zfs project' invalid options combinations" + +log_must mkdir $PRJDIR +log_must mkdir $PRJDIR/a1 +log_must touch $PRJDIR/a2 + +log_mustnot zfs project -c +log_mustnot zfs project -c -k $PRJDIR/a1 +log_mustnot zfs project -c -C $PRJDIR/a1 +log_mustnot zfs project -c -s $PRJDIR/a1 +log_must zfs project -c -d -r $PRJDIR/a1 +log_must zfs project -c -r -d $PRJDIR/a1 +log_mustnot zfs project -c -d $PRJDIR/a2 +log_mustnot zfs project -c -r $PRJDIR/a2 + +log_mustnot zfs project -C +log_mustnot zfs project -C -c $PRJDIR/a1 +log_mustnot zfs project -C -d $PRJDIR/a1 +log_mustnot zfs project -C -p 100 $PRJDIR/a1 +log_mustnot zfs project -C -s $PRJDIR/a1 +log_mustnot zfs project -C -r -0 $PRJDIR/a1 +log_mustnot zfs project -C -0 $PRJDIR/a1 + +log_mustnot zfs project -s +log_mustnot zfs project -s -d $PRJDIR/a1 +log_mustnot zfs project -s -k $PRJDIR/a1 +log_mustnot zfs project -s -r -0 $PRJDIR/a1 +log_mustnot zfs project -s -0 $PRJDIR/a1 +log_mustnot zfs project -s -r $PRJDIR/a2 + +log_mustnot zfs project -p 100 +log_mustnot zfs project -p -1 $PRJDIR/a2 +log_mustnot zfs project -p 100 -d $PRJDIR/a1 +log_mustnot zfs project -p 100 -k $PRJDIR/a1 +log_mustnot zfs project -p 100 -0 $PRJDIR/a1 +log_mustnot zfs project -p 100 -r -0 $PRJDIR/a1 + +log_mustnot zfs project +log_mustnot zfs project -0 $PRJDIR/a2 +log_mustnot zfs project -k $PRJDIR/a2 +log_mustnot zfs project -S $PRJDIR/a1 + +log_pass "Check 'zfs project' invalid options combinations" diff --git a/tests/zfs-tests/tests/functional/projectquota/setup.ksh b/tests/zfs-tests/tests/functional/projectquota/setup.ksh new file mode 100755 index 000000000000..c81b300e5e96 --- /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/tests/functional/projectquota/projectquota_common.kshlib + +verify_runnable "both" + +del_user $PUSER +del_group $PGROUP +log_must add_group $PGROUP +log_must add_user $PGROUP $PUSER + +# +# Verify the test user can execute the zfs utilities. This may not +# be possible due to default permissions on the user home directory. +# This can be resolved granting group read access. +# +# chmod 0750 $HOME +# +user_run $PUSER zfs list +if [ $? -ne 0 ]; then + log_unsupported "Test user $PUSER cannot execute zfs utilities" +fi + +DISK=${DISKS%% *} +default_setup_noexit $DISK + +log_pass 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..27449ad1becd --- /dev/null +++ b/tests/zfs-tests/tests/functional/upgrade/upgrade_projectquota_001_pos.ksh @@ -0,0 +1,128 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2017 by Fan Yong. All rights reserved. +# + +. $STF_SUITE/tests/functional/upgrade/upgrade_common.kshlib + +# +# DESCRIPTION: +# +# Check whether zfs upgrade for project quota works or not. +# The project quota is per dataset based feature, this test +# will create multiple datasets and try different upgrade methods. +# +# STRATEGY: +# 1. Create a pool with all features disabled +# 2. Create a few dataset for testing +# 3. Make sure automatic upgrade work +# 4. Make sure manual upgrade work +# + +verify_runnable "global" + +if ! lsattr -pd > /dev/null 2>&1; then + log_unsupported "Current e2fsprogs does not support set/show project ID" +fi + +log_assert "pool upgrade for projectquota should work" +log_onexit cleanup_upgrade + +log_must zpool create -d -m $TESTDIR $TESTPOOL $TMPDEV + +log_must mkfiles $TESTDIR/tf $((RANDOM % 100 + 1)) +log_must zfs create $TESTPOOL/fs1 +log_must mkfiles $TESTDIR/fs1/tf $((RANDOM % 100 + 1)) +log_must zfs umount $TESTPOOL/fs1 + +log_must zfs create $TESTPOOL/fs2 +log_must mkdir $TESTDIR/fs2/dir +log_must mkfiles $TESTDIR/fs2/tf $((RANDOM % 100 + 1)) + +log_must zfs create $TESTPOOL/fs3 +log_must mkdir $TESTDIR/fs3/dir +log_must mkfiles $TESTDIR/fs3/tf $((RANDOM % 100 + 1)) + +# Make sure project quota is disabled +zfs projectspace -o used $TESTPOOL | grep -q "USED" && + log_fail "project quota should be disabled initially" + +# set projectquota before upgrade will fail +log_mustnot zfs set projectquota@100=100m $TESTDIR/fs3 + +# set projectobjquota before upgrade will fail +log_mustnot zfs set projectobjquota@100=1000 $TESTDIR/fs3 + +# 'chattr -p' should fail before upgrade +log_mustnot chattr -p 100 $TESTDIR/fs3/dir + +# 'chattr +P' should fail before upgrade +log_mustnot chattr +P $TESTDIR/fs3/dir + +# Upgrade zpool to support all features +log_must zpool upgrade $TESTPOOL + +# Double check project quota is disabled +zfs projectspace -o used $TESTPOOL | grep -q "USED" && + log_fail "project quota should be disabled after pool upgrade" + +# Mount dataset should trigger upgrade +log_must zfs mount $TESTPOOL/fs1 +log_must sleep 3 # upgrade done in the background so let's wait for a while +zfs projectspace -o used $TESTPOOL/fs1 | grep -q "USED" || + log_fail "project quota should be enabled for $TESTPOOL/fs1" + +# Create file should trigger dataset upgrade +log_must mkfile 1m $TESTDIR/fs2/dir/tf +log_must sleep 3 # upgrade done in the background so let's wait for a while +zfs projectspace -o used $TESTPOOL/fs2 | grep -q "USED" || + log_fail "project quota should be enabled for $TESTPOOL/fs2" + +# "lsattr -p" should NOT trigger upgrade +log_must lsattr -p -d $TESTDIR/fs3/dir +zfs projectspace -o used $TESTPOOL/fs3 | grep -q "USED" && + log_fail "project quota should not active for $TESTPOOL/fs3" + +# 'chattr -p' should trigger dataset upgrade +log_must chattr -p 100 $TESTDIR/fs3/dir +log_must sleep 5 # upgrade done in the background so let's wait for a while +zfs projectspace -o used $TESTPOOL/fs3 | grep -q "USED" || + log_fail "project quota should be enabled for $TESTPOOL/fs3" +cnt=$(zfs get -H projectobjused@100 $TESTPOOL/fs3 | awk '{print $3}') +# if 'xattr=on', then 'cnt = 2' +[[ $cnt -ne 1 ]] && [[ $cnt -ne 2 ]] && + log_fail "projectquota accounting failed $cnt" + +# All in all, after having been through this, the dataset for testpool +# still shouldn't be upgraded +zfs projectspace -o used $TESTPOOL | grep -q "USED" && + log_fail "project quota should be disabled for $TESTPOOL" + +# Manual upgrade root dataset +# uses an ioctl which will wait for the upgrade to be done before returning +log_must zfs set version=current $TESTPOOL +zfs projectspace -o used $TESTPOOL | grep -q "USED" || + log_fail "project quota should be enabled for $TESTPOOL" + +log_pass "Project Quota upgrade done" 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