Skip to content

Commit 099fa7e

Browse files
Allow zfs to send replication streams with missing snapshots
A tentative implementation and discussion was done in openzfs#5285. According to it a send --skip-missing|-s flag has been added. In a replication stream, when there are snapshots missing in the hierarchy, if -s is provided print a warning and ignore dataset (and its children) instead of throwing an error Reviewed-by: Paul Dagnelie <[email protected]> Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Pablo Correa Gómez <[email protected]> Closes openzfs#11710
1 parent 2ec0b0d commit 099fa7e

File tree

7 files changed

+126
-18
lines changed

7 files changed

+126
-18
lines changed

cmd/zfs/zfs_main.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4376,6 +4376,7 @@ zfs_do_send(int argc, char **argv)
43764376

43774377
struct option long_options[] = {
43784378
{"replicate", no_argument, NULL, 'R'},
4379+
{"skip-missing", no_argument, NULL, 's'},
43794380
{"redact", required_argument, NULL, 'd'},
43804381
{"props", no_argument, NULL, 'p'},
43814382
{"parsable", no_argument, NULL, 'P'},
@@ -4394,7 +4395,7 @@ zfs_do_send(int argc, char **argv)
43944395
};
43954396

43964397
/* check options */
4397-
while ((c = getopt_long(argc, argv, ":i:I:RDpvnPLeht:cwbd:S",
4398+
while ((c = getopt_long(argc, argv, ":i:I:RsDpvnPLeht:cwbd:S",
43984399
long_options, NULL)) != -1) {
43994400
switch (c) {
44004401
case 'i':
@@ -4411,6 +4412,9 @@ zfs_do_send(int argc, char **argv)
44114412
case 'R':
44124413
flags.replicate = B_TRUE;
44134414
break;
4415+
case 's':
4416+
flags.skipmissing = B_TRUE;
4417+
break;
44144418
case 'd':
44154419
redactbook = optarg;
44164420
break;
@@ -4575,6 +4579,13 @@ zfs_do_send(int argc, char **argv)
45754579
resume_token));
45764580
}
45774581

4582+
if (flags.skipmissing && !flags.replicate) {
4583+
(void) fprintf(stderr,
4584+
gettext("skip-missing flag can only be used in "
4585+
"conjunction with replicate\n"));
4586+
usage(B_FALSE);
4587+
}
4588+
45784589
/*
45794590
* For everything except -R and -I, use the new, cleaner code path.
45804591
*/

include/libzfs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,9 @@ typedef struct sendflags {
666666
/* recursive send (ie, -R) */
667667
boolean_t replicate;
668668

669+
/* for recursive send, skip sending missing snapshots */
670+
boolean_t skipmissing;
671+
669672
/* for incrementals, do all intermediate snapshots */
670673
boolean_t doall;
671674

lib/libzfs/libzfs_sendrecv.c

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ typedef struct send_data {
247247
boolean_t raw;
248248
boolean_t doall;
249249
boolean_t replicate;
250+
boolean_t skipmissing;
250251
boolean_t verbose;
251252
boolean_t backup;
252253
boolean_t seenfrom;
@@ -497,7 +498,8 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
497498
* - skip sending the current dataset if it was created later than
498499
* the parent tosnap
499500
* - return error if the current dataset was created earlier than
500-
* the parent tosnap
501+
* the parent tosnap, unless --skip-missing specified. Then
502+
* just print a warning
501503
*/
502504
if (sd->tosnap != NULL && tosnap_txg == 0) {
503505
if (sd->tosnap_txg != 0 && txg > sd->tosnap_txg) {
@@ -506,6 +508,11 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
506508
"skipping dataset %s: snapshot %s does "
507509
"not exist\n"), zhp->zfs_name, sd->tosnap);
508510
}
511+
} else if (sd->skipmissing) {
512+
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
513+
"WARNING: skipping dataset %s and its children:"
514+
" snapshot %s does not exist\n"),
515+
zhp->zfs_name, sd->tosnap);
509516
} else {
510517
(void) fprintf(stderr, dgettext(TEXT_DOMAIN,
511518
"cannot send %s@%s%s: snapshot %s@%s does not "
@@ -649,8 +656,9 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg)
649656
static int
650657
gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
651658
const char *tosnap, boolean_t recursive, boolean_t raw, boolean_t doall,
652-
boolean_t replicate, boolean_t verbose, boolean_t backup, boolean_t holds,
653-
boolean_t props, nvlist_t **nvlp, avl_tree_t **avlp)
659+
boolean_t replicate, boolean_t skipmissing, boolean_t verbose,
660+
boolean_t backup, boolean_t holds, boolean_t props, nvlist_t **nvlp,
661+
avl_tree_t **avlp)
654662
{
655663
zfs_handle_t *zhp;
656664
send_data_t sd = { 0 };
@@ -668,6 +676,7 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap,
668676
sd.raw = raw;
669677
sd.doall = doall;
670678
sd.replicate = replicate;
679+
sd.skipmissing = skipmissing;
671680
sd.verbose = verbose;
672681
sd.backup = backup;
673682
sd.holds = holds;
@@ -1975,8 +1984,8 @@ send_conclusion_record(int fd, zio_cksum_t *zc)
19751984
static int
19761985
send_prelim_records(zfs_handle_t *zhp, const char *from, int fd,
19771986
boolean_t gather_props, boolean_t recursive, boolean_t verbose,
1978-
boolean_t dryrun, boolean_t raw, boolean_t replicate, boolean_t backup,
1979-
boolean_t holds, boolean_t props, boolean_t doall,
1987+
boolean_t dryrun, boolean_t raw, boolean_t replicate, boolean_t skipmissing,
1988+
boolean_t backup, boolean_t holds, boolean_t props, boolean_t doall,
19801989
nvlist_t **fssp, avl_tree_t **fsavlp)
19811990
{
19821991
int err = 0;
@@ -2022,8 +2031,8 @@ send_prelim_records(zfs_handle_t *zhp, const char *from, int fd,
20222031
}
20232032

20242033
if ((err = gather_nvlist(zhp->zfs_hdl, tofs,
2025-
from, tosnap, recursive, raw, doall, replicate, verbose,
2026-
backup, holds, props, &fss, fsavlp)) != 0) {
2034+
from, tosnap, recursive, raw, doall, replicate, skipmissing,
2035+
verbose, backup, holds, props, &fss, fsavlp)) != 0) {
20272036
return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP,
20282037
errbuf));
20292038
}
@@ -2160,8 +2169,9 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
21602169
err = send_prelim_records(tosnap, fromsnap, outfd,
21612170
flags->replicate || flags->props || flags->holds,
21622171
flags->replicate, flags->verbosity > 0, flags->dryrun,
2163-
flags->raw, flags->replicate, flags->backup, flags->holds,
2164-
flags->props, flags->doall, &fss, &fsavl);
2172+
flags->raw, flags->replicate, flags->skipmissing,
2173+
flags->backup, flags->holds, flags->props, flags->doall,
2174+
&fss, &fsavl);
21652175
zfs_close(tosnap);
21662176
if (err != 0)
21672177
goto err_out;
@@ -2464,7 +2474,7 @@ zfs_send_one(zfs_handle_t *zhp, const char *from, int fd, sendflags_t *flags,
24642474
*/
24652475
err = send_prelim_records(zhp, NULL, fd, B_TRUE, B_FALSE,
24662476
flags->verbosity > 0, flags->dryrun, flags->raw,
2467-
flags->replicate, flags->backup, flags->holds,
2477+
flags->replicate, B_FALSE, flags->backup, flags->holds,
24682478
flags->props, flags->doall, NULL, NULL);
24692479
if (err != 0)
24702480
return (err);
@@ -3236,7 +3246,7 @@ recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs,
32363246
deleted = fnvlist_alloc();
32373247

32383248
if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL,
3239-
recursive, B_TRUE, B_FALSE, recursive, B_FALSE, B_FALSE,
3249+
recursive, B_TRUE, B_FALSE, recursive, B_FALSE, B_FALSE, B_FALSE,
32403250
B_FALSE, B_TRUE, &local_nv, &local_avl)) != 0)
32413251
return (error);
32423252

