]> git.proxmox.com Git - mirror_zfs.git/blobdiff - cmd/zfs/zfs_main.c
Project Quota on ZFS
[mirror_zfs.git] / cmd / zfs / zfs_main.c
index 991dd444458614e8bfe838386e0549020749a1a0..16410d2f23115e1fc4cd882ec5d8e604e8b49f29 100644 (file)
@@ -59,6 +59,7 @@
 #include <sys/systeminfo.h>
 #include <sys/types.h>
 #include <time.h>
+#include <sys/zfs_project.h>
 
 #include <libzfs.h>
 #include <libzfs_core.h>
@@ -74,6 +75,7 @@
 #include "zfs_util.h"
 #include "zfs_comutil.h"
 #include "libzfs_impl.h"
+#include "zfs_projectutil.h"
 
 libzfs_handle_t *g_zfs;
 
@@ -111,6 +113,7 @@ static int zfs_do_channel_program(int argc, char **argv);
 static int zfs_do_load_key(int argc, char **argv);
 static int zfs_do_unload_key(int argc, char **argv);
 static int zfs_do_change_key(int argc, char **argv);
+static int zfs_do_project(int argc, char **argv);
 
 /*
  * Enable a reasonable set of defaults for libumem debugging on DEBUG builds.
@@ -153,6 +156,8 @@ typedef enum {
        HELP_UNALLOW,
        HELP_USERSPACE,
        HELP_GROUPSPACE,
+       HELP_PROJECTSPACE,
+       HELP_PROJECT,
        HELP_HOLD,
        HELP_HOLDS,
        HELP_RELEASE,
@@ -197,8 +202,12 @@ static zfs_command_t command_table[] = {
        { "get",        zfs_do_get,             HELP_GET                },
        { "inherit",    zfs_do_inherit,         HELP_INHERIT            },
        { "upgrade",    zfs_do_upgrade,         HELP_UPGRADE            },
+       { NULL },
        { "userspace",  zfs_do_userspace,       HELP_USERSPACE          },
        { "groupspace", zfs_do_userspace,       HELP_GROUPSPACE         },
+       { "projectspace", zfs_do_userspace,     HELP_PROJECTSPACE       },
+       { NULL },
+       { "project",    zfs_do_project,         HELP_PROJECT            },
        { NULL },
        { "mount",      zfs_do_mount,           HELP_MOUNT              },
        { "unmount",    zfs_do_unmount,         HELP_UNMOUNT            },
@@ -328,6 +337,15 @@ get_usage(zfs_help_t idx)
                    "[-s field] ...\n"
                    "\t    [-S field] ... [-t type[,...]] "
                    "<filesystem|snapshot>\n"));
+       case HELP_PROJECTSPACE:
+               return (gettext("\tprojectspace [-Hp] [-o field[,...]] "
+                   "[-s field] ... \n"
+                   "\t    [-S field] ... <filesystem|snapshot>\n"));
+       case HELP_PROJECT:
+               return (gettext("\tproject [-d|-r] <directory|file ...>\n"
+                   "\tproject -c [-0] [-d|-r] [-p id] <directory|file ...>\n"
+                   "\tproject -C [-k] [-r] <directory ...>\n"
+                   "\tproject [-p id] [-r] [-s] <directory ...>\n"));
        case HELP_HOLD:
                return (gettext("\thold [-r] <tag> <snapshot> ...\n"));
        case HELP_HOLDS:
@@ -489,10 +507,26 @@ usage(boolean_t requested)
                (void) fprintf(fp, " NO       NO   <size>\n");
                (void) fprintf(fp, "\t%-15s ", "groupused@...");
                (void) fprintf(fp, " NO       NO   <size>\n");
+               (void) fprintf(fp, "\t%-15s ", "projectused@...");
+               (void) fprintf(fp, " NO       NO   <size>\n");
+               (void) fprintf(fp, "\t%-15s ", "userobjused@...");
+               (void) fprintf(fp, " NO       NO   <size>\n");
+               (void) fprintf(fp, "\t%-15s ", "groupobjused@...");
+               (void) fprintf(fp, " NO       NO   <size>\n");
+               (void) fprintf(fp, "\t%-15s ", "projectobjused@...");
+               (void) fprintf(fp, " NO       NO   <size>\n");
                (void) fprintf(fp, "\t%-15s ", "userquota@...");
                (void) fprintf(fp, "YES       NO   <size> | none\n");
                (void) fprintf(fp, "\t%-15s ", "groupquota@...");
                (void) fprintf(fp, "YES       NO   <size> | none\n");
+               (void) fprintf(fp, "\t%-15s ", "projectquota@...");
+               (void) fprintf(fp, "YES       NO   <size> | none\n");
+               (void) fprintf(fp, "\t%-15s ", "userobjquota@...");
+               (void) fprintf(fp, "YES       NO   <size> | none\n");
+               (void) fprintf(fp, "\t%-15s ", "groupobjquota@...");
+               (void) fprintf(fp, "YES       NO   <size> | none\n");
+               (void) fprintf(fp, "\t%-15s ", "projectobjquota@...");
+               (void) fprintf(fp, "YES       NO   <size> | none\n");
                (void) fprintf(fp, "\t%-15s ", "written@<snap>");
                (void) fprintf(fp, " NO       NO   <size>\n");
 
@@ -500,9 +534,9 @@ usage(boolean_t requested)
                    "with standard units such as K, M, G, etc.\n"));
                (void) fprintf(fp, gettext("\nUser-defined properties can "
                    "be specified by using a name containing a colon (:).\n"));
-               (void) fprintf(fp, gettext("\nThe {user|group}{used|quota}@ "
-                   "properties must be appended with\n"
-                   "a user or group specifier of one of these forms:\n"
+               (void) fprintf(fp, gettext("\nThe {user|group|project}"
+                   "[obj]{used|quota}@ properties must be appended with\n"
+                   "a user|group|project specifier of one of these forms:\n"
                    "    POSIX name      (eg: \"matt\")\n"
                    "    POSIX id        (eg: \"126829\")\n"
                    "    SMB name@domain (eg: \"matt@sun\")\n"
@@ -2270,6 +2304,8 @@ zfs_do_upgrade(int argc, char **argv)
  *               [-S field [-S field]...] [-t type[,...]] filesystem | snapshot
  * zfs groupspace [-Hinp] [-o field[,...]] [-s field [-s field]...]
  *                [-S field [-S field]...] [-t type[,...]] filesystem | snapshot
+ * zfs projectspace [-Hp] [-o field[,...]] [-s field [-s field]...]
+ *                [-S field [-S field]...] filesystem | snapshot
  *
  *     -H      Scripted mode; elide headers and separate columns by tabs.
  *     -i      Translate SID to POSIX ID.
@@ -2303,8 +2339,10 @@ static char *us_field_names[] = { "type", "name", "used", "quota",
 #define        USTYPE_PSX_USR  (1 << 1)
 #define        USTYPE_SMB_GRP  (1 << 2)
 #define        USTYPE_SMB_USR  (1 << 3)
+#define        USTYPE_PROJ     (1 << 4)
 #define        USTYPE_ALL      \
-       (USTYPE_PSX_GRP | USTYPE_PSX_USR | USTYPE_SMB_GRP | USTYPE_SMB_USR)
+       (USTYPE_PSX_GRP | USTYPE_PSX_USR | USTYPE_SMB_GRP | USTYPE_SMB_USR | \
+           USTYPE_PROJ)
 
 static int us_type_bits[] = {
        USTYPE_PSX_GRP,
@@ -2459,6 +2497,13 @@ zfs_prop_is_group(unsigned p)
            p == ZFS_PROP_GROUPOBJUSED || p == ZFS_PROP_GROUPOBJQUOTA);
 }
 
+static boolean_t
+zfs_prop_is_project(unsigned p)
+{
+       return (p == ZFS_PROP_PROJECTUSED || p == ZFS_PROP_PROJECTQUOTA ||
+           p == ZFS_PROP_PROJECTOBJUSED || p == ZFS_PROP_PROJECTOBJQUOTA);
+}
+
 static inline const char *
 us_type2str(unsigned field_type)
 {
@@ -2471,6 +2516,8 @@ us_type2str(unsigned field_type)
                return ("SMB User");
        case USTYPE_SMB_GRP:
                return ("SMB Group");
+       case USTYPE_PROJ:
+               return ("Project");
        default:
                return ("Undefined");
        }
@@ -2556,7 +2603,7 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space)
                                if ((g = getgrgid(rid)) != NULL)
                                        name = g->gr_name;
                        }
-               } else {
+               } else if (zfs_prop_is_user(prop)) {
                        type = USTYPE_PSX_USR;
                        if (!cb->cb_numname) {
                                struct passwd *p;
@@ -2564,6 +2611,8 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space)
                                if ((p = getpwuid(rid)) != NULL)
                                        name = p->pw_name;
                        }
+               } else {
+                       type = USTYPE_PROJ;
                }
        }
 
@@ -2615,7 +2664,9 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space)
        /* Calculate/update width of USED/QUOTA fields */
        if (cb->cb_nicenum) {
                if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED ||
-                   prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA) {
+                   prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA ||
+                   prop == ZFS_PROP_PROJECTUSED ||
+                   prop == ZFS_PROP_PROJECTQUOTA) {
                        zfs_nicebytes(space, sizebuf, sizeof (sizebuf));
                } else {
                        zfs_nicenum(space, sizebuf, sizeof (sizebuf));
@@ -2625,21 +2676,24 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space)
                    (u_longlong_t)space);
        }
        sizelen = strlen(sizebuf);
