aboutsummaryrefslogtreecommitdiffstats
path: root/cmd/zfs
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/zfs')
-rw-r--r--cmd/zfs/Makefile.am4
-rw-r--r--cmd/zfs/zfs_main.c330
-rw-r--r--cmd/zfs/zfs_project.c295
-rw-r--r--cmd/zfs/zfs_projectutil.h49
4 files changed, 665 insertions, 13 deletions
diff --git a/cmd/zfs/Makefile.am b/cmd/zfs/Makefile.am
index 4e689d1ee..8b6ddaa20 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 991dd4444..16410d2f2 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 000000000..5ac88f279
--- /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 000000000..1792a3383
--- /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 */