]> git.proxmox.com Git - mirror_zfs.git/blobdiff - cmd/zfs/zfs_main.c
Implement Redacted Send/Receive
[mirror_zfs.git] / cmd / zfs / zfs_main.c
index 214a437c5dd1465a541be864c11b6d99590231e7..9f34cc2f84a7b02c1f1e653ee7ce52c7cd511f0a 100644 (file)
@@ -33,6 +33,7 @@
 
 #include <assert.h>
 #include <ctype.h>
+#include <sys/debug.h>
 #include <errno.h>
 #include <getopt.h>
 #include <libgen.h>
@@ -119,6 +120,7 @@ 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);
 static int zfs_do_version(int argc, char **argv);
+static int zfs_do_redact(int argc, char **argv);
 
 /*
  * Enable a reasonable set of defaults for libumem debugging on DEBUG builds.
@@ -173,7 +175,8 @@ typedef enum {
        HELP_LOAD_KEY,
        HELP_UNLOAD_KEY,
        HELP_CHANGE_KEY,
-       HELP_VERSION
+       HELP_VERSION,
+       HELP_REDACT,
 } zfs_help_t;
 
 typedef struct zfs_command {
@@ -238,6 +241,7 @@ static zfs_command_t command_table[] = {
        { "load-key",   zfs_do_load_key,        HELP_LOAD_KEY           },
        { "unload-key", zfs_do_unload_key,      HELP_UNLOAD_KEY         },
        { "change-key", zfs_do_change_key,      HELP_CHANGE_KEY         },
+       { "redact",     zfs_do_redact,          HELP_REDACT             },
 };
 
 #define        NCOMMAND        (sizeof (command_table) / sizeof (command_table[0]))
@@ -279,7 +283,7 @@ get_usage(zfs_help_t idx)
                    "[filesystem|volume|snapshot] ...\n"));
        case HELP_MOUNT:
                return (gettext("\tmount\n"
-                   "\tmount [-lvO] [-o opts] <-a | filesystem>\n"));
+                   "\tmount [-flvO] [-o opts] <-a | filesystem>\n"));
        case HELP_PROMOTE:
                return (gettext("\tpromote <clone-filesystem>\n"));
        case HELP_RECEIVE:
@@ -302,6 +306,9 @@ get_usage(zfs_help_t idx)
                    "<snapshot>\n"
                    "\tsend [-nvPLecw] [-i snapshot|bookmark] "
                    "<filesystem|volume|snapshot>\n"
+                   "[-i bookmark] <snapshot> <bookmark_name>\n"
+                   "\tsend [-DnPpvLecr] [-i bookmark|snapshot] "
+                   "--redact <bookmark> <snapshot>\n"
                    "\tsend [-nvPe] -t <receive_resume_token>\n"));
        case HELP_SET:
                return (gettext("\tset <property=value> ... "
@@ -386,6 +393,9 @@ get_usage(zfs_help_t idx)
                    "\tchange-key -i [-l] <filesystem|volume>\n"));
        case HELP_VERSION:
                return (gettext("\tversion\n"));
+       case HELP_REDACT:
+               return (gettext("\tredact <snapshot> <bookmark> "
+                   "<redaction_snapshot> ..."));
        }
 
        abort();
@@ -543,6 +553,8 @@ usage(boolean_t requested)
                (void) fprintf(fp, "YES       NO   <size> | none\n");
                (void) fprintf(fp, "\t%-15s ", "written@<snap>");
                (void) fprintf(fp, " NO       NO   <size>\n");
+               (void) fprintf(fp, "\t%-15s ", "written#<bookmark>");
+               (void) fprintf(fp, " NO       NO   <size>\n");
 
                (void) fprintf(fp, gettext("\nSizes are specified in bytes "
                    "with standard units such as K, M, G, etc.\n"));
@@ -1501,6 +1513,13 @@ zfs_do_destroy(int argc, char **argv)
                        return (-1);
                }
 
+               /*
+                * Unfortunately, zfs_bookmark() doesn't honor the
+                * casesensitivity setting.  However, we can't simply
+                * remove this check, because lzc_destroy_bookmarks()
+                * ignores non-existent bookmarks, so this is necessary
+                * to get a proper error message.
+                */
                if (!zfs_bookmark_exists(argv[0])) {
                        (void) fprintf(stderr, gettext("bookmark '%s' "
                            "does not exist.\n"), argv[0]);
@@ -3595,6 +3614,73 @@ zfs_do_promote(int argc, char **argv)
        return (ret);
 }
 