-       if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED) {
+       if (prop == ZFS_PROP_USERUSED || prop == ZFS_PROP_GROUPUSED ||
+           prop == ZFS_PROP_PROJECTUSED) {
                propname = "used";
                if (!nvlist_exists(props, "quota"))
                        (void) nvlist_add_uint64(props, "quota", 0);
-       } else if (prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA) {
+       } else if (prop == ZFS_PROP_USERQUOTA || prop == ZFS_PROP_GROUPQUOTA ||
+           prop == ZFS_PROP_PROJECTQUOTA) {
                propname = "quota";
                if (!nvlist_exists(props, "used"))
                        (void) nvlist_add_uint64(props, "used", 0);
        } else if (prop == ZFS_PROP_USEROBJUSED ||
-           prop == ZFS_PROP_GROUPOBJUSED) {
+           prop == ZFS_PROP_GROUPOBJUSED || prop == ZFS_PROP_PROJECTOBJUSED) {
                propname = "objused";
                if (!nvlist_exists(props, "objquota"))
                        (void) nvlist_add_uint64(props, "objquota", 0);
        } else if (prop == ZFS_PROP_USEROBJQUOTA ||
-           prop == ZFS_PROP_GROUPOBJQUOTA) {
+           prop == ZFS_PROP_GROUPOBJQUOTA ||
+           prop == ZFS_PROP_PROJECTOBJQUOTA) {
                propname = "objquota";
                if (!nvlist_exists(props, "objused"))
                        (void) nvlist_add_uint64(props, "objused", 0);
@@ -2838,13 +2892,22 @@ zfs_do_userspace(int argc, char **argv)
        if (argc < 2)
                usage(B_FALSE);
 
-       if (strcmp(argv[0], "groupspace") == 0)
+       if (strcmp(argv[0], "groupspace") == 0) {
                /* Toggle default group types */
                types = USTYPE_PSX_GRP | USTYPE_SMB_GRP;
+       } else if (strcmp(argv[0], "projectspace") == 0) {
+               types = USTYPE_PROJ;
+               prtnum = B_TRUE;
+       }
 
        while ((c = getopt(argc, argv, "nHpo:s:S:t:i")) != -1) {
                switch (c) {
                case 'n':
+                       if (types == USTYPE_PROJ) {
+                               (void) fprintf(stderr,
+                                   gettext("invalid option 'n'\n"));
+                               usage(B_FALSE);
+                       }
                        prtnum = B_TRUE;
                        break;
                case 'H':
@@ -2866,9 +2929,19 @@ zfs_do_userspace(int argc, char **argv)
                        }
                        break;
                case 't':
+                       if (types == USTYPE_PROJ) {
+                               (void) fprintf(stderr,
+                                   gettext("invalid option 't'\n"));
+                               usage(B_FALSE);
+                       }
                        tfield = optarg;
                        break;
                case 'i':
+                       if (types == USTYPE_PROJ) {
+                               (void) fprintf(stderr,
+                                   gettext("invalid option 'i'\n"));
+                               usage(B_FALSE);
+                       }
                        sid2posix = B_TRUE;
                        break;
                case ':':
@@ -2965,7 +3038,8 @@ zfs_do_userspace(int argc, char **argv)
                if ((zfs_prop_is_user(p) &&
                    !(types & (USTYPE_PSX_USR | USTYPE_SMB_USR))) ||
                    (zfs_prop_is_group(p) &&
-                   !(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP))))
+                   !(types & (USTYPE_PSX_GRP | USTYPE_SMB_GRP))) ||
+                   (zfs_prop_is_project(p) && types != USTYPE_PROJ))
                        continue;
 
                cb.cb_prop = p;
