Allow zfs send to exclude datasets

Add support for a -exclude/-X option to `zfs send` to allow dataset 
hierarchies to be excluded.

Snapshots can be excluded using a channel program; however,
this can result in failures with 'zfs send -R'; this option allows 
them to be excluded.  Fortunately, this required a change only to 
cmd/zfs/zfs_main.c, using the already-existing callback argument 
to zfs_send() that is currently unused.

Reviewed-by: Paul Dagnelie <pcd@delphix.com>
Reviewed-by: Christian Schwarz <christian.schwarz@nutanix.com>
Reviewed-by: Ahelenia Ziemiańska <nabijaczleweli@nabijaczleweli.xyz>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Co-authored-by: Sean Eric Fagan <kithrup@mac.com>
Signed-off-by: Sean Eric Fagan <kithrup@mac.com>
Closes #13158
This commit is contained in:
Sean Eric Fagan
2022-03-18 17:02:12 -07:00
committed by GitHub
parent 3ce3d30532
commit 565089f592
9 changed files with 474 additions and 6 deletions
+92 -5
View File
@@ -315,8 +315,9 @@ get_usage(zfs_help_t idx)
case HELP_ROLLBACK:
return (gettext("\trollback [-rRf] <snapshot>\n"));
case HELP_SEND:
return (gettext("\tsend [-DnPpRvLecwhb] [-[i|I] snapshot] "
"<snapshot>\n"
return (gettext("\tsend [-DnPpRvLecwhb] "
"[-X dataset[,dataset]...] "
"[-[i|I] snapshot] <snapshot>\n"
"\tsend [-DnvPLecw] [-i snapshot|bookmark] "
"<filesystem|volume|snapshot>\n"
"\tsend [-DnPpvLec] [-i bookmark|snapshot] "
@@ -4317,6 +4318,77 @@ usage:
return (-1);
}
typedef struct zfs_send_exclude_arg {
size_t count;
char **list;
} zfs_send_exclude_arg_t;
/*
* This function creates the zfs_send_exclude_arg_t
* object described above; it can be called multiple
* times, and the input can be comma-separated.
* This is NOT the most efficient data layout; however,
* I couldn't think of a non-pathological case where
* it should have more than a couple dozen instances
* of excludes. If that turns out to be used in
* practice, we might want to instead use a tree.
*/
static void
add_dataset_excludes(char *exclude, zfs_send_exclude_arg_t *context)
{
char *tok;
while ((tok = strsep(&exclude, ",")) != NULL) {
if (!zfs_name_valid(tok, ZFS_TYPE_DATASET) ||
strchr(tok, '/') == NULL) {
(void) fprintf(stderr, gettext("-X %s: "
"not a valid non-root dataset name.\n"), tok);
usage(B_FALSE);
}
context->list = safe_realloc(context->list,
(sizeof (char *)) * (context->count + 1));
context->list[context->count++] = tok;
}
}
static void
free_dataset_excludes(zfs_send_exclude_arg_t *exclude_list)
{
free(exclude_list->list);
}
/*
* This is the call back used by zfs_send to
* determine if a dataset should be skipped.
* As stated above, this is not the most efficient
* data structure to use, but as long as the
* number of excluded datasets is relatively
* small (a couple of dozen or so), it won't
* have a big impact on performance on modern
* processors. Since it's excluding hierarchies,
* we'd probably want to move to a more complex
* tree structure in that case.
*/
static boolean_t
zfs_do_send_exclude(zfs_handle_t *zhp, void *context)
{
zfs_send_exclude_arg_t *exclude = context;
const char *name = zfs_get_name(zhp);
for (size_t indx = 0; indx < exclude->count; indx++) {
char *exclude_name = exclude->list[indx];
size_t len = strlen(exclude_name);
/* If it's shorter, it can't possibly match */
if (strlen(name) < len)
continue;
if (strncmp(name, exclude_name, len) == 0 &&
(name[len] == '/' || name[len] == '\0' ||
name[len] == '@')) {
return (B_FALSE);
}
}
return (B_TRUE);
}
/*
* Send a backup stream to stdout.
@@ -4333,6 +4405,7 @@ zfs_do_send(int argc, char **argv)
int c, err;
nvlist_t *dbgnv = NULL;
char *redactbook = NULL;
zfs_send_exclude_arg_t exclude_context = { 0 };
struct option long_options[] = {
{"replicate", no_argument, NULL, 'R'},
@@ -4351,13 +4424,17 @@ zfs_do_send(int argc, char **argv)
{"backup", no_argument, NULL, 'b'},
{"holds", no_argument, NULL, 'h'},
{"saved", no_argument, NULL, 'S'},
{"exclude", required_argument, NULL, 'X'},
{0, 0, 0, 0}
};
/* check options */
while ((c = getopt_long(argc, argv, ":i:I:RsDpvnPLeht:cwbd:S",
while ((c = getopt_long(argc, argv, ":i:I:RsDpvnPLeht:cwbd:SX:",
long_options, NULL)) != -1) {
switch (c) {
case 'X':
add_dataset_excludes(optarg, &exclude_context);
break;
case 'i':
if (fromname)
usage(B_FALSE);
@@ -4467,6 +4544,13 @@ zfs_do_send(int argc, char **argv)
if (flags.parsable && flags.verbosity == 0)
flags.verbosity = 1;
if (exclude_context.count > 0 && !flags.replicate) {
(void) fprintf(stderr, gettext("Cannot specify "
"dataset exclusion (-X) on a non-recursive "
"send.\n"));
return (1);
}
argc -= optind;
argv += optind;
@@ -4648,8 +4732,11 @@ zfs_do_send(int argc, char **argv)
if (flags.replicate && fromname == NULL)
flags.doall = B_TRUE;
err = zfs_send(zhp, fromname, toname, &flags, STDOUT_FILENO, NULL, 0,
flags.verbosity >= 3 ? &dbgnv : NULL);
err = zfs_send(zhp, fromname, toname, &flags, STDOUT_FILENO,
exclude_context.count > 0 ? zfs_do_send_exclude : NULL,
&exclude_context, flags.verbosity >= 3 ? &dbgnv : NULL);
free_dataset_excludes(&exclude_context);
if (flags.verbosity >= 3 && dbgnv != NULL) {
/*