Skip to content

Commit 30af21b

Browse files
pcd1193182behlendorf
authored andcommitted
Implement Redacted Send/Receive
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 #7958
1 parent c1b5801 commit 30af21b

File tree

103 files changed

+10930
-2085
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+10930
-2085
lines changed

cmd/zdb/zdb.c

Lines changed: 252 additions & 30 deletions
Large diffs are not rendered by default.

cmd/zfs/zfs_main.c

Lines changed: 166 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
#include <assert.h>
3535
#include <ctype.h>
36+
#include <sys/debug.h>
3637
#include <errno.h>
3738
#include <getopt.h>
3839
#include <libgen.h>
@@ -119,6 +120,7 @@ static int zfs_do_unload_key(int argc, char **argv);
119120
static int zfs_do_change_key(int argc, char **argv);
120121
static int zfs_do_project(int argc, char **argv);
121122
static int zfs_do_version(int argc, char **argv);
123+
static int zfs_do_redact(int argc, char **argv);
122124

123125
/*
124126
* Enable a reasonable set of defaults for libumem debugging on DEBUG builds.
@@ -173,7 +175,8 @@ typedef enum {
173175
HELP_LOAD_KEY,
174176
HELP_UNLOAD_KEY,
175177
HELP_CHANGE_KEY,
176-
HELP_VERSION
178+
HELP_VERSION,
179+
HELP_REDACT,
177180
} zfs_help_t;
178181

179182
typedef struct zfs_command {
@@ -238,6 +241,7 @@ static zfs_command_t command_table[] = {
238241
{ "load-key", zfs_do_load_key, HELP_LOAD_KEY },
239242
{ "unload-key", zfs_do_unload_key, HELP_UNLOAD_KEY },
240243
{ "change-key", zfs_do_change_key, HELP_CHANGE_KEY },
244+
{ "redact", zfs_do_redact, HELP_REDACT },
241245
};
242246

243247
#define NCOMMAND (sizeof (command_table) / sizeof (command_table[0]))
@@ -279,7 +283,7 @@ get_usage(zfs_help_t idx)
279283
"[filesystem|volume|snapshot] ...\n"));
280284
case HELP_MOUNT:
281285
return (gettext("\tmount\n"
282-
"\tmount [-lvO] [-o opts] <-a | filesystem>\n"));
286+
"\tmount [-flvO] [-o opts] <-a | filesystem>\n"));
283287
case HELP_PROMOTE:
284288
return (gettext("\tpromote <clone-filesystem>\n"));
285289
case HELP_RECEIVE:
@@ -302,6 +306,9 @@ get_usage(zfs_help_t idx)
302306
"<snapshot>\n"
303307
"\tsend [-nvPLecw] [-i snapshot|bookmark] "
304308
"<filesystem|volume|snapshot>\n"
309+
"[-i bookmark] <snapshot> <bookmark_name>\n"
310+
"\tsend [-DnPpvLecr] [-i bookmark|snapshot] "
311+
"--redact <bookmark> <snapshot>\n"
305312
"\tsend [-nvPe] -t <receive_resume_token>\n"));
306313
case HELP_SET:
307314
return (gettext("\tset <property=value> ... "
@@ -386,6 +393,9 @@ get_usage(zfs_help_t idx)
386393
"\tchange-key -i [-l] <filesystem|volume>\n"));
387394
case HELP_VERSION:
388395
return (gettext("\tversion\n"));
396+
case HELP_REDACT:
397+
return (gettext("\tredact <snapshot> <bookmark> "
398+
"<redaction_snapshot> ..."));
389399
}
390400

391401
abort();
@@ -543,6 +553,8 @@ usage(boolean_t requested)
543553
(void) fprintf(fp, "YES NO <size> | none\n");
544554
(void) fprintf(fp, "\t%-15s ", "written@<snap>");
545555
(void) fprintf(fp, " NO NO <size>\n");
556+
(void) fprintf(fp, "\t%-15s ", "written#<bookmark>");
557+
(void) fprintf(fp, " NO NO <size>\n");
546558

547559
(void) fprintf(fp, gettext("\nSizes are specified in bytes "
548560
"with standard units such as K, M, G, etc.\n"));
@@ -1501,6 +1513,13 @@ zfs_do_destroy(int argc, char **argv)
15011513
return (-1);
15021514
}
15031515

1516+
/*
1517+
* Unfortunately, zfs_bookmark() doesn't honor the
1518+
* casesensitivity setting. However, we can't simply
1519+
* remove this check, because lzc_destroy_bookmarks()
1520+
* ignores non-existent bookmarks, so this is necessary
1521+
* to get a proper error message.
1522+
*/
15041523
if (!zfs_bookmark_exists(argv[0])) {
15051524
(void) fprintf(stderr, gettext("bookmark '%s' "
15061525
"does not exist.\n"), argv[0]);
@@ -3595,6 +3614,73 @@ zfs_do_promote(int argc, char **argv)
35953614
return (ret);
35963615
}
35973616