@@ -4276,6 +4350,11 @@ zfs_do_receive(int argc, char **argv)
 #define        ZFS_DELEG_PERM_LOAD_KEY         "load-key"
 #define        ZFS_DELEG_PERM_CHANGE_KEY       "change-key"
 
+#define        ZFS_DELEG_PERM_PROJECTUSED      "projectused"
+#define        ZFS_DELEG_PERM_PROJECTQUOTA     "projectquota"
+#define        ZFS_DELEG_PERM_PROJECTOBJUSED   "projectobjused"
+#define        ZFS_DELEG_PERM_PROJECTOBJQUOTA  "projectobjquota"
+
 #define        ZFS_NUM_DELEG_NOTES ZFS_DELEG_NOTE_NONE
 
 static zfs_deleg_perm_tab_t zfs_deleg_perm_tbl[] = {
@@ -4307,6 +4386,10 @@ static zfs_deleg_perm_tab_t zfs_deleg_perm_tbl[] = {
        { ZFS_DELEG_PERM_USEROBJUSED, ZFS_DELEG_NOTE_USEROBJUSED },
        { ZFS_DELEG_PERM_GROUPOBJQUOTA, ZFS_DELEG_NOTE_GROUPOBJQUOTA },
        { ZFS_DELEG_PERM_GROUPOBJUSED, ZFS_DELEG_NOTE_GROUPOBJUSED },
+       { ZFS_DELEG_PERM_PROJECTUSED, ZFS_DELEG_NOTE_PROJECTUSED },
+       { ZFS_DELEG_PERM_PROJECTQUOTA, ZFS_DELEG_NOTE_PROJECTQUOTA },
+       { ZFS_DELEG_PERM_PROJECTOBJUSED, ZFS_DELEG_NOTE_PROJECTOBJUSED },
+       { ZFS_DELEG_PERM_PROJECTOBJQUOTA, ZFS_DELEG_NOTE_PROJECTOBJQUOTA },
        { NULL, ZFS_DELEG_NOTE_NONE }
 };
 
@@ -4388,6 +4471,10 @@ deleg_perm_type(zfs_deleg_note_t note)
        case ZFS_DELEG_NOTE_USEROBJUSED:
        case ZFS_DELEG_NOTE_GROUPOBJQUOTA:
        case ZFS_DELEG_NOTE_GROUPOBJUSED:
+       case ZFS_DELEG_NOTE_PROJECTUSED:
+       case ZFS_DELEG_NOTE_PROJECTQUOTA:
+       case ZFS_DELEG_NOTE_PROJECTOBJUSED:
+       case ZFS_DELEG_NOTE_PROJECTOBJQUOTA:
                /* other */
                return (gettext("other"));
        default:
@@ -4912,6 +4999,20 @@ deleg_perm_comment(zfs_deleg_note_t note)
        case ZFS_DELEG_NOTE_USEROBJUSED:
                str = gettext("Allows reading any userobjused@... property");
                break;
+       case ZFS_DELEG_NOTE_PROJECTQUOTA:
+               str = gettext("Allows accessing any projectquota@... property");
+               break;
+       case ZFS_DELEG_NOTE_PROJECTOBJQUOTA:
+               str = gettext("Allows accessing any \n\t\t\t\t"
+                   "projectobjquota@... property");
+               break;
+       case ZFS_DELEG_NOTE_PROJECTUSED:
+               str = gettext("Allows reading any projectused@... property");
+               break;
+       case ZFS_DELEG_NOTE_PROJECTOBJUSED:
+               str = gettext("Allows accessing any \n\t\t\t\t"
+                   "projectobjused@... property");
+               break;
                /* other */
        default:
                str = "";
@@ -7513,6 +7614,211 @@ zfs_do_change_key(int argc, char **argv)
        return (0);
 }
 
