Skip to content

Allow zfs to send replication streams with missing snapshots #11710

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion cmd/zfs/zfs_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -4378,6 +4378,7 @@ zfs_do_send(int argc, char **argv)

struct option long_options[] = {
{"replicate", no_argument, NULL, 'R'},
{"skip-missing", no_argument, NULL, 's'},
{"redact", required_argument, NULL, 'd'},
{"props", no_argument, NULL, 'p'},
{"parsable", no_argument, NULL, 'P'},
Expand All @@ -4396,7 +4397,7 @@ zfs_do_send(int argc, char **argv)
};

/* check options */
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:S",
while ((c = getopt_long(argc, argv, ":i:I:RsDpvnPLeht:cwbd:S",
long_options, NULL)) != -1) {
switch (c) {
case 'i':
Expand All @@ -4413,6 +4414,9 @@ zfs_do_send(int argc, char **argv)
case 'R':
flags.replicate = B_TRUE;
break;
case 's':
flags.skipmissing = B_TRUE;
break;
case 'd':
redactbook = optarg;
break;
Expand Down Expand Up @@ -4577,6 +4581,13 @@ zfs_do_send(int argc, char **argv)
resume_token));
}

if (flags.skipmissing && !flags.replicate) {
(void) fprintf(stderr,
gettext("skip-missing flag can only be used in "
"conjunction with replicate\n"));
usage(B_FALSE);
}

/*
* For everything except -R and -I, use the new, cleaner code path.
*/
Expand Down
3 changes: 3 additions & 0 deletions include/libzfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,9 @@ typedef struct sendflags {
/* recursive send (ie, -R) */
boolean_t replicate;

/* for recursive send, skip sending missing snapshots */
boolean_t skipmissing;

/* for incrementals, do all intermediate snapshots */
boolean_t doall;

Expand Down
36 changes: 23 additions & 13 deletions lib/libzfs/libzfs_sendrecv.c
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ typedef struct send_data {
boolean_t raw;
boolean_t doall;
boolean_t replicate;
boolean_t skipmissing;
boolean_t verbose;
boolean_t backup;
boolean_t seenfrom;
Expand Down Expand Up @@ -498,7 +499,8 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
* - skip sending the current dataset if it was created later than
* the parent tosnap
* - return error if the current dataset was created earlier than
* the parent tosnap
* the parent tosnap, unless --skip-missing specified. Then
* just print a warning
*/
if (sd->tosnap != NULL && tosnap_txg == 0) {
if (sd->tosnap_txg != 0 && txg > sd->tosnap_txg) {
Expand All @@ -507,6 +509,11 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
"skipping dataset %s: snapshot %s does "
"not exist\n"), zhp->zfs_name, sd->tosnap);
}
} else if (sd->skipmissing) {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"WARNING: skipping dataset %s and its children:"
" snapshot %s does not exist\n"),
zhp->zfs_name, sd->tosnap);
} else {
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
"cannot send %s@%s%s: snapshot %s@%s does not "
Expand Down Expand Up @@ -650,8 +657,9 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
static int
gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
const char *tosnap, boolean_t recursive, boolean_t raw, boolean_t doall,
boolean_t replicate, boolean_t verbose, boolean_t backup, boolean_t holds,
boolean_t props, nvlist_t **nvlp, avl_tree_t **avlp)
boolean_t replicate, boolean_t skipmissing, boolean_t verbose,
boolean_t backup, boolean_t holds, boolean_t props, nvlist_t **nvlp,
avl_tree_t **avlp)
{
zfs_handle_t *zhp;
send_data_t sd = { 0 };
Expand All @@ -669,6 +677,7 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
sd.raw = raw;
sd.doall = doall;
sd.replicate = replicate;
sd.skipmissing = skipmissing;
sd.verbose = verbose;
sd.backup = backup;
sd.holds = holds;
Expand Down Expand Up @@ -1976,8 +1985,8 @@ send_conclusion_record(int fd, zio_cksum_t *zc)
static int
send_prelim_records(zfs_handle_t *zhp, const char *from, int fd,
boolean_t gather_props, boolean_t recursive, boolean_t verbose,
boolean_t dryrun, boolean_t raw, boolean_t replicate, boolean_t backup,
boolean_t holds, boolean_t props, boolean_t doall,
boolean_t dryrun, boolean_t raw, boolean_t replicate, boolean_t skipmissing,
boolean_t backup, boolean_t holds, boolean_t props, boolean_t doall,
nvlist_t **fssp, avl_tree_t **fsavlp)
{
int err = 0;
Expand Down Expand Up @@ -2023,8 +2032,8 @@ send_prelim_records(zfs_handle_t *zhp, const char *from, int fd,
}

if ((err = gather_nvlist(zhp->zfs_hdl, tofs,
from, tosnap, recursive, raw, doall, replicate, verbose,
backup, holds, props, &fss, fsavlp)) != 0) {
from, tosnap, recursive, raw, doall, replicate, skipmissing,
verbose, backup, holds, props, &fss, fsavlp)) != 0) {
return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP,
errbuf));
}
Expand Down Expand Up @@ -2161,8 +2170,9 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
err = send_prelim_records(tosnap, fromsnap, outfd,
flags->replicate || flags->props || flags->holds,
flags->replicate, flags->verbosity > 0, flags->dryrun,
flags->raw, flags->replicate, flags->backup, flags->holds,
flags->props, flags->doall, &fss, &fsavl);
flags->raw, flags->replicate, flags->skipmissing,
flags->backup, flags->holds, flags->props, flags->doall,
&fss, &fsavl);
zfs_close(tosnap);
if (err != 0)
goto err_out;
Expand Down Expand Up @@ -2465,7 +2475,7 @@ zfs_send_one(zfs_handle_t *zhp, const char *from, int fd, sendflags_t *flags,
*/
err = send_prelim_records(zhp, NULL, fd, B_TRUE, B_FALSE,
flags->verbosity > 0, flags->dryrun, flags->raw,
flags->replicate, flags->backup, flags->holds,
flags->replicate, B_FALSE, flags->backup, flags->holds,
flags->props, flags->doall, NULL, NULL);
if (err != 0)
return (err);
Expand Down Expand Up @@ -3237,7 +3247,7 @@ recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs,
deleted = fnvlist_alloc();

if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL,
recursive, B_TRUE, B_FALSE, recursive, B_FALSE, B_FALSE,
recursive, B_TRUE, B_FALSE, recursive, B_FALSE, B_FALSE, B_FALSE,
B_FALSE, B_TRUE, &local_nv, &local_avl)) != 0)
return (error);