@@ -4729,8 +4739,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
47294739
*/
47304740
*cp = '\0';
47314741
if (gather_nvlist(hdl, destsnap, NULL, NULL, B_FALSE, B_TRUE,
4732-
B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_TRUE,
4733-
&local_nv, &local_avl) == 0) {
4742+
B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE, B_FALSE,
4743+
B_TRUE, &local_nv, &local_avl) == 0) {
47344744
*cp = '@';
47354745
fs = fsavl_find(local_avl, drrb->drr_toguid, NULL);
47364746
fsavl_destroy(local_avl);

man/man8/zfs-send.8

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@
3939
.Sh SYNOPSIS
4040
.Nm zfs
4141
.Cm send
42-
.Op Fl DLPRbcehnpvw
42+
.Op Fl DLPRsbcehnpvw
4343
.Op Oo Fl I Ns | Ns Fl i Oc Ar snapshot
4444
.Ar snapshot
4545
.Nm zfs
4646
.Cm send
47-
.Op Fl DLPRcenpvw
47+
.Op Fl DLPRscenpvw
4848
.Op Fl i Ar snapshot Ns | Ns Ar bookmark
4949
.Ar filesystem Ns | Ns Ar volume Ns | Ns Ar snapshot
5050
.Nm zfs
@@ -139,6 +139,12 @@ do not exist on the sending side are destroyed. If the
139139
flag is used to send encrypted datasets, then
140140
.Fl w
141141
must also be specified.
142+
.It Fl s, -skip-missing
143+
Allows sending a replication stream even when there are snapshots missing in the
144+
hierarchy. When a snapshot is missing, instead of throwing an error and aborting
145+
the send, a warning is printed to STDERR and the dataset to which it belongs
146+
and its descendents are skipped. This flag can only be used in conjunction with
147+
.Fl R .
142148
.It Fl e, -embed
143149
Generate a more compact stream by using
144150
.Sy WRITE_EMBEDDED

tests/runfiles/common.run

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ tags = ['functional', 'cli_root', 'zfs_rollback']
258258
tests = ['zfs_send_001_pos', 'zfs_send_002_pos', 'zfs_send_003_pos',
259259
'zfs_send_004_neg', 'zfs_send_005_pos', 'zfs_send_006_pos',
260260
'zfs_send_007_pos', 'zfs_send_encrypted', 'zfs_send_raw',
261-
'zfs_send_sparse', 'zfs_send-b']
261+
'zfs_send_sparse', 'zfs_send-b', 'zfs_send_skip_missing']
262262
tags = ['functional', 'cli_root', 'zfs_send']
263263