+/*
+ * 1) zfs project [-d|-r] <file|directory ...>
+ *    List project ID and inherit flag of file(s) or directories.
+ *    -d: List the directory itself, not its children.
+ *    -r: List subdirectories recursively.
+ *
+ * 2) zfs project -C [-k] [-r] <file|directory ...>
+ *    Clear project inherit flag and/or ID on the file(s) or directories.
+ *    -k: Keep the project ID unchanged. If not specified, the project ID
+ *       will be reset as zero.
+ *    -r: Clear on subdirectories recursively.
+ *
+ * 3) zfs project -c [-0] [-d|-r] [-p id] <file|directory ...>
+ *    Check project ID and inherit flag on the file(s) or directories,
+ *    report the outliers.
+ *    -0: Print file name followed by a NUL instead of newline.
+ *    -d: Check the directory itself, not its children.
+ *    -p: Specify the referenced ID for comparing with the target file(s)
+ *       or directories' project IDs. If not specified, the target (top)
+ *       directory's project ID will be used as the referenced one.
+ *    -r: Check subdirectories recursively.
+ *
+ * 4) zfs project [-p id] [-r] [-s] <file|directory ...>
+ *    Set project ID and/or inherit flag on the file(s) or directories.
+ *    -p: Set the project ID as the given id.
+ *    -r: Set on subdirectorie recursively. If not specify "-p" option,
+ *       it will use top-level directory's project ID as the given id,
+ *       then set both project ID and inherit flag on all descendants
+ *       of the top-level directory.
+ *    -s: Set project inherit flag.
+ */
+static int
+zfs_do_project(int argc, char **argv)
+{
+       zfs_project_control_t zpc = {
+               .zpc_expected_projid = ZFS_INVALID_PROJID,
+               .zpc_op = ZFS_PROJECT_OP_DEFAULT,
+               .zpc_dironly = B_FALSE,
+               .zpc_keep_projid = B_FALSE,
+               .zpc_newline = B_TRUE,
+               .zpc_recursive = B_FALSE,
+               .zpc_set_flag = B_FALSE,
+       };
+       int ret = 0, c;
+
+       if (argc < 2)
+               usage(B_FALSE);
+
+       while ((c = getopt(argc, argv, "0Ccdkp:rs")) != -1) {
+               switch (c) {
+               case '0':
+                       zpc.zpc_newline = B_FALSE;
+                       break;
+               case 'C':
+                       if (zpc.zpc_op != ZFS_PROJECT_OP_DEFAULT) {
+                               (void) fprintf(stderr, gettext("cannot "
+                                   "specify '-C' '-c' '-s' together\n"));
+                               usage(B_FALSE);
+                       }
+
+                       zpc.zpc_op = ZFS_PROJECT_OP_CLEAR;
+                       break;
+               case 'c':
+                       if (zpc.zpc_op != ZFS_PROJECT_OP_DEFAULT) {
+                               (void) fprintf(stderr, gettext("cannot "
+                                   "specify '-C' '-c' '-s' together\n"));
+                               usage(B_FALSE);
+                       }
+
+                       zpc.zpc_op = ZFS_PROJECT_OP_CHECK;
+                       break;
+               case 'd':
+                       zpc.zpc_dironly = B_TRUE;
+                       /* overwrite "-r" option */
+                       zpc.zpc_recursive = B_FALSE;
+                       break;
+               case 'k':
+                       zpc.zpc_keep_projid = B_TRUE;
+                       break;
+               case 'p': {
+                       char *endptr;
+
+                       errno = 0;
+                       zpc.zpc_expected_projid = strtoull(optarg, &endptr, 0);
+                       if (errno != 0 || *endptr != '\0') {
+                               (void) fprintf(stderr,
+                                   gettext("project ID must be less than "
+                                   "%u\n"), UINT32_MAX);
+                               usage(B_FALSE);
+                       }
+                       if (zpc.zpc_expected_projid >= UINT32_MAX) {
+                               (void) fprintf(stderr,
+                                   gettext("invalid project ID\n"));
+                               usage(B_FALSE);
+                       }
+                       break;
+               }
+               case 'r':
+                       zpc.zpc_recursive = B_TRUE;
+                       /* overwrite "-d" option */
+                       zpc.zpc_dironly = B_FALSE;
+                       break;
+               case 's':
+                       if (zpc.zpc_op != ZFS_PROJECT_OP_DEFAULT) {
+                               (void) fprintf(stderr, gettext("cannot "
+                                   "specify '-C' '-c' '-s' together\n"));
+                               usage(B_FALSE);
+                       }
+
+                       zpc.zpc_set_flag = B_TRUE;
+                       zpc.zpc_op = ZFS_PROJECT_OP_SET;
+                       break;
+               default:
+                       (void) fprintf(stderr, gettext("invalid option '%c'\n"),
+                           optopt);
+                       usage(B_FALSE);
+               }
+       }
+
+       if (zpc.zpc_op == ZFS_PROJECT_OP_DEFAULT) {
+               if (zpc.zpc_expected_projid != ZFS_INVALID_PROJID)
+                       zpc.zpc_op = ZFS_PROJECT_OP_SET;
+               else
+                       zpc.zpc_op = ZFS_PROJECT_OP_LIST;
+       }
+
+       switch (zpc.zpc_op) {
+       case ZFS_PROJECT_OP_LIST:
+               if (zpc.zpc_keep_projid) {
+                       (void) fprintf(stderr,
+                           gettext("'-k' is only valid together with '-C'\n"));
+                       usage(B_FALSE);
+               }
+               if (!zpc.zpc_newline) {
+                       (void) fprintf(stderr,
+                           gettext("'-0' is only valid together with '-c'\n"));
+                       usage(B_FALSE);
+               }
+               break;
+       case ZFS_PROJECT_OP_CHECK:
+               if (zpc.zpc_keep_projid) {
+                       (void) fprintf(stderr,
+                           gettext("'-k' is only valid together with '-C'\n"));
+                       usage(B_FALSE);
+               }
+               break;
+       case ZFS_PROJECT_OP_CLEAR:
+               if (zpc.zpc_dironly) {
+                       (void) fprintf(stderr,
+                           gettext("'-d' is useless together with '-C'\n"));
+                       usage(B_FALSE);
+               }
+               if (!zpc.zpc_newline) {
+                       (void) fprintf(stderr,
+                           gettext("'-0' is only valid together with '-c'\n"));
+                       usage(B_FALSE);
+               }
+               if (zpc.zpc_expected_projid != ZFS_INVALID_PROJID) {
+                       (void) fprintf(stderr,
+                           gettext("'-p' is useless together with '-C'\n"));
+                       usage(B_FALSE);
+               }
+               break;
+       case ZFS_PROJECT_OP_SET:
+               if (zpc.zpc_dironly) {
+                       (void) fprintf(stderr,
+                           gettext("'-d' is useless for set project ID and/or "
+                           "inherit flag\n"));
+                       usage(B_FALSE);
+               }
+               if (zpc.zpc_keep_projid) {
+                       (void) fprintf(stderr,
+                           gettext("'-k' is only valid together with '-C'\n"));
+                       usage(B_FALSE);
+               }
+               if (!zpc.zpc_newline) {
+                       (void) fprintf(stderr,
+                           gettext("'-0' is only valid together with '-c'\n"));
+                       usage(B_FALSE);
+               }
+               break;
+       default:
+               ASSERT(0);
+               break;
+       }
+
+       argv += optind;
+       argc -= optind;
+       if (argc == 0) {
+               (void) fprintf(stderr,
+                   gettext("missing file or directory target(s)\n"));
+               usage(B_FALSE);
+       }
+
+       for (int i = 0; i < argc; i++) {
+               int err;
+
+               err = zfs_project_handle(argv[i], &zpc);
+               if (err && !ret)
+                       ret = err;
+       }
+
+       return (ret);
+}
+
 int
 main(int argc, char **argv)
 {