+
+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);
+}
+