+ nvlist_t *argnvl = fnvlist_alloc();
+ fnvlist_add_string_array(argnvl, ZCP_ARG_CLIARGV, argv + 2, argc - 2);
+
+ if (sync_flag) {
+ ret = lzc_channel_program(poolname, progbuf,
+ instrlimit, memlimit, argnvl, &outnvl);
+ } else {
+ ret = lzc_channel_program_nosync(poolname, progbuf,
+ instrlimit, memlimit, argnvl, &outnvl);
+ }
+
+ if (ret != 0) {
+ /*
+ * On error, report the error message handed back by lua if one
+ * exists. Otherwise, generate an appropriate error message,
+ * falling back on strerror() for an unexpected return code.
+ */
+ char *errstring = NULL;
+ const char *msg = gettext("Channel program execution failed");
+ uint64_t instructions = 0;
+ if (outnvl != NULL && nvlist_exists(outnvl, ZCP_RET_ERROR)) {
+ (void) nvlist_lookup_string(outnvl,
+ ZCP_RET_ERROR, &errstring);
+ if (errstring == NULL)
+ errstring = strerror(ret);
+ if (ret == ETIME) {
+ (void) nvlist_lookup_uint64(outnvl,
+ ZCP_ARG_INSTRLIMIT, &instructions);
+ }
+ } else {
+ switch (ret) {
+ case EINVAL:
+ errstring =
+ "Invalid instruction or memory limit.";
+ break;
+ case ENOMEM:
+ errstring = "Return value too large.";
+ break;
+ case ENOSPC:
+ errstring = "Memory limit exhausted.";
+ break;
+ case ETIME:
+ errstring = "Timed out.";
+ break;
+ case EPERM:
+ errstring = "Permission denied. Channel "
+ "programs must be run as root.";
+ break;
+ default:
+ (void) zfs_standard_error(g_zfs, ret, msg);
+ }
+ }
+ if (errstring != NULL)
+ (void) fprintf(stderr, "%s:\n%s\n", msg, errstring);
+
+ if (ret == ETIME && instructions != 0)
+ (void) fprintf(stderr,
+ gettext("%llu Lua instructions\n"),
+ (u_longlong_t)instructions);
+ } else {
+ if (json_output) {
+ (void) nvlist_print_json(stdout, outnvl);
+ } else if (nvlist_empty(outnvl)) {
+ (void) fprintf(stdout, gettext("Channel program fully "
+ "executed and did not produce output.\n"));
+ } else {
+ (void) fprintf(stdout, gettext("Channel program fully "
+ "executed and produced output:\n"));
+ dump_nvlist(outnvl, 4);
+ }
+ }
+
+ free(progbuf);
+ fnvlist_free(outnvl);
+ fnvlist_free(argnvl);
+ return (ret != 0);
+
+usage:
+ usage(B_FALSE);
+ return (-1);
+}
+
+
+typedef struct loadkey_cbdata {
+ boolean_t cb_loadkey;
+ boolean_t cb_recursive;
+ boolean_t cb_noop;
+ char *cb_keylocation;
+ uint64_t cb_numfailed;
+ uint64_t cb_numattempted;
+} loadkey_cbdata_t;
+
+static int
+load_key_callback(zfs_handle_t *zhp, void *data)
+{
+ int ret;
+ boolean_t is_encroot;
+ loadkey_cbdata_t *cb = data;
+ uint64_t keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS);
+
+ /*
+ * If we are working recursively, we want to skip loading / unloading
+ * keys for non-encryption roots and datasets whose keys are already
+ * in the desired end-state.
+ */
+ if (cb->cb_recursive) {
+ ret = zfs_crypto_get_encryption_root(zhp, &is_encroot, NULL);
+ if (ret != 0)
+ return (ret);
+ if (!is_encroot)
+ return (0);
+
+ if ((cb->cb_loadkey && keystatus == ZFS_KEYSTATUS_AVAILABLE) ||
+ (!cb->cb_loadkey && keystatus == ZFS_KEYSTATUS_UNAVAILABLE))
+ return (0);
+ }
+
+ cb->cb_numattempted++;
+
+ if (cb->cb_loadkey)
+ ret = zfs_crypto_load_key(zhp, cb->cb_noop, cb->cb_keylocation);
+ else
+ ret = zfs_crypto_unload_key(zhp);
+
+ if (ret != 0) {
+ cb->cb_numfailed++;
+ return (ret);
+ }
+
+ return (0);
+}
+
+static int
+load_unload_keys(int argc, char **argv, boolean_t loadkey)
+{
+ int c, ret = 0, flags = 0;
+ boolean_t do_all = B_FALSE;
+ loadkey_cbdata_t cb = { 0 };
+
+ cb.cb_loadkey = loadkey;
+
+ while ((c = getopt(argc, argv, "anrL:")) != -1) {
+ /* noop and alternate keylocations only apply to zfs load-key */
+ if (loadkey) {
+ switch (c) {
+ case 'n':
+ cb.cb_noop = B_TRUE;
+ continue;
+ case 'L':
+ cb.cb_keylocation = optarg;
+ continue;
+ default:
+ break;
+ }
+ }
+
+ switch (c) {
+ case 'a':
+ do_all = B_TRUE;
+ cb.cb_recursive = B_TRUE;
+ break;
+ case 'r':
+ flags |= ZFS_ITER_RECURSE;
+ cb.cb_recursive = B_TRUE;
+ break;
+ default:
+ (void) fprintf(stderr,
+ gettext("invalid option '%c'\n"), optopt);
+ usage(B_FALSE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (!do_all && argc == 0) {
+ (void) fprintf(stderr,
+ gettext("Missing dataset argument or -a option\n"));
+ usage(B_FALSE);
+ }
+
+ if (do_all && argc != 0) {
+ (void) fprintf(stderr,
+ gettext("Cannot specify dataset with -a option\n"));
+ usage(B_FALSE);
+ }
+
+ if (cb.cb_recursive && cb.cb_keylocation != NULL &&
+ strcmp(cb.cb_keylocation, "prompt") != 0) {
+ (void) fprintf(stderr, gettext("alternate keylocation may only "
+ "be 'prompt' with -r or -a\n"));
+ usage(B_FALSE);
+ }
+
+ ret = zfs_for_each(argc, argv, flags,
+ ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, NULL, NULL, 0,
+ load_key_callback, &cb);
+
+ if (cb.cb_noop || (cb.cb_recursive && cb.cb_numattempted != 0)) {
+ (void) printf(gettext("%llu / %llu key(s) successfully %s\n"),
+ (u_longlong_t)(cb.cb_numattempted - cb.cb_numfailed),
+ (u_longlong_t)cb.cb_numattempted,
+ loadkey ? (cb.cb_noop ? "verified" : "loaded") :
+ "unloaded");
+ }
+
+ if (cb.cb_numfailed != 0)
+ ret = -1;
+
+ return (ret);
+}
+
+static int
+zfs_do_load_key(int argc, char **argv)
+{
+ return (load_unload_keys(argc, argv, B_TRUE));
+}
+
+
+static int
+zfs_do_unload_key(int argc, char **argv)
+{
+ return (load_unload_keys(argc, argv, B_FALSE));
+}
+
+static int
+zfs_do_change_key(int argc, char **argv)
+{
+ int c, ret;
+ uint64_t keystatus;
+ boolean_t loadkey = B_FALSE, inheritkey = B_FALSE;
+ zfs_handle_t *zhp = NULL;
+ nvlist_t *props = fnvlist_alloc();
+
+ while ((c = getopt(argc, argv, "lio:")) != -1) {
+ switch (c) {
+ case 'l':
+ loadkey = B_TRUE;
+ break;
+ case 'i':
+ inheritkey = B_TRUE;
+ break;
+ case 'o':
+ if (!parseprop(props, optarg)) {
+ nvlist_free(props);
+ return (1);
+ }
+ break;
+ default:
+ (void) fprintf(stderr,
+ gettext("invalid option '%c'\n"), optopt);
+ usage(B_FALSE);
+ }
+ }
+
+ if (inheritkey && !nvlist_empty(props)) {
+ (void) fprintf(stderr,
+ gettext("Properties not allowed for inheriting\n"));
+ usage(B_FALSE);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ (void) fprintf(stderr, gettext("Missing dataset argument\n"));
+ usage(B_FALSE);
+ }
+
+ if (argc > 1) {
+ (void) fprintf(stderr, gettext("Too many arguments\n"));
+ usage(B_FALSE);
+ }
+
+ zhp = zfs_open(g_zfs, argv[argc - 1],
+ ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME);
+ if (zhp == NULL)
+ usage(B_FALSE);
+
+ if (loadkey) {
+ keystatus = zfs_prop_get_int(zhp, ZFS_PROP_KEYSTATUS);
+ if (keystatus != ZFS_KEYSTATUS_AVAILABLE) {
+ ret = zfs_crypto_load_key(zhp, B_FALSE, NULL);
+ if (ret != 0) {
+ nvlist_free(props);
+ zfs_close(zhp);
+ return (-1);
+ }
+ }
+
+ /* refresh the properties so the new keystatus is visible */
+ zfs_refresh_properties(zhp);
+ }
+
+ ret = zfs_crypto_rewrap(zhp, props, inheritkey);
+ if (ret != 0) {
+ nvlist_free(props);
+ zfs_close(zhp);
+ return (-1);
+ }
+
+ nvlist_free(props);
+ zfs_close(zhp);
+ 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)
+{
+ int ret = 0;
+ int i = 0;
+ char *cmdname;
+ char **newargv;
+
+ (void) setlocale(LC_ALL, "");
+ (void) textdomain(TEXT_DOMAIN);
+
+ opterr = 0;
+
+ /*
+ * Make sure the user has specified some command.
+ */
+ if (argc < 2) {
+ (void) fprintf(stderr, gettext("missing command\n"));
+ usage(B_FALSE);
+ }
+
+ cmdname = argv[1];
+
+ /*
+ * The 'umount' command is an alias for 'unmount'
+ */
+ if (strcmp(cmdname, "umount") == 0)
+ cmdname = "unmount";
+
+ /*
+ * The 'recv' command is an alias for 'receive'
+ */
+ if (strcmp(cmdname, "recv") == 0)
+ cmdname = "receive";
+
+ /*
+ * The 'snap' command is an alias for 'snapshot'
+ */
+ if (strcmp(cmdname, "snap") == 0)
+ cmdname = "snapshot";
+
+ /*
+ * The 'remap' command has been disabled and may be removed in the
+ * future. See the comment above zfs_do_remap() for details.
+ */
+ if (!libzfs_envvar_is_set("ZFS_REMAP_ENABLED"))
+ disable_command_idx("remap");
+
+ /*
+ * Special case '-?'
+ */
+ if ((strcmp(cmdname, "-?") == 0) ||
+ (strcmp(cmdname, "--help") == 0))
+ usage(B_TRUE);
+
+ if ((g_zfs = libzfs_init()) == NULL) {
+ (void) fprintf(stderr, "%s", libzfs_error_init(errno));
+ return (1);
+ }
+
+ mnttab_file = g_zfs->libzfs_mnttab;
+
+ zfs_save_arguments(argc, argv, history_str, sizeof (history_str));
+
+ libzfs_print_on_error(g_zfs, B_TRUE);
+
+ /*
+ * Many commands modify input strings for string parsing reasons.
+ * We create a copy to protect the original argv.
+ */
+ newargv = malloc((argc + 1) * sizeof (newargv[0]));
+ for (i = 0; i < argc; i++)
+ newargv[i] = strdup(argv[i]);
+ newargv[argc] = NULL;
+
+ /*
+ * Run the appropriate command.
+ */
+ libzfs_mnttab_cache(g_zfs, B_TRUE);
+ if (find_command_idx(cmdname, &i) == 0) {
+ current_command = &command_table[i];
+ ret = command_table[i].func(argc - 1, newargv + 1);
+ } else if (strchr(cmdname, '=') != NULL) {