Skip to content

Commit

Permalink
Implement Redacted Send/Receive
Browse files Browse the repository at this point in the history
Redacted send/receive allows users to send subsets of their data to 
a target system. One possible use case for this feature is to not 
transmit sensitive information to a data warehousing, test/dev, or 
analytics environment. Another is to save space by not replicating 
unimportant data within a given dataset, for example in backup tools 
like zrepl.

Redacted send/receive is a three-stage process. First, a clone (or 
clones) is made of the snapshot to be sent to the target. In this 
clone (or clones), all unnecessary or unwanted data is removed or
modified. This clone is then snapshotted to create the "redaction 
snapshot" (or snapshots). Second, the new zfs redact command is used 
to create a redaction bookmark. The redaction bookmark stores the 
list of blocks in a snapshot that were modified by the redaction 
snapshot(s). Finally, the redaction bookmark is passed as a parameter 
to zfs send. When sending to the snapshot that was redacted, the
redaction bookmark is used to filter out blocks that contain sensitive 
or unwanted information, and those blocks are not included in the send 
stream.  When sending from the redaction bookmark, the blocks it 
contains are considered as candidate blocks in addition to those 
blocks in the destination snapshot that were modified since the 
creation_txg of the redaction bookmark.  This step is necessary to 
allow the target to rehydrate data in the case where some blocks are 
accidentally or unnecessarily modified in the redaction snapshot.

The changes to bookmarks to enable fast space estimation involve 
adding deadlists to bookmarks. There is also logic to manage the 
life cycles of these deadlists.

The new size estimation process operates in cases where previously 
an accurate estimate could not be provided. In those cases, a send 
is performed where no data blocks are read, reducing the runtime 
significantly and providing a byte-accurate size estimate.

Reviewed-by: Dan Kimmel <[email protected]>
Reviewed-by: Matt Ahrens <[email protected]>
Reviewed-by: Prashanth Sreenivasa <[email protected]>
Reviewed-by: John Kennedy <[email protected]>
Reviewed-by: George Wilson <[email protected]>
Reviewed-by: Chris Williamson <[email protected]>
Reviewed-by: Pavel Zhakarov <[email protected]>
Reviewed-by: Sebastien Roy <[email protected]>
Reviewed-by: Prakash Surya <[email protected]>
Reviewed-by: Brian Behlendorf <[email protected]>
Signed-off-by: Paul Dagnelie <[email protected]>
Closes openzfs#7958
  • Loading branch information
pcd1193182 authored and behlendorf committed Jun 19, 2019
1 parent c1b5801 commit 30af21b
Show file tree
Hide file tree
Showing 103 changed files with 10,930 additions and 2,085 deletions.
282 changes: 252 additions & 30 deletions cmd/zdb/zdb.c

Large diffs are not rendered by default.

194 changes: 166 additions & 28 deletions cmd/zfs/zfs_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include <assert.h>
#include <ctype.h>
#include <sys/debug.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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]))
Expand Down Expand Up @@ -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:
Expand All @@ -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> ... "
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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>
*
Expand Down Expand Up @@ -4006,6 +4092,7 @@ zfs_do_snapshot(int argc, char **argv)
return (-1);
}


/*
* Send a backup stream to stdout.
*/
Expand All @@ -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'},
Expand All @@ -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)
Expand All @@ -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;
Expand All @@ -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':
Expand Down Expand Up @@ -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 {
Expand All @@ -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"
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 30af21b

Please sign in to comment.