+static int
+zfs_do_redact(int argc, char **argv)
+{
+       char *snap = NULL;
+       char *bookname = NULL;
+       char **rsnaps = NULL;
+       int numrsnaps = 0;
+       argv++;
+       argc--;
+       if (argc < 3) {
+               (void) fprintf(stderr, gettext("too few arguments"));
+               usage(B_FALSE);
+       }
+
+       snap = argv[0];
+       bookname = argv[1];
+       rsnaps = argv + 2;
+       numrsnaps = argc - 2;
+
+       nvlist_t *rsnapnv = fnvlist_alloc();
+
+       for (int i = 0; i < numrsnaps; i++) {
+               fnvlist_add_boolean(rsnapnv, rsnaps[i]);
+       }
+
+       int err = lzc_redact(snap, bookname, rsnapnv);
+       fnvlist_free(rsnapnv);
+
+       switch (err) {
+       case 0:
+               break;
+       case ENOENT:
+               (void) fprintf(stderr,
+                   gettext("provided snapshot %s does not exist"), snap);
+               break;
+       case EEXIST:
+               (void) fprintf(stderr, gettext("specified redaction bookmark "
+                   "(%s) provided already exists"), bookname);
+               break;
+       case ENAMETOOLONG:
+               (void) fprintf(stderr, gettext("provided bookmark name cannot "
+                   "be used, final name would be too long"));
+               break;
+       case E2BIG:
+               (void) fprintf(stderr, gettext("too many redaction snapshots "
+                   "specified"));
+               break;
+       case EINVAL:
+               (void) fprintf(stderr, gettext("redaction snapshot must be "
+                   "descendent of snapshot being redacted"));
+               break;
+       case EALREADY:
+               (void) fprintf(stderr, gettext("attempted to redact redacted "
+                   "dataset or with respect to redacted dataset"));
+               break;
+       case ENOTSUP:
+               (void) fprintf(stderr, gettext("redaction bookmarks feature "
+                   "not enabled"));
+               break;
+       default:
+               (void) fprintf(stderr, gettext("internal error: %s"),
+                   strerror(errno));
+       }
+
+       return (err);
+}
+
 /*
  * zfs rollback [-rRf] <snapshot>
  *
@@ -4006,6 +4092,7 @@ usage:
        return (-1);
 }
 
+
 /*
  * Send a backup stream to stdout.
  */
@@ -4020,10 +4107,11 @@ zfs_do_send(int argc, char **argv)
        sendflags_t flags = { 0 };
        int c, err;
        nvlist_t *dbgnv = NULL;
-       boolean_t extraverbose = B_FALSE;
+       char *redactbook = NULL;
 
        struct option long_options[] = {
                {"replicate",   no_argument,            NULL, 'R'},
+               {"redact",      required_argument,      NULL, 'd'},
                {"props",       no_argument,            NULL, 'p'},
                {"parsable",    no_argument,            NULL, 'P'},
                {"dedup",       no_argument,            NULL, 'D'},
@@ -4040,8 +4128,8 @@ zfs_do_send(int argc, char **argv)
        };
 
        /* check options */
-       while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwb", long_options,
-           NULL)) != -1) {
+       while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:",
+           long_options, NULL)) != -1) {
                switch (c) {
                case 'i':
                        if (fromname)
@@ -4057,6 +4145,9 @@ zfs_do_send(int argc, char **argv)
                case 'R':
                        flags.replicate = B_TRUE;
                        break;
+               case 'd':
+                       redactbook = optarg;
+                       break;
                case 'p':
                        flags.props = B_TRUE;
                        break;
@@ -4068,12 +4159,9 @@ zfs_do_send(int argc, char **argv)
                        break;
                case 'P':
                        flags.parsable = B_TRUE;
-                       flags.verbose = B_TRUE;
                        break;
                case 'v':
-                       if (flags.verbose)
-                               extraverbose = B_TRUE;
-                       flags.verbose = B_TRUE;
+                       flags.verbosity++;
                        flags.progress = B_TRUE;
                        break;
                case 'D':
@@ -4141,19 +4229,22 @@ zfs_do_send(int argc, char **argv)
                }
        }
 
+       if (flags.parsable && flags.verbosity == 0)
+               flags.verbosity = 1;
+
        argc -= optind;
        argv += optind;
 
        if (resume_token != NULL) {
                if (fromname != NULL || flags.replicate || flags.props ||
-                   flags.backup || flags.dedup) {
+                   flags.backup || flags.dedup || flags.holds ||
+                   redactbook != NULL) {
                        (void) fprintf(stderr,
                            gettext("invalid flags combined with -t\n"));
                        usage(B_FALSE);
                }
-               if (argc != 0) {
-                       (void) fprintf(stderr, gettext("no additional "
-                           "arguments are permitted with -t\n"));
+               if (argc > 0) {
+                       (void) fprintf(stderr, gettext("too many arguments\n"));
                        usage(B_FALSE);
                }
        } else {
@@ -4168,6 +4259,12 @@ zfs_do_send(int argc, char **argv)
                }
        }
 
