mirror of
https://git.proxmox.com/git/mirror_zfs.git
synced 2026-05-22 18:40:43 +03:00
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:
+92
-5
@@ -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) {
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user