Expand Down Expand Up @@ -4730,8 +4740,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
*/
*cp = '\0';
if (gather_nvlist(hdl, destsnap, NULL, NULL, B_FALSE, B_TRUE,
B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_TRUE,
&local_nv, &local_avl) == 0) {
B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE,
B_TRUE, &local_nv, &local_avl) == 0) {
*cp = '@';
fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
fsavl_destroy(local_avl);
Expand Down
10 changes: 8 additions & 2 deletions man/man8/zfs-send.8
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@
.Sh SYNOPSIS
.Nm zfs
.Cm send
.Op Fl DLPRbcehnpvw
.Op Fl DLPRsbcehnpvw
.Op Oo Fl I Ns | Ns Fl i Oc Ar snapshot
.Ar snapshot
.Nm zfs
.Cm send
.Op Fl DLPRcenpvw
.Op Fl DLPRscenpvw
.Op Fl i Ar snapshot Ns | Ns Ar bookmark
.Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot
.Nm zfs
Expand Down Expand Up @@ -139,6 +139,12 @@ do not exist on the sending side are destroyed. If the
flag is used to send encrypted datasets, then
.Fl w
must also be specified.
.It Fl s, -skip-missing
Allows sending a replication stream even when there are snapshots missing in the
hierarchy. When a snapshot is missing, instead of throwing an error and aborting
the send, a warning is printed to STDERR and the dataset to which it belongs
and its descendents are skipped. This flag can only be used in conjunction with
.Fl R .
.It Fl e, -embed
Generate a more compact stream by using
.Sy WRITE_EMBEDDED
Expand Down
2 changes: 1 addition & 1 deletion tests/runfiles/common.run
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ tags = ['functional', 'cli_root', 'zfs_rollback']
tests = ['zfs_send_001_pos', 'zfs_send_002_pos', 'zfs_send_003_pos',
'zfs_send_004_neg', 'zfs_send_005_pos', 'zfs_send_006_pos',
'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw',
'zfs_send_sparse', 'zfs_send-b']
'zfs_send_sparse', 'zfs_send-b', 'zfs_send_skip_missing']
tags = ['functional', 'cli_root', 'zfs_send']

[tests/functional/cli_root/zfs_set]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ dist_pkgdata_SCRIPTS = \
zfs_send_encrypted_unloaded.ksh \
zfs_send_raw.ksh \
zfs_send_sparse.ksh \
zfs_send-b.ksh
zfs_send-b.ksh \
zfs_send_skip_missing.ksh

dist_pkgdata_DATA = \
zfs_send.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#

#
# Copyright (c) 2016, loli10K. All rights reserved.
# Copyright (c) 2021, Pablo Correa Gómez. All rights reserved.
#

. $STF_SUITE/tests/functional/cli_root/cli_common.kshlib
. $STF_SUITE/tests/functional/cli_root/zfs_send/zfs_send.cfg

#
# DESCRIPTION:
# Verify 'zfs send' will avoid sending replication send
# streams when we're missing snapshots in the dataset
# hierarchy, unless -s|--skip-missing provided
#
# STRATEGY:
# 1. Create a parent and child fs and then only snapshot the parent
# 2. Verify sending with replication will fail
# 3. Verify sending with skip-missing will print a warning but succeed
#

verify_runnable "both"

function cleanup
{
snapexists $SNAP && log_must zfs destroy -f $SNAP

datasetexists $PARENT && log_must zfs destroy -rf $PARENT

[[ -e $WARNF ]] && log_must rm -f $WARNF
rm -f $TEST_BASE_DIR/devnull
}

log_assert "Verify 'zfs send -Rs' works as expected."
log_onexit cleanup

PARENT=$TESTPOOL/parent
CHILD=$PARENT/child
SNAP=$PARENT@snap
WARNF=$TEST_BASE_DIR/warn.2

log_note "Verify 'zfs send -R' fails to generate replication stream"\
" for datasets created before"

log_must zfs create $PARENT
log_must zfs create $CHILD
log_must zfs snapshot $SNAP
log_mustnot eval "zfs send -R $SNAP >$TEST_BASE_DIR/devnull"

log_note "Verify 'zfs send -Rs' warns about missing snapshots, "\
"but still succeeds"

log_must eval "zfs send -Rs $SNAP 2> $WARNF >$TEST_BASE_DIR/devnull"
log_must eval "[[ -s $WARNF ]]"

log_pass "Verify 'zfs send -Rs' works as expected."