From 28eb473c517551ae990ad16a698bedb366b8e189 Mon Sep 17 00:00:00 2001 From: ettavolt Date: Sat, 17 Jun 2023 20:05:30 -0400 Subject: [PATCH] btrfs-progs: receive: cannot find clone source subvol when receiving in reverse direction process_clone() only searches the received_uuid, but could exist in an earlier uuid that isn't the received_uuid. Mirror what process_snapshot does and search both the received_uuid and if that fails look up by normal uuid. Fixes: https://github.com/kdave/btrfs-progs/issues/606 Issue: #606 Pull-request: #643 Pull-request: #862 Signed-off-by: Arsenii Skvortsov Signed-off-by: David Sterba --- cmds/receive.c | 34 ++++--- tests/misc-tests/064-reverse-receive/test.sh | 99 ++++++++++++++++++++ 2 files changed, 122 insertions(+), 11 deletions(-) create mode 100755 tests/misc-tests/064-reverse-receive/test.sh diff --git a/cmds/receive.c b/cmds/receive.c index 412bc8af..4cc5b900 100644 --- a/cmds/receive.c +++ b/cmds/receive.c @@ -221,6 +221,25 @@ out: return ret; } +/* + * Search for the @subvol_uuid, try received_uuid and subvolume as a fallback + * if it's not found. + */ +static struct subvol_info *search_source_subvol(int mnt_fd, const u8 *subvol_uuid, + u64 transid) +{ + struct subvol_info *found; + + found = subvol_uuid_search(mnt_fd, 0, subvol_uuid, transid, NULL, + subvol_search_by_received_uuid); + if (IS_ERR_OR_NULL(found)) { + found = subvol_uuid_search(mnt_fd, 0, subvol_uuid, transid, + NULL, subvol_search_by_uuid); + } + + return found; +} + static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid, const u8 *parent_uuid, u64 parent_ctransid, void *user) @@ -283,14 +302,8 @@ static int process_snapshot(const char *path, const u8 *uuid, u64 ctransid, memset(&args_v2, 0, sizeof(args_v2)); strncpy_null(args_v2.name, path, sizeof(args_v2.name)); - parent_subvol = subvol_uuid_search(rctx->mnt_fd, 0, parent_uuid, - parent_ctransid, NULL, - subvol_search_by_received_uuid); - if (IS_ERR_OR_NULL(parent_subvol)) { - parent_subvol = subvol_uuid_search(rctx->mnt_fd, 0, parent_uuid, - parent_ctransid, NULL, - subvol_search_by_uuid); - } + parent_subvol = search_source_subvol(rctx->mnt_fd, parent_uuid, + parent_ctransid); if (IS_ERR_OR_NULL(parent_subvol)) { if (!parent_subvol) ret = -ENOENT; @@ -745,9 +758,8 @@ static int process_clone(const char *path, u64 offset, u64 len, BTRFS_UUID_SIZE) == 0) { subvol_path = rctx->cur_subvol_path; } else { - si = subvol_uuid_search(rctx->mnt_fd, 0, clone_uuid, clone_ctransid, - NULL, - subvol_search_by_received_uuid); + si = search_source_subvol(rctx->mnt_fd, clone_uuid, + clone_ctransid); if (IS_ERR_OR_NULL(si)) { char uuid_str[BTRFS_UUID_UNPARSED_SIZE]; diff --git a/tests/misc-tests/064-reverse-receive/test.sh b/tests/misc-tests/064-reverse-receive/test.sh new file mode 100755 index 00000000..a545cda8 --- /dev/null +++ b/tests/misc-tests/064-reverse-receive/test.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# +# Receive in reverse direction must not throw an error if it can find an +# earlier "sent" parent. In general, shows a backup+sync setup between two (or +# more) PCs with an external drive. + +source "$TEST_TOP/common" + +check_prereq mkfs.btrfs +check_prereq btrfs +check_global_prereq dd + +declare -a roots +i_pc1=1 +# An external drive used to backup and carry profile. +i_ext=2 +i_pc2=3 +roots[$i_pc1]="$TEST_MNT/pc1" +roots[$i_ext]="$TEST_MNT/external" +roots[$i_pc2]="$TEST_MNT/pc2" + +setup_root_helper +mkdir -p "${roots[@]}" +setup_loopdevs 3 +prepare_loopdevs +for i in `seq 3`; do + TEST_DEV="${loopdevs[$i]}" + TEST_MNT="${roots[$i]}" + run_check_mkfs_test_dev + run_check_mount_test_dev + run_check $SUDO_HELPER mkdir -p "$TEST_MNT/.snapshots" +done + +run_check_update_file() +{ + run_check $SUDO_HELPER cp --reflink "${roots[$1]}/profile/$2" "${roots[$1]}/profile/staging" + run_check $SUDO_HELPER dd if=/dev/urandom conv=notrunc bs=4K count=4 seek=$3 "of=${roots[$1]}/profile/staging" + run_check $SUDO_HELPER mv "${roots[$1]}/profile/staging" "${roots[$1]}/profile/$2" +} +run_check_copy_snapshot_with_diff() +{ + _mktemp_local send.data + run_check $SUDO_HELPER "$TOP/btrfs" send -f send.data -p "${roots[$1]}/.snapshots/$2" "${roots[$1]}/.snapshots/$3" + run_check $SUDO_HELPER "$TOP/btrfs" receive -f send.data "${roots[$4]}/.snapshots" +} +run_check_backup_profile() +{ + run_check $SUDO_HELPER "$TOP/btrfs" subvolume snapshot -r "${roots[$1]}/profile" "${roots[$1]}/.snapshots/$3" + run_check_copy_snapshot_with_diff "$1" "$2" "$3" "$i_ext" + # Don't keep old snapshot in pc + run_check $SUDO_HELPER "$TOP/btrfs" subvolume delete "${roots[$1]}/.snapshots/$2" +} +run_check_restore_profile() +{ + run_check $SUDO_HELPER "$TOP/btrfs" subvolume snapshot "${roots[$1]}/.snapshots/$2" "${roots[$1]}/profile" +} +run_check_copy_fresh_backup_and_replace_profile() +{ + run_check_copy_snapshot_with_diff "$i_ext" "$2" "$3" "$1" + # IRL, it would be a nice idea to make a backup snapshot before deleting. + run_check $SUDO_HELPER "$TOP/btrfs" subvolume delete "${roots[$1]}/profile" + run_check_restore_profile "$1" "$3" + # Don't keep old snapshot in pc + run_check $SUDO_HELPER "$TOP/btrfs" subvolume delete "${roots[$1]}/.snapshots/$2" +} + + +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create "${roots[$i_pc1]}/profile" +run_check $SUDO_HELPER dd if=/dev/urandom bs=4K count=16 "of=${roots[$i_pc1]}/profile/day1" +run_check $SUDO_HELPER "$TOP/btrfs" subvolume snapshot -r "${roots[$i_pc1]}/profile" "${roots[$i_pc1]}/.snapshots/day1" +_mktemp_local send.data +run_check $SUDO_HELPER "$TOP/btrfs" send -f send.data "${roots[$i_pc1]}/.snapshots/day1" +run_check $SUDO_HELPER "$TOP/btrfs" receive -f send.data "${roots[$i_ext]}/.snapshots" + +run_check_update_file "$i_pc1" day1 2 +run_check_backup_profile "$i_pc1" day1 day2 + +_mktemp_local send.data +run_check $SUDO_HELPER "$TOP/btrfs" send -f send.data "${roots[$i_ext]}/.snapshots/day2" +run_check $SUDO_HELPER "$TOP/btrfs" receive -f send.data "${roots[$i_pc2]}/.snapshots" +run_check_restore_profile "$i_pc2" day2 +run_check_update_file "$i_pc2" day1 3 +run_check_backup_profile "$i_pc2" day2 day3 + +run_check_update_file "$i_pc2" day1 4 +run_check_backup_profile "$i_pc2" day3 day4 + +run_check_copy_fresh_backup_and_replace_profile "$i_pc1" day2 day4 +run_check_update_file "$i_pc1" day1 5 +run_check_backup_profile "$i_pc1" day4 day5 + +run_check_copy_fresh_backup_and_replace_profile "$i_pc2" day4 day5 +run_check_update_file "$i_pc2" day1 6 +run_check_backup_profile "$i_pc2" day5 day6 + +run_check_umount_test_dev "${loopdevs[@]}" +rmdir "${roots[@]}" +rm -f send.data +cleanup_loopdevs