264264
[tests/functional/cli_root/zfs_set]

tests/zfs-tests/tests/functional/cli_root/zfs_send/Makefile.am

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ dist_pkgdata_SCRIPTS = \
1313
zfs_send_encrypted_unloaded.ksh \
1414
zfs_send_raw.ksh \
1515
zfs_send_sparse.ksh \
16-
zfs_send-b.ksh
16+
zfs_send-b.ksh \
17+
zfs_send_skip_missing.ksh
1718

1819
dist_pkgdata_DATA = \
1920
zfs_send.cfg
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/bin/ksh -p
2+
#
3+
# CDDL HEADER START
4+
#
5+
# The contents of this file are subject to the terms of the
6+
# Common Development and Distribution License (the "License").
7+
# You may not use this file except in compliance with the License.
8+
#
9+
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10+
# or http://www.opensolaris.org/os/licensing.
11+
# See the License for the specific language governing permissions
12+
# and limitations under the License.
13+
#
14+
# When distributing Covered Code, include this CDDL HEADER in each
15+
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16+
# If applicable, add the following below this CDDL HEADER, with the
17+
# fields enclosed by brackets "[]" replaced with your own identifying
18+
# information: Portions Copyright [yyyy] [name of copyright owner]
19+
#
20+
# CDDL HEADER END
21+
#
22+
23+
#
24+
# Copyright (c) 2016, loli10K. All rights reserved.
25+
# Copyright (c) 2021, Pablo Correa Gómez. All rights reserved.
26+
#
27+
28+
. $STF_SUITE/tests/functional/cli_root/cli_common.kshlib
29+
. $STF_SUITE/tests/functional/cli_root/zfs_send/zfs_send.cfg
30+
31+
#
32+
# DESCRIPTION:
33+
# Verify 'zfs send' will avoid sending replication send
34+
# streams when we're missing snapshots in the dataset
35+
# hierarchy, unless -s|--skip-missing provided
36+
#
37+
# STRATEGY:
38+
# 1. Create a parent and child fs and then only snapshot the parent
39+
# 2. Verify sending with replication will fail
40+
# 3. Verify sending with skip-missing will print a warning but succeed
41+
#
42+
43+
verify_runnable "both"
44+
45+
function cleanup
46+
{
47+
snapexists $SNAP && log_must zfs destroy -f $SNAP
48+
49+
datasetexists $PARENT && log_must zfs destroy -rf $PARENT
50+
51+
[[ -e $WARNF ]] && log_must rm -f $WARNF
52+
rm -f $TEST_BASE_DIR/devnull
53+
}
54+
55+
log_assert "Verify 'zfs send -Rs' works as expected."
56+
log_onexit cleanup
57+
58+
PARENT=$TESTPOOL/parent
59+
CHILD=$PARENT/child
60+
SNAP=$PARENT@snap
61+
WARNF=$TEST_BASE_DIR/warn.2
62+
63+
log_note "Verify 'zfs send -R' fails to generate replication stream"\
64+
" for datasets created before"
65+
66+
log_must zfs create $PARENT
67+
log_must zfs create $CHILD
68+
log_must zfs snapshot $SNAP
69+
log_mustnot eval "zfs send -R $SNAP >$TEST_BASE_DIR/devnull"
70+
71+
log_note "Verify 'zfs send -Rs' warns about missing snapshots, "\
72+
"but still succeeds"
73+
74+
log_must eval "zfs send -Rs $SNAP 2> $WARNF >$TEST_BASE_DIR/devnull"
75+
log_must eval "[[ -s $WARNF ]]"
76+
77+
log_pass "Verify 'zfs send -Rs' works as expected."

0 commit comments

Comments
 (0)