btrbk: data structure refactoring:

- remove %subvol_list_cache: may slow down aa bit, but makes possible
  to inject nodes correctly

- simplify subtree list (is now an array as it should have been from
  the beginning); correctly fill tree_cache

- fix vinfo_set_detail; cleanup
pull/73/head
Axel Burri 2016-03-10 05:26:43 +01:00
parent 1d054bf04a
commit e85b6dadf1
1 changed files with 104 additions and 89 deletions

183
btrbk
View File

@ -168,10 +168,14 @@ my %table_formats = (
); );
my %btrfs_tree_cache; # map URL to btr_tree node my %btrfs_tree_cache; # map URL to btr_tree node
my %subvol_list_cache; # map URL to subvolume list ( rel_path => vinfo, ... )
my %uuid_info; # map UUID to btr_tree node my %uuid_info; # map UUID to btr_tree node
my %uuid_url_map; # map UUID to hash ( URL => btr_tree, ... ) my %uuid_url_map; # map UUID to hash ( URL => btr_tree, ... )
# enabling this may speed up a bit, but makes it impossible to inject nodes.
# keep disabled for now!
our $USE_SUBVOL_CACHE__DANGEROUS = undef;
my %subvol_list_cache; # map URL to subvolume list ( rel_path => vinfo, ... )
my $dryrun; my $dryrun;
my $loglevel = 1; my $loglevel = 1;
my $show_progress = 0; my $show_progress = 0;
@ -250,6 +254,10 @@ sub INFO { my $t = shift; print STDERR "$t\n" if($loglevel >= 2); }
sub WARN { my $t = shift; print STDERR "WARNING: $t\n" if($loglevel >= 1); } sub WARN { my $t = shift; print STDERR "WARNING: $t\n" if($loglevel >= 1); }
sub ERROR { my $t = shift; print STDERR "ERROR: $t\n"; } sub ERROR { my $t = shift; print STDERR "ERROR: $t\n"; }
sub VINFO { my $vinfo = shift; my $t = shift || "vinfo";
print STDERR Data::Dumper->new([$vinfo], [$t])->Maxdepth(2)->Dump();
}
sub ABORTED($;$) sub ABORTED($;$)
{ {
my $config = shift; my $config = shift;
@ -468,12 +476,6 @@ sub vinfo($;$)
} }
sub vinfo_dump($;$)
{
return Data::Dumper->new([shift], [(shift || "vinfo")])->Maxdepth(2)->Dump();
}
sub vinfo_assign_config($$) sub vinfo_assign_config($$)
{ {
my $vinfo = shift || die; my $vinfo = shift || die;
@ -490,7 +492,6 @@ sub vinfo_child($$;$)
{ {
my $parent = shift || die; my $parent = shift || die;
my $rel_path = shift // die; my $rel_path = shift // die;
my $config = shift;
my $name = $rel_path; my $name = $rel_path;
$name =~ s/^.*\///; $name =~ s/^.*\///;
@ -534,9 +535,8 @@ sub vinfo_init_root($)
return undef unless $detail; return undef unless $detail;
vinfo_set_detail($vol, $detail, $path_verified); vinfo_set_detail($vol, $detail, $path_verified);
# read (and cache) the subvolume list # read the subvolume list, and update %btrfs_tree_cache
return undef unless vinfo_subvol_list($vol, fill_cache => 1);
return undef unless vinfo_subvol_list($vol);
TRACE "vinfo root created: $vol->{PRINT}"; TRACE "vinfo root created: $vol->{PRINT}";
return $vol; return $vol;
@ -548,15 +548,20 @@ sub vinfo_set_detail($$;$)
my $vol = shift || die; my $vol = shift || die;
my $detail = shift || die; my $detail = shift || die;
my $path_verified = shift; my $path_verified = shift;
my @vinfo_detail_keys = qw(id is_root gen cgen uuid parent_uuid received_uuid readonly);
TRACE "updating vinfo detail for: $vol->{PRINT}";
VINFO($detail) if($loglevel >= 4);
# add detail data to vinfo hash # add detail data to vinfo hash
foreach(keys %$detail) { # copy only from keys in @vinfo_detail_keys
next if(uc($_) eq $_); # skip UPPER_CASE keys (except REAL_PATH below) foreach (@vinfo_detail_keys) {
next if($_ eq "path"); # skip "path", this comes in wrong by "btrfs subvolume list" # die if already present matches new
# check if already present matches new
die if(exists($vol->{$_}) && ($vol->{$_} ne $detail->{$_})); die if(exists($vol->{$_}) && ($vol->{$_} ne $detail->{$_}));
$vol->{$_} = $detail->{$_}; $vol->{$_} = $detail->{$_};
# WARN "vinfo_set_detail: missing key \"$_\"" unless($detail->{$_});
# die unless(($_ eq "readonly") || ($_ eq "is_root") || $detail->{is_root} || $detail->{$_});
} }
# be very paranoid, this should never happen # be very paranoid, this should never happen
@ -564,6 +569,7 @@ sub vinfo_set_detail($$;$)
die if(defined($detail->{NAME}) && ($detail->{NAME} ne $vol->{NAME})); die if(defined($detail->{NAME}) && ($detail->{NAME} ne $vol->{NAME}));
die if(defined($detail->{SUBVOL_PATH}) && defined($vol->{SUBVOL_PATH}) && ($detail->{SUBVOL_PATH} ne $vol->{SUBVOL_PATH})); die if(defined($detail->{SUBVOL_PATH}) && defined($vol->{SUBVOL_PATH}) && ($detail->{SUBVOL_PATH} ne $vol->{SUBVOL_PATH}));
# honor REAL_PATH from btrfs_subvolume_detail
if($detail->{REAL_PATH}) { if($detail->{REAL_PATH}) {
$vol->{REAL_PATH} = $detail->{REAL_PATH}; $vol->{REAL_PATH} = $detail->{REAL_PATH};
} }
@ -578,9 +584,6 @@ sub vinfo_set_detail($$;$)
$vol->{REAL_URL} = $vol->{REAL_PATH}; $vol->{REAL_URL} = $vol->{REAL_PATH};
} }
} }
TRACE "vinfo updated for: $vol->{PRINT}";
TRACE(vinfo_dump($vol)) if($loglevel >= 4);
return $vol; return $vol;
} }
@ -1131,7 +1134,7 @@ sub btrfs_subvolume_detail($)
TRACE "btr_detail: found btrfs subvolume: $vol->{PRINT}"; TRACE "btr_detail: found btrfs subvolume: $vol->{PRINT}";
# NOTE: received_uuid is not required here, as btrfs-progs < 4.1 does not give us that information. # NOTE: received_uuid is not required here, as btrfs-progs < 4.1 does not give us that information.
# no worries, we get this from btrfs_subvolume_list() for all subvols. # no worries, we get this from btrfs_subvolume_list() for all subvols.
my @required_keys = qw(name uuid parent_uuid id gen cgen top_level); my @required_keys = qw(name uuid parent_uuid id gen cgen top_level); #!!!
my %trans = ( my %trans = (
"Name" => "name", "Name" => "name",
"uuid" => "uuid", "uuid" => "uuid",
@ -1161,7 +1164,7 @@ sub btrfs_subvolume_detail($)
} }
} }
DEBUG "Parsed " . scalar(keys %detail) . " subvolume detail items: $vol->{PRINT}"; DEBUG "Parsed " . scalar(keys %detail) . " subvolume detail items: $vol->{PRINT}";
TRACE(Data::Dumper->new([\%detail], [("btrfs_subvolume_detail")])->Maxdepth(2)->Dump()) if($loglevel >= 4); VINFO(\%detail, "detail") if($loglevel >= 4);
foreach(@required_keys) { foreach(@required_keys) {
unless(defined($detail{$_})) { unless(defined($detail{$_})) {
ERROR "Failed to parse subvolume detail (unsupported btrfs-progs) for: $vol->{PRINT}"; ERROR "Failed to parse subvolume detail (unsupported btrfs-progs) for: $vol->{PRINT}";
@ -1622,6 +1625,8 @@ sub _btr_tree_fill_cache
my $abs_path = shift; my $abs_path = shift;
# traverse tree and update tree cache # traverse tree and update tree cache
#TRACE "_btr_tree_fill_cache: $abs_path";
$btrfs_tree_cache{$abs_path} = $node; $btrfs_tree_cache{$abs_path} = $node;
$uuid_url_map{$node->{uuid}}->{$abs_path} = $node if($node->{uuid}); $uuid_url_map{$node->{uuid}}->{$abs_path} = $node if($node->{uuid});
foreach(values %{$node->{SUBTREE}}) { foreach(values %{$node->{SUBTREE}}) {
@ -1636,8 +1641,15 @@ sub btr_tree($)
my $vol = shift; my $vol = shift;
# return cached info if present # return cached info if present
return $btrfs_tree_cache{$vol->{REAL_URL}} if($vol->{REAL_URL} && $btrfs_tree_cache{$vol->{REAL_URL}}); if($vol->{REAL_URL} && $btrfs_tree_cache{$vol->{REAL_URL}}) {
return $btrfs_tree_cache{$vol->{URL}} if($btrfs_tree_cache{$vol->{URL}}); TRACE "btrfs_tree: cache HIT: $vol->{REAL_URL}";
return $btrfs_tree_cache{$vol->{REAL_URL}};
}
if($btrfs_tree_cache{$vol->{URL}}) {
TRACE "btrfs_tree: cache HIT: $vol->{URL}";
return $btrfs_tree_cache{$vol->{URL}};
}
TRACE "btrfs_tree: cache MISS: $vol->{REAL_URL} :: $vol->{URL}";
# NOTE: make sure to to have either $vol->{uuid} or $vol->{is_root} # NOTE: make sure to to have either $vol->{uuid} or $vol->{is_root}
# (provided by btrfs_subvolume_show()), or we cannot determine the # (provided by btrfs_subvolume_show()), or we cannot determine the
@ -1651,7 +1663,10 @@ sub btr_tree($)
# man btrfs-subvolume: # man btrfs-subvolume:
# Also every btrfs filesystem has a default subvolume as its initially # Also every btrfs filesystem has a default subvolume as its initially
# top-level subvolume, whose subvolume id is 5(FS_TREE). # top-level subvolume, whose subvolume id is 5(FS_TREE).
my %tree = ( id => 5, SUBTREE => {} ); my %tree = ( id => 5,
is_root => 1,
SUBTREE => {}
);
my %id = ( 5 => \%tree ); my %id = ( 5 => \%tree );
my $subvol_list = btrfs_subvolume_list($vol); my $subvol_list = btrfs_subvolume_list($vol);
@ -1668,6 +1683,7 @@ sub btr_tree($)
} }
# note: it is possible that id < top_level, e.g. after restoring # note: it is possible that id < top_level, e.g. after restoring
my $vol_root;
foreach my $node (@$subvol_list) foreach my $node (@$subvol_list)
{ {
# set SUBTREE / TOP_LEVEL node # set SUBTREE / TOP_LEVEL node
@ -1685,83 +1701,84 @@ sub btr_tree($)
} }
$node->{REL_PATH} = $rel_path; # relative to {TOP_LEVEL}->{path} $node->{REL_PATH} = $rel_path; # relative to {TOP_LEVEL}->{path}
}
my $vol_root; $vol_root = $node if($vol->{id} == $node->{id});
}
unless($vol_root) {
if($vol->{is_root}) { if($vol->{is_root}) {
$vol_root = \%tree; $vol_root = \%tree;
} }
else { else {
# TODO: graceful, this might happen on buggy btrfs-progs ERROR "Failed to resolve tree root for: " . ($vol->{PRINT} // $vol->{id});
die unless($uuid_info{$vol->{uuid}}); return undef;
$vol_root = $uuid_info{$vol->{uuid}}; }
} }
_btr_tree_fill_cache($vol_root, $vol->{REAL_URL}); TRACE "btr_tree: returning tree at id=$vol_root->{id}";
VINFO($vol_root, "node") if($loglevel >= 4);
return $vol_root; return $vol_root;
} }
sub _subtree_list sub _vinfo_subtree_list
{ {
my $tree = shift; my $tree = shift;
my $vinfo_parent = shift;
my $list = shift // []; my $list = shift // [];
my $prefix = shift // ""; my $path_prefix = shift // "";
$tree = $tree->{SUBTREE}; foreach(values %{$tree->{SUBTREE}}) {
foreach(values %$tree) { my $path = $path_prefix . $_->{REL_PATH};
my $path = $prefix . $_->{REL_PATH}; my $vinfo = vinfo_child($vinfo_parent, $path);
push(@$list, { SUBVOL_PATH => $path, vinfo_set_detail($vinfo, $_);
node => $_, $vinfo->{node} = $_;
}); push(@$list, $vinfo);
_subtree_list($_, $list, $path . '/'); _vinfo_subtree_list($_, $vinfo_parent, $list, $path . '/');
} }
return $list; return $list;
} }
sub vinfo_subvol_list($) sub vinfo_subvol_list($;@)
{ {
my $vol = shift || die; my $vol = shift || die;
return $vol->{SUBVOL_LIST} if($vol->{SUBVOL_LIST}); my %opts = @_;
# find cached list
my $subvol_list = $subvol_list_cache{$vol->{URL}};
$subvol_list //= $subvol_list_cache{$vol->{REAL_URL}} if($vol->{REAL_URL} && ($vol->{REAL_URL} ne $vol->{URL}));
if($subvol_list) {
TRACE "vinfo_subvol_list: cache HIT: $vol->{URL}";
$vol->{SUBVOL_LIST} = $subvol_list;
return $subvol_list;
}
TRACE "vinfo_subvol_list: cache MISS: $vol->{URL}";
my $tree_root = btr_tree($vol); my $tree_root = btr_tree($vol);
return undef unless($tree_root); return undef unless($tree_root);
# recurse into $tree_root, returns list of href: { SUBVOL_PATH, node } if($opts{fill_cache}) {
my $list = _subtree_list($tree_root); # force fill cache. _vinfo_subtree_list (below) does not do this, fix!!! TODO
_btr_tree_fill_cache($tree_root, $vol->{REAL_URL});
# return a hash of relative subvolume path
my %ret;
foreach(@$list) {
my $subvol_path = $_->{SUBVOL_PATH};
die if exists $ret{$subvol_path};
my $subvol = vinfo_child($vol, $subvol_path);
vinfo_set_detail($subvol, $_->{node});
$ret{$subvol_path} = $subvol;
} }
DEBUG "Found " . scalar(keys %ret) . " subvolumes below: $vol->{PRINT}"; # recurse into $tree_root, returns array of vinfo
TRACE(Data::Dumper->Dump([\%ret], ["vinfo_subvol_list"])) if($loglevel >= 4); return _vinfo_subtree_list($tree_root, $vol);
}
$vol->{SUBVOL_LIST} = \%ret;
$subvol_list_cache{$vol->{URL}} = \%ret; sub __get_by_id($)
$subvol_list_cache{$vol->{REAL_URL}} = \%ret if($vol->{REAL_URL} && ($vol->{REAL_URL} ne $vol->{URL})); {
return \%ret; my $subvol_list = shift;
my $filter_vol = shift;
my @ret = grep { $_->{id} == $filter_vol->{id} } @$subvol_list;
return undef unless(scalar @ret);
die unless(scalar(@ret) == 1);
return $ret[0];
}
sub __get_by_key_eq($$$)
{
my $subvol_list = shift;
my $filter_key = shift;
my $filter_value = shift;
my @ret = grep { $_->{$filter_key} eq $filter_value } @$subvol_list;
return undef unless(scalar @ret);
die unless(scalar(@ret) == 1);
return $ret[0];
} }
@ -1783,9 +1800,7 @@ sub vinfo_subvol($$)
{ {
my $vol = shift || die; my $vol = shift || die;
my $rel_path = shift // die; my $rel_path = shift // die;
return __get_by_key_eq(vinfo_subvol_list($vol), 'SUBVOL_PATH', $rel_path);
my $subvols = vinfo_subvol_list($vol);
return $subvols->{$rel_path};
} }
@ -1903,7 +1918,7 @@ sub macro_delete($$$$;@)
my $raw_format = ($root_subvol->{CONFIG}->{CONTEXT} eq "target") ? ($root_subvol->{CONFIG}->{target_type} eq "raw") : undef; my $raw_format = ($root_subvol->{CONFIG}->{CONTEXT} eq "target") ? ($root_subvol->{CONFIG}->{target_type} eq "raw") : undef;
my @schedule; my @schedule;
foreach my $vol (values %{vinfo_subvol_list($root_subvol)}) { foreach my $vol (@{vinfo_subvol_list($root_subvol)}) {
my $filename_info = parse_filename($vol->{SUBVOL_PATH}, $subvol_basename, $raw_format); my $filename_info = parse_filename($vol->{SUBVOL_PATH}, $subvol_basename, $raw_format);
unless($filename_info) { unless($filename_info) {
TRACE "Target subvolume does not match btrbk filename scheme, skipping: $vol->{PRINT}"; TRACE "Target subvolume does not match btrbk filename scheme, skipping: $vol->{PRINT}";
@ -1974,7 +1989,7 @@ sub get_snapshot_children($$)
my @ret; my @ret;
my $sroot_subvols = vinfo_subvol_list($sroot); my $sroot_subvols = vinfo_subvol_list($sroot);
foreach (values %$sroot_subvols) { foreach (@$sroot_subvols) {
next unless($_->{readonly}); next unless($_->{readonly});
next unless($_->{parent_uuid} eq $svol->{uuid}); next unless($_->{parent_uuid} eq $svol->{uuid});
TRACE "get_snapshot_children: found: $_->{PRINT}"; TRACE "get_snapshot_children: found: $_->{PRINT}";
@ -1996,7 +2011,7 @@ sub get_receive_targets($$)
{ {
# guess matches by subvolume name (node->received_uuid is not available if BTRFS_PROGS_COMPAT is set) # guess matches by subvolume name (node->received_uuid is not available if BTRFS_PROGS_COMPAT is set)
DEBUG "Fallback to compatibility mode (get_receive_targets)"; DEBUG "Fallback to compatibility mode (get_receive_targets)";
foreach my $target (values %$droot_subvols) { foreach my $target (@$droot_subvols) {
next unless($_->{readonly}); next unless($_->{readonly});
if($target->{NAME} eq $src_vol->{NAME}) { if($target->{NAME} eq $src_vol->{NAME}) {
TRACE "get_receive_targets: by-name: Found receive target: $target->{SUBVOL_PATH}"; TRACE "get_receive_targets: by-name: Found receive target: $target->{SUBVOL_PATH}";
@ -2009,7 +2024,7 @@ sub get_receive_targets($$)
# find matches by comparing uuid / received_uuid # find matches by comparing uuid / received_uuid
my $uuid = $src_vol->{uuid}; my $uuid = $src_vol->{uuid};
die("subvolume info not present: $uuid") unless($uuid_info{$uuid}); die("subvolume info not present: $uuid") unless($uuid_info{$uuid});
foreach (values %$droot_subvols) { foreach (@$droot_subvols) {
next unless($_->{readonly}); next unless($_->{readonly});
next unless($_->{received_uuid} eq $uuid); next unless($_->{received_uuid} eq $uuid);
TRACE "get_receive_targets: by-uuid: Found receive target: $_->{SUBVOL_PATH}"; TRACE "get_receive_targets: by-uuid: Found receive target: $_->{SUBVOL_PATH}";
@ -3024,7 +3039,7 @@ MAIN:
next; next;
} }
# make sure $svol is in subtree of $sroot # make sure $svol is in subtree of $sroot
if(grep { $_->{uuid} eq $detail->{uuid} } values %{vinfo_subvol_list($sroot)}) { if(grep { $_->{uuid} eq $detail->{uuid} } @{vinfo_subvol_list($sroot)}) {
vinfo_set_detail($svol, $detail); vinfo_set_detail($svol, $detail);
} else { } else {
ABORTED($svol, "Not a child subvolume of: $sroot->{PRINT}"); ABORTED($svol, "Not a child subvolume of: $sroot->{PRINT}");
@ -3283,7 +3298,7 @@ MAIN:
my $stats_received = 0; my $stats_received = 0;
my $stats_orphaned = 0; my $stats_orphaned = 0;
my $stats_incomplete = 0; my $stats_incomplete = 0;
foreach my $target_vol (sort { $a->{SUBVOL_PATH} cmp $b->{SUBVOL_PATH} } values %{vinfo_subvol_list($droot)}) { foreach my $target_vol (sort { $a->{SUBVOL_PATH} cmp $b->{SUBVOL_PATH} } @{vinfo_subvol_list($droot)}) {
my $parent_snapshot; my $parent_snapshot;
my $incomplete_backup; my $incomplete_backup;
foreach (@snapshot_children) { foreach (@snapshot_children) {
@ -3428,7 +3443,7 @@ MAIN:
INFO "Cleaning incomplete backups in: $droot->{PRINT}/$snapshot_name.*"; INFO "Cleaning incomplete backups in: $droot->{PRINT}/$snapshot_name.*";
push @out, "$droot->{PRINT}/$snapshot_name.*"; push @out, "$droot->{PRINT}/$snapshot_name.*";
my @delete; my @delete;
foreach my $target_vol (sort { $a->{SUBVOL_PATH} cmp $b->{SUBVOL_PATH} } values %{vinfo_subvol_list($droot)}) { foreach my $target_vol (sort { $a->{SUBVOL_PATH} cmp $b->{SUBVOL_PATH} } @{vinfo_subvol_list($droot)}) {
# incomplete received (garbled) subvolumes have no received_uuid (as of btrfs-progs v4.3.1). # incomplete received (garbled) subvolumes have no received_uuid (as of btrfs-progs v4.3.1).
# a subvolume in droot matching our naming is considered incomplete if received_uuid is not set! # a subvolume in droot matching our naming is considered incomplete if received_uuid is not set!
if(($target_vol->{received_uuid} eq '-') && parse_filename($target_vol->{SUBVOL_PATH}, $snapshot_name)) { if(($target_vol->{received_uuid} eq '-') && parse_filename($target_vol->{SUBVOL_PATH}, $snapshot_name)) {
@ -3553,14 +3568,14 @@ MAIN:
sprintf("%04d%02d%02d", @today) : sprintf("%04d%02d%02d", @today) :
sprintf("%04d%02d%02dT%02d%02d", @today_and_now[0..4])); sprintf("%04d%02d%02dT%02d%02d", @today_and_now[0..4]));
my @unconfirmed_target_name; my @unconfirmed_target_name;
my @lookup = keys %{vinfo_subvol_list($sroot)}; my @lookup = map { $_->{SUBVOL_PATH} } @{vinfo_subvol_list($sroot)};
@lookup = grep s/^\Q$snapdir\E// , @lookup; @lookup = grep s/^\Q$snapdir\E// , @lookup;
foreach my $droot (vinfo_subsection($svol, 'target', 1)) { foreach my $droot (vinfo_subsection($svol, 'target', 1)) {
if(ABORTED($droot)) { if(ABORTED($droot)) {
push(@unconfirmed_target_name, $droot); push(@unconfirmed_target_name, $droot);
next; next;
} }
push(@lookup, keys %{vinfo_subvol_list($droot)}); push(@lookup, map { $_->{SUBVOL_PATH} } @{vinfo_subvol_list($droot)});
} }
@lookup = grep /^\Q$snapshot_basename.$timestamp\E(_[0-9]+)?$/ ,@lookup; @lookup = grep /^\Q$snapshot_basename.$timestamp\E(_[0-9]+)?$/ ,@lookup;
TRACE "Present snapshot names for \"$svol->{PRINT}\": " . join(', ', @lookup); TRACE "Present snapshot names for \"$svol->{PRINT}\": " . join(', ', @lookup);
@ -3636,7 +3651,7 @@ MAIN:
DEBUG "Checking schedule for resume candidates"; DEBUG "Checking schedule for resume candidates";
# add all present backups to schedule, with no value # add all present backups to schedule, with no value
# these are needed for correct results of schedule() # these are needed for correct results of schedule()
foreach my $vol (values %{vinfo_subvol_list($droot)}) { foreach my $vol (@{vinfo_subvol_list($droot)}) {
my $filename_info = parse_filename($vol->{SUBVOL_PATH}, $snapshot_basename, ($droot->{CONFIG}->{target_type} eq "raw")); my $filename_info = parse_filename($vol->{SUBVOL_PATH}, $snapshot_basename, ($droot->{CONFIG}->{target_type} eq "raw"));
unless($filename_info) { unless($filename_info) {
TRACE "Receive target does not match btrbk filename scheme, skipping: $vol->{PRINT}"; TRACE "Receive target does not match btrbk filename scheme, skipping: $vol->{PRINT}";
@ -3737,7 +3752,7 @@ MAIN:
$preserve_latest_backup ||= "preserve forced: possibly parent of latest backup"; $preserve_latest_backup ||= "preserve forced: possibly parent of latest backup";
# Note that we could check against $svol->{SNAPSHOT_CREATED}->{parent_uuid} to be certain, # Note that we could check against $svol->{SNAPSHOT_CREATED}->{parent_uuid} to be certain,
# but this information is not available in $dryrun: # but this information is not available in $dryrun:
# foreach my $vol (values %{vinfo_subvol_list($droot)}) { # foreach my $vol (@{vinfo_subvol_list($droot)}) {
# $vol->{FORCE_PRESERVE} = 1 if($vol->{received_uuid} eq $svol->{SNAPSHOT_CREATED}->{parent_uuid}); # $vol->{FORCE_PRESERVE} = 1 if($vol->{received_uuid} eq $svol->{SNAPSHOT_CREATED}->{parent_uuid});
# } # }
} }