diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c index 49d805136..18130a34f 100644 --- a/lib/libzfs/libzfs_sendrecv.c +++ b/lib/libzfs/libzfs_sendrecv.c @@ -260,6 +260,8 @@ typedef struct send_data { boolean_t props; boolean_t no_preserve_encryption; + snapfilter_cb_t *filter_cb; + void *filter_cb_arg; /* * The header nvlist is of the following format: * { @@ -512,6 +514,10 @@ send_iterate_fs(zfs_handle_t *zhp, void *arg) uint64_t fromsnap_txg_save = sd->fromsnap_txg; uint64_t tosnap_txg_save = sd->tosnap_txg; + if (sd->filter_cb && + (sd->filter_cb(zhp, sd->filter_cb_arg) == 0)) + return (0); + fromsnap_txg = get_snap_txg(zhp->zfs_hdl, zhp->zfs_name, sd->fromsnap); if (fromsnap_txg != 0) sd->fromsnap_txg = fromsnap_txg; @@ -697,7 +703,8 @@ 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 skipmissing, boolean_t verbose, boolean_t backup, boolean_t holds, boolean_t props, - boolean_t no_preserve_encryption, nvlist_t **nvlp, avl_tree_t **avlp) + boolean_t no_preserve_encryption, nvlist_t **nvlp, avl_tree_t **avlp, + snapfilter_cb_t *filter_cb, void *filter_cb_arg) { zfs_handle_t *zhp; send_data_t sd = { 0 }; @@ -721,6 +728,8 @@ gather_nvlist(libzfs_handle_t *hdl, const char *fsname, const char *fromsnap, sd.holds = holds; sd.props = props; sd.no_preserve_encryption = no_preserve_encryption; + sd.filter_cb = filter_cb; + sd.filter_cb_arg = filter_cb_arg; if ((error = send_iterate_fs(zhp, &sd)) != 0) { fnvlist_free(sd.fss); @@ -2200,7 +2209,8 @@ 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 skipmissing, boolean_t backup, boolean_t holds, boolean_t props, boolean_t doall, - boolean_t no_preserve_encryption, nvlist_t **fssp, avl_tree_t **fsavlp) + boolean_t no_preserve_encryption, nvlist_t **fssp, avl_tree_t **fsavlp, + snapfilter_cb_t filter_func, void *cb_arg) { int err = 0; char *packbuf = NULL; @@ -2247,7 +2257,7 @@ send_prelim_records(zfs_handle_t *zhp, const char *from, int fd, if (gather_nvlist(zhp->zfs_hdl, tofs, from, tosnap, recursive, raw, doall, replicate, skipmissing, verbose, backup, holds, props, no_preserve_encryption, - &fss, fsavlp) != 0) { + &fss, fsavlp, filter_func, cb_arg) != 0) { return (zfs_error(zhp->zfs_hdl, EZFS_BADBACKUP, errbuf)); } @@ -2394,7 +2404,8 @@ zfs_send_cb_impl(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap, flags->replicate, flags->verbosity > 0, flags->dryrun, flags->raw, flags->replicate, flags->skipmissing, flags->backup, flags->holds, flags->props, flags->doall, - flags->no_preserve_encryption, &fss, &fsavl); + flags->no_preserve_encryption, &fss, &fsavl, + filter_func, cb_arg); zfs_close(tosnap); if (err != 0) goto err_out; @@ -2738,7 +2749,7 @@ zfs_send_one_cb_impl(zfs_handle_t *zhp, const char *from, int fd, flags->verbosity > 0, flags->dryrun, flags->raw, flags->replicate, B_FALSE, flags->backup, flags->holds, flags->props, flags->doall, flags->no_preserve_encryption, - NULL, NULL); + NULL, NULL, NULL, NULL); if (err != 0) return (err); } @@ -3395,7 +3406,8 @@ recv_fix_encryption_hierarchy(libzfs_handle_t *hdl, const char *top_zfs, /* Using top_zfs, gather the nvlists for all local filesystems. */ if ((err = gather_nvlist(hdl, top_zfs, NULL, NULL, recursive, B_TRUE, B_FALSE, recursive, B_FALSE, B_FALSE, B_FALSE, - B_FALSE, B_TRUE, B_FALSE, &local_nv, &local_avl)) != 0) + B_FALSE, B_TRUE, B_FALSE, &local_nv, &local_avl, + NULL, NULL)) != 0) return (err); /* @@ -3550,7 +3562,8 @@ again: if ((error = gather_nvlist(hdl, tofs, fromsnap, NULL, recursive, B_TRUE, B_FALSE, recursive, B_FALSE, B_FALSE, B_FALSE, - B_FALSE, B_TRUE, B_FALSE, &local_nv, &local_avl)) != 0) + B_FALSE, B_TRUE, B_FALSE, &local_nv, &local_avl, + NULL, NULL)) != 0) return (error); /* @@ -5141,7 +5154,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_FALSE, - B_TRUE, B_FALSE, &local_nv, &local_avl) == 0) { + B_TRUE, B_FALSE, &local_nv, &local_avl, + NULL, NULL) == 0) { *cp = '@'; fs = fsavl_find(local_avl, drrb->drr_toguid, NULL); fsavl_destroy(local_avl); diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index e5ded9343..f22f3c759 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -987,7 +987,8 @@ tests = ['recv_dedup', 'recv_dedup_encrypted_zvol', 'rsend_001_pos', 'rsend_014_pos', 'rsend_016_neg', 'rsend_019_pos', 'rsend_020_pos', 'rsend_021_pos', 'rsend_022_pos', 'rsend_024_pos', 'rsend_025_pos', 'rsend_026_neg', 'rsend_027_pos', 'rsend_028_neg', 'rsend_029_neg', - 'rsend_030_pos', 'rsend_031_pos', 'send-c_verify_ratio', + 'rsend_030_pos', 'rsend_031_pos', 'rsend-exclude_001_pos', + 'rsend-exclude_002_pos', 'send-c_verify_ratio', 'send-c_verify_contents', 'send-c_props', 'send-c_incremental', 'send-c_volume', 'send-c_zstream_recompress', 'send-c_zstreamdump', 'send-c_lz4_disabled', 'send-c_recv_lz4_disabled', diff --git a/tests/runfiles/sanity.run b/tests/runfiles/sanity.run index ad2533793..9b49f63c5 100644 --- a/tests/runfiles/sanity.run +++ b/tests/runfiles/sanity.run @@ -557,7 +557,8 @@ tags = ['functional', 'reservation'] tests = ['recv_dedup', 'recv_dedup_encrypted_zvol', 'rsend_001_pos', 'rsend_002_pos', 'rsend_003_pos', 'rsend_004_pos', 'rsend_005_pos', 'rsend_006_pos', 'rsend_009_pos', 'rsend_010_pos', 'rsend_011_pos', - 'rsend_014_pos', 'rsend_016_neg', 'send-c_verify_contents', + 'rsend_014_pos', 'rsend_016_neg', 'rsend-exclude_001_pos', + 'rsend-exclude_002_pos', 'send-c_verify_contents', 'send-c_volume', 'send-c_zstreamdump', 'send-c_recv_dedup', 'send-L_toggle', 'send_encrypted_hierarchy', 'send_encrypted_props', 'send_encrypted_freeobjects', diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index 9d60ce3f2..fdf211877 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -2051,6 +2051,8 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/rsend/rsend_029_neg.ksh \ functional/rsend/rsend_030_pos.ksh \ functional/rsend/rsend_031_pos.ksh \ + functional/rsend/rsend-exclude_001_pos.ksh \ + functional/rsend/rsend-exclude_002_pos.ksh \ functional/rsend/send-c_embedded_blocks.ksh \ functional/rsend/send-c_incremental.ksh \ functional/rsend/send-c_longname.ksh \ diff --git a/tests/zfs-tests/tests/functional/rsend/rsend-exclude_001_pos.ksh b/tests/zfs-tests/tests/functional/rsend/rsend-exclude_001_pos.ksh new file mode 100755 index 000000000..0b16a1890 --- /dev/null +++ b/tests/zfs-tests/tests/functional/rsend/rsend-exclude_001_pos.ksh @@ -0,0 +1,68 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 + +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2023 by Delphix. All rights reserved. +# Copyright (c) 2026 by Sean Eric Fagan. All rights reserved. +# + +. $STF_SUITE/tests/functional/rsend/rsend.kshlib + +# +# Description: +# Verify recursive incremental with -X properly excludes +# specified datasets. +# +# Strategy: +# 1. Create multiple datasets on source pool. +# 2. Create snapshots on source pool. +# 3. Recursively send snapshots excluding datasets +# 4. Confirm destination pool does not include excluded datasets. +# + +verify_runnable "both" + +sendfs=$POOL/sendfs +recvfs=$POOL2/recvfs + +function cleanup { + rm -f $BACKDIR/list + rm -f $BACKDIR/stream1 + zfs destroy -rf $sendfs + zfs destroy -rf $recvfs +} + +log_assert "Verify recursive sends excluding datasets behave properly." +log_onexit cleanup + +log_must zfs create $sendfs +log_must zfs create $sendfs/ds1 +log_must zfs create $sendfs/ds1/sub1 +log_must zfs create $sendfs/ds1/sub1/sub2 +log_must zfs create $sendfs/ds2 +log_must zfs create $sendfs/ds2/sub1 +log_must zfs create $sendfs/ds2/sub1/sub3 +log_must zfs create $recvfs + +log_must zfs snapshot -r $sendfs@A + +# Now we'll send $sendfs@A, but exclude ds1/sub1 +log_must zfs send -R --exclude ds1/sub1 $sendfs@A > $BACKDIR/stream1 +log_must zfs recv -dFu $recvfs < $BACKDIR/stream1 +log_must zfs list -r $recvfs > $BACKDIR/list +lost_mustnot grep -q ds1/sub1/sub2 $BACKDIR/list +lost_mustnot grep -q ds1/sub1 $BACKDIR/list +log_must grep -q ds2/sub1 $BACKDIR/list + +log_pass "Verify recursive incremental excluding datasets behave properly." diff --git a/tests/zfs-tests/tests/functional/rsend/rsend-exclude_002_pos.ksh b/tests/zfs-tests/tests/functional/rsend/rsend-exclude_002_pos.ksh new file mode 100755 index 000000000..b16d427a4 --- /dev/null +++ b/tests/zfs-tests/tests/functional/rsend/rsend-exclude_002_pos.ksh @@ -0,0 +1,73 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 + +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright (c) 2023 by Delphix. All rights reserved. +# Copyright (c) 2026 by Sean Eric Fagan. All rights reserved. +# + +. $STF_SUITE/tests/functional/rsend/rsend.kshlib + +# +# Description: +# Verify recursive incremental with -X properly excludes +# encrypted specified datasets. +# +# Strategy: +# 1. Create multiple datasets on source pool. +# 2. Create snapshots on source pool. +# 3. Recursively send snapshots excluding datasets +# 4. Confirm destination pool does not include excluded datasets. +# + +verify_runnable "both" + +sendfs=$POOL/sendfs +recvfs=$POOL2/recvfs +PASSPHRASE=${PASSPHRASE:-password} + +function cleanup { + rm -f $BACKDIR/list + rm -f $BACKDIR/stream1 + zfs destroy -rf $sendfs + zfs destroy -rf $recvfs +} + +log_assert "Verify recursive sends excluding datasets behave properly." +log_onexit cleanup + +log_must zfs create $sendfs +log_must zfs create $sendfs/ds1 +log_must zfs create $sendfs/ds1/sub1 +log_must zfs create $sendfs/ds1/sub1/sub2 +log_must zfs create $sendfs/ds2 +log_must zfs create $sendfs/ds2/sub1 +log_must zfs create $sendfs/ds2/sub1/sub3 +log_must eval "echo $PASSPHRASE | zfs create -o encryption=on" \ + "-o keyformat=passphrase $sendfs/enc" +log_must zfs create $sendfs/enc/enc2 + +log_must zfs create $recvfs + +log_must zfs snapshot -r $sendfs@A + +# Now we'll send $sendfs@A, but exclude enc +log_must zfs send -R --exclude $sendfs/enc $sendfs@A > $BACKDIR/stream1 +log_must zfs recv -dFu $recvfs < $BACKDIR/stream1 +log_must zfs list -r $recvfs > $BACKDIR/list +lost_mustnot grep -q enc/enc2 $BACKDIR/list +lost_mustnot grep -q enc $BACKDIR/list +log_must grep -q ds2/sub1 $BACKDIR/list + +log_pass "Verify recursive incremental excluding encrypted datasets behave properly."