3617+
static int
3618+
zfs_do_redact(int argc, char **argv)
3619+
{
3620+
char *snap = NULL;
3621+
char *bookname = NULL;
3622+
char **rsnaps = NULL;
3623+
int numrsnaps = 0;
3624+
argv++;
3625+
argc--;
3626+
if (argc < 3) {
3627+
(void) fprintf(stderr, gettext("too few arguments"));
3628+
usage(B_FALSE);
3629+
}
3630+
3631+
snap = argv[0];
3632+
bookname = argv[1];
3633+
rsnaps = argv + 2;
3634+
numrsnaps = argc - 2;
3635+
3636+
nvlist_t *rsnapnv = fnvlist_alloc();
3637+
3638+
for (int i = 0; i < numrsnaps; i++) {
3639+
fnvlist_add_boolean(rsnapnv, rsnaps[i]);
3640+
}
3641+
3642+
int err = lzc_redact(snap, bookname, rsnapnv);
3643+
fnvlist_free(rsnapnv);
3644+
3645+
switch (err) {
3646+
case 0:
3647+
break;
3648+
case ENOENT:
3649+
(void) fprintf(stderr,
3650+
gettext("provided snapshot %s does not exist"), snap);
3651+
break;
3652+
case EEXIST:
3653+
(void) fprintf(stderr, gettext("specified redaction bookmark "
3654+
"(%s) provided already exists"), bookname);
3655+
break;
3656+
case ENAMETOOLONG:
3657+
(void) fprintf(stderr, gettext("provided bookmark name cannot "
3658+
"be used, final name would be too long"));
3659+
break;
3660+
case E2BIG:
3661+
(void) fprintf(stderr, gettext("too many redaction snapshots "
3662+
"specified"));
3663+
break;
3664+
case EINVAL:
3665+
(void) fprintf(stderr, gettext("redaction snapshot must be "
3666+
"descendent of snapshot being redacted"));
3667+
break;
3668+
case EALREADY:
3669+
(void) fprintf(stderr, gettext("attempted to redact redacted "
3670+
"dataset or with respect to redacted dataset"));
3671+
break;
3672+
case ENOTSUP:
3673+
(void) fprintf(stderr, gettext("redaction bookmarks feature "
3674+
"not enabled"));
3675+
break;
3676+
default:
3677+
(void) fprintf(stderr, gettext("internal error: %s"),
3678+
strerror(errno));
3679+
}
3680+
3681+
return (err);
3682+
}
3683+
35983684
/*
35993685
* zfs rollback [-rRf] <snapshot>
36003686
*
@@ -4006,6 +4092,7 @@ zfs_do_snapshot(int argc, char **argv)
40064092
return (-1);
40074093
}
40084094

4095+
40094096
/*
40104097
* Send a backup stream to stdout.
40114098
*/
@@ -4020,10 +4107,11 @@ zfs_do_send(int argc, char **argv)
40204107
sendflags_t flags = { 0 };
40214108
int c, err;
40224109
nvlist_t *dbgnv = NULL;
4023-
boolean_t extraverbose = B_FALSE;
4110+
char *redactbook = NULL;
40244111

40254112
struct option long_options[] = {
40264113
{"replicate", no_argument, NULL, 'R'},
4114+
{"redact", required_argument, NULL, 'd'},
40274115
{"props", no_argument, NULL, 'p'},
40284116
{"parsable", no_argument, NULL, 'P'},
40294117
{"dedup", no_argument, NULL, 'D'},
@@ -4040,8 +4128,8 @@ zfs_do_send(int argc, char **argv)
40404128
};
40414129