+       if (flags.raw && redactbook != NULL) {
+               (void) fprintf(stderr,
+                   gettext("Error: raw sends may not be redacted.\n"));
+               return (1);
+       }
+
        if (!flags.dryrun && isatty(STDOUT_FILENO)) {
                (void) fprintf(stderr,
                    gettext("Error: Stream can not be written to a terminal.\n"
@@ -4181,43 +4278,70 @@ zfs_do_send(int argc, char **argv)
        }
 
        /*
-        * Special case sending a filesystem, or from a bookmark.
+        * For everything except -R and -I, use the new, cleaner code path.
         */
-       if (strchr(argv[0], '@') == NULL ||
-           (fromname && strchr(fromname, '#') != NULL)) {
+       if (!(flags.replicate || flags.doall)) {
                char frombuf[ZFS_MAX_DATASET_NAME_LEN];
 
-               if (flags.replicate || flags.doall || flags.props ||
-                   flags.backup || flags.dedup || flags.holds ||
-                   (strchr(argv[0], '@') == NULL &&
-                   (flags.dryrun || flags.verbose || flags.progress))) {
-                       (void) fprintf(stderr, gettext("Error: "
-                           "Unsupported flag with filesystem or bookmark.\n"));
-                       return (1);
+               if (redactbook != NULL) {
+                       if (strchr(argv[0], '@') == NULL) {
+                               (void) fprintf(stderr, gettext("Error: Cannot "
+                                   "do a redacted send to a filesystem.\n"));
+                               return (1);
+                       }
                }
 
                zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET);
                if (zhp == NULL)
                        return (1);
 
+               if (fromname != NULL && (strchr(fromname, '#') == NULL &&
+                   strchr(fromname, '@') == NULL)) {
+                       /*
+                        * Neither bookmark or snapshot was specified.  Print a
+                        * warning, and assume snapshot.
+                        */
+                       (void) fprintf(stderr, "Warning: incremental source "
+                           "didn't specify type, assuming snapshot. Use '@' "
+                           "or '#' prefix to avoid ambiguity.\n");
+                       (void) snprintf(frombuf, sizeof (frombuf), "@%s",
+                           fromname);
+                       fromname = frombuf;
+               }
                if (fromname != NULL &&
                    (fromname[0] == '#' || fromname[0] == '@')) {
                        /*
                         * Incremental source name begins with # or @.
                         * Default to same fs as target.
                         */
+                       char tmpbuf[ZFS_MAX_DATASET_NAME_LEN];
+                       (void) strlcpy(tmpbuf, fromname, sizeof (tmpbuf));
                        (void) strlcpy(frombuf, argv[0], sizeof (frombuf));
                        cp = strchr(frombuf, '@');
                        if (cp != NULL)
                                *cp = '\0';
-                       (void) strlcat(frombuf, fromname, sizeof (frombuf));
+                       (void) strlcat(frombuf, tmpbuf, sizeof (frombuf));
                        fromname = frombuf;
                }
-               err = zfs_send_one(zhp, fromname, STDOUT_FILENO, flags);
+               err = zfs_send_one(zhp, fromname, STDOUT_FILENO, &flags,
+                   redactbook);
                zfs_close(zhp);
                return (err != 0);
        }
 
+       if (fromname != NULL && strchr(fromname, '#')) {
+               (void) fprintf(stderr,
+                   gettext("Error: multiple snapshots cannot be "
+                   "sent from a bookmark.\n"));
+               return (1);
+       }
+
+       if (redactbook != NULL) {
+               (void) fprintf(stderr, gettext("Error: multiple snapshots "
+                   "cannot be sent redacted.\n"));
+               return (1);
+       }
+
        cp = strchr(argv[0], '@');
        *cp = '\0';
        toname = cp + 1;
@@ -4261,9 +4385,9 @@ zfs_do_send(int argc, char **argv)
                flags.doall = B_TRUE;
 
        err = zfs_send(zhp, fromname, toname, &flags, STDOUT_FILENO, NULL, 0,
-           extraverbose ? &dbgnv : NULL);
+           flags.verbosity >= 3 ? &dbgnv : NULL);
 
-       if (extraverbose && dbgnv != NULL) {
+       if (flags.verbosity >= 3 && dbgnv != NULL) {
                /*
                 * dump_nvlist prints to stdout, but that's been
                 * redirected to a file.  Make it print to stderr
@@ -6379,6 +6503,17 @@ share_mount_one(zfs_handle_t *zhp, int op, int flags, char *protocol,
                return (1);
        }
 
+       if (zfs_prop_get_int(zhp, ZFS_PROP_REDACTED) && !(flags & MS_FORCE)) {
+               if (!explicit)
+                       return (0);
+
+               (void) fprintf(stderr, gettext("cannot %s '%s': "
+                   "Dataset is not complete, was created by receiving "
+                   "a redacted zfs send stream.\n"), cmdname,
+                   zfs_get_name(zhp));
+               return (1);
+       }
+
        /*
         * At this point, we have verified that the mountpoint and/or
         * shareopts are appropriate for auto management. If the
@@ -6537,7 +6672,7 @@ share_mount(int op, int argc, char **argv)
        int flags = 0;
 
        /* check options */
-       while ((c = getopt(argc, argv, op == OP_MOUNT ? ":alvo:O" : "al"))
+       while ((c = getopt(argc, argv, op == OP_MOUNT ? ":alvo:Of" : "al"))
            != -1) {
                switch (c) {
                case 'a':
@@ -6565,6 +6700,9 @@ share_mount(int op, int argc, char **argv)
                case 'O':
                        flags |= MS_OVERLAY;
                        break;
+               case 'f':
+                       flags |= MS_FORCE;
+                       break;
                case ':':
                        (void) fprintf(stderr, gettext("missing argument for "
                            "'%c' option\n"), optopt);