40424130
/* check options */
4043-
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwb", long_options,
4044-
NULL)) != -1) {
4131+
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:",
4132+
long_options, NULL)) != -1) {
40454133
switch (c) {
40464134
case 'i':
40474135
if (fromname)
@@ -4057,6 +4145,9 @@ zfs_do_send(int argc, char **argv)
40574145
case 'R':
40584146
flags.replicate = B_TRUE;
40594147
break;
4148+
case 'd':
4149+
redactbook = optarg;
4150+
break;
40604151
case 'p':
40614152
flags.props = B_TRUE;
40624153
break;
@@ -4068,12 +4159,9 @@ zfs_do_send(int argc, char **argv)
40684159
break;
40694160
case 'P':
40704161
flags.parsable = B_TRUE;
4071-
flags.verbose = B_TRUE;
40724162
break;
40734163
case 'v':
4074-
if (flags.verbose)
4075-
extraverbose = B_TRUE;
4076-
flags.verbose = B_TRUE;
4164+
flags.verbosity++;
40774165
flags.progress = B_TRUE;
40784166
break;
40794167
case 'D':
@@ -4141,19 +4229,22 @@ zfs_do_send(int argc, char **argv)
41414229
}
41424230
}
41434231

4232+
if (flags.parsable && flags.verbosity == 0)
4233+
flags.verbosity = 1;
4234+
41444235
argc -= optind;
41454236
argv += optind;
41464237

41474238
if (resume_token != NULL) {
41484239
if (fromname != NULL || flags.replicate || flags.props ||
4149-
flags.backup || flags.dedup) {
4240+
flags.backup || flags.dedup || flags.holds ||
4241+
redactbook != NULL) {
41504242
(void) fprintf(stderr,
41514243
gettext("invalid flags combined with -t\n"));
41524244
usage(B_FALSE);
41534245
}
4154-
if (argc != 0) {
4155-
(void) fprintf(stderr, gettext("no additional "
4156-
"arguments are permitted with -t\n"));
4246+
if (argc > 0) {
4247+
(void) fprintf(stderr, gettext("too many arguments\n"));
41574248
usage(B_FALSE);
41584249
}
41594250
} else {
@@ -4168,6 +4259,12 @@ zfs_do_send(int argc, char **argv)
41684259
}
41694260
}
41704261

4262+
if (flags.raw && redactbook != NULL) {
4263+
(void) fprintf(stderr,
4264+
gettext("Error: raw sends may not be redacted.\n"));
4265+
return (1);
4266+
}
4267+
41714268
if (!flags.dryrun && isatty(STDOUT_FILENO)) {
41724269
(void) fprintf(stderr,
41734270
gettext("Error: Stream can not be written to a terminal.\n"
@@ -4181,43 +4278,70 @@ zfs_do_send(int argc, char **argv)
41814278
}
41824279

41834280
/*
4184-
* Special case sending a filesystem, or from a bookmark.
4281+
* For everything except -R and -I, use the new, cleaner code path.
41854282
*/
4186-
if (strchr(argv[0], '@') == NULL ||
4187-
(fromname && strchr(fromname, '#') != NULL)) {
4283+
if (!(flags.replicate || flags.doall)) {
41884284
char frombuf[ZFS_MAX_DATASET_NAME_LEN];
41894285

4190-
if (flags.replicate || flags.doall || flags.props ||
4191-
flags.backup || flags.dedup || flags.holds ||
4192-
(strchr(argv[0], '@') == NULL &&
4193-
(flags.dryrun || flags.verbose || flags.progress))) {
4194-
(void) fprintf(stderr, gettext("Error: "
4195-
"Unsupported flag with filesystem or bookmark.\n"));
4196-
return (1);
4286+
if (redactbook != NULL) {
4287+
if (strchr(argv[0], '@') == NULL) {
4288+
(void) fprintf(stderr, gettext("Error: Cannot "
4289+
"do a redacted send to a filesystem.\n"));
4290+
return (1);
4291+
}
41974292
}
41984293

41994294
zhp = zfs_open(g_zfs, argv[0], ZFS_TYPE_DATASET);
42004295
if (zhp == NULL)
42014296
return (1);
42024297

4298+
if (fromname != NULL && (strchr(fromname, '#') == NULL &&
4299+
strchr(fromname, '@') == NULL)) {
4300+
/*
4301+
* Neither bookmark or snapshot was specified. Print a
4302+
* warning, and assume snapshot.
4303+
*/
4304+
(void) fprintf(stderr, "Warning: incremental source "
4305+
"didn't specify type, assuming snapshot. Use '@' "
4306+
"or '#' prefix to avoid ambiguity.\n");
4307+
(void) snprintf(frombuf, sizeof (frombuf), "@%s",
4308+
fromname);
4309+
fromname = frombuf;
4310+
}
42034311
if (fromname != NULL &&
42044312
(fromname[0] == '#' || fromname[0] == '@')) {
42054313
/*
42064314
* Incremental source name begins with # or @.
42074315
* Default to same fs as target.
42084316
*/
4317+
char tmpbuf[ZFS_MAX_DATASET_NAME_LEN];
4318+
(void) strlcpy(tmpbuf, fromname, sizeof (tmpbuf));
42094319
(void) strlcpy(frombuf, argv[0], sizeof (frombuf));
42104320
cp = strchr(frombuf, '@');
42114321
if (cp != NULL)
42124322
*cp = '\0';
4213-
(void) strlcat(frombuf, fromname, sizeof (frombuf));
4323+
(void) strlcat(frombuf, tmpbuf, sizeof (frombuf));
42144324
fromname = frombuf;
42154325
}
4216-
err = zfs_send_one(zhp, fromname, STDOUT_FILENO, flags);
4326+
err = zfs_send_one(zhp, fromname, STDOUT_FILENO, &flags,
4327+
redactbook);
42174328
zfs_close(zhp);
42184329
return (err != 0);
42194330
}
42204331

4332+
if (fromname != NULL && strchr(fromname, '#')) {
4333+
(void) fprintf(stderr,
4334+
gettext("Error: multiple snapshots cannot be "
4335+
"sent from a bookmark.\n"));
4336+
return (1);
4337+
}
4338+
4339+
if (redactbook != NULL) {
4340+
(void) fprintf(stderr, gettext("Error: multiple snapshots "
4341+
"cannot be sent redacted.\n"));
4342+
return (1);
4343+
}
4344+
42214345
cp = strchr(argv[0], '@');
42224346
*cp = '\0';
42234347
toname = cp + 1;
@@ -4261,9 +4385,9 @@ zfs_do_send(int argc, char **argv)
42614385
flags.doall = B_TRUE;
42624386

42634387
err = zfs_send(zhp, fromname, toname, &flags, STDOUT_FILENO, NULL, 0,
4264-
extraverbose ? &dbgnv : NULL);
4388+
flags.verbosity >= 3 ? &dbgnv : NULL);
42654389

4266-
if (extraverbose && dbgnv != NULL) {
4390+
if (flags.verbosity >= 3 && dbgnv != NULL) {
42674391
/*
42684392
* dump_nvlist prints to stdout, but that's been
42694393
* 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,
63796503
return (1);
63806504
}
63816505

6506+
if (zfs_prop_get_int(zhp, ZFS_PROP_REDACTED) && !(flags & MS_FORCE)) {
6507+
if (!explicit)
6508+
return (0);
6509+
6510+
(void) fprintf(stderr, gettext("cannot %s '%s': "
6511+
"Dataset is not complete, was created by receiving "
6512+
"a redacted zfs send stream.\n"), cmdname,
6513+
zfs_get_name(zhp));
6514+
return (1);
6515+
}
6516+
63826517
/*
63836518
* At this point, we have verified that the mountpoint and/or
63846519
* shareopts are appropriate for auto management. If the
@@ -6537,7 +6672,7 @@ share_mount(int op, int argc, char **argv)
65376672
int flags = 0;
65386673

65396674
/* check options */
6540-
while ((c = getopt(argc, argv, op == OP_MOUNT ? ":alvo:O" : "al"))
6675+
while ((c = getopt(argc, argv, op == OP_MOUNT ? ":alvo:Of" : "al"))
65416676
!= -1) {
65426677
switch (c) {
65436678
case 'a':
@@ -6565,6 +6700,9 @@ share_mount(int op, int argc, char **argv)
65656700
case 'O':
65666701
flags |= MS_OVERLAY;
65676702
break;
6703+
case 'f':
6704+
flags |= MS_FORCE;
6705+
break;
65686706
case ':':
65696707
(void) fprintf(stderr, gettext("missing argument for "
65706708
"'%c' option\n"), optopt);

0 commit comments

Comments
 (0)