diff --git a/userspace/marsadm b/userspace/marsadm index 1acf358d..5cc6164e 100755 --- a/userspace/marsadm +++ b/userspace/marsadm @@ -550,20 +550,32 @@ sub get_minmax_replays { my %visited_pos; +sub _visit { + my ($nr, $peer) = @_; + $nr =~ s:^0*::; + my $visit = "$nr,$peer"; + $visited_pos{$visit} = 1; +} + +sub _is_visited { + my ($nr, $peer) = @_; + $nr =~ s:^0*::; + my $visit = "$nr,$peer"; + return $visited_pos{$visit}; +} + sub _parse_pos { my ($pos, $do_remember) = @_; - $pos =~ m/((?:log|version)-([0-9]+)-([^,]+)(?:,([0-9]+))?)/ or ldie "cannot parse '$pos'\n"; - if ($do_remember) { - my $visit = "$2,$3"; - $visited_pos{$visit} = 1; - } + $pos =~ m/((?:log|version)-([0-9]+)-([^,]+)(?:,([0-9]+))?)/ or lwarn "cannot parse position info '$pos'\n"; + _visit($2, $3) if $do_remember; return ($1, int($2), $3, defined($4) ? int($4) : -1); } sub _get_prev_pos { - my ($basedir, $nr, $host) = @_; - my $path = sprintf("$basedir/version-%09d-$host", $nr); - my $vers = get_link($path, 1); + my ($basedir, $nr, $peer, $do_remember) = @_; + my $path = sprintf("$basedir/version-%09d-$peer", $nr); + my $vers = get_link($path, 2); + _parse_pos($path, 1) if $do_remember && defined($vers) && $vers; $vers =~ s/^.*://; return $vers; } @@ -647,30 +659,39 @@ sub detect_splitbrain { } sub _mark_path_backward { - my ($basedir, $pos) = @_; + my ($basedir, $pos, $peer, $skip_last) = @_; + my $sum = 0; for (;;) { my ($p, $nr, $from, $len) = _parse_pos($pos, 1); - $pos = _get_prev_pos($basedir, $nr, $from); + _visit($nr, $peer); + $pos = _get_prev_pos($basedir, $nr, $peer, 1); last if !$pos; + # optionally don't count the last versionlink, pointing into nirvana + if (defined($skip_last) && $skip_last && $nr > 1) { + my ($p, $nr, $from, $len) = _parse_pos($pos, 0); + last if !$p; + my $next = _get_prev_pos($basedir, $nr, $peer, 1); + last if !$next; + } + $sum += $len; } + return $sum; } sub _mark_path_forward { - my ($basedir, $pos) = @_; + my ($basedir, $pos, $peer) = @_; my @list = ($pos); while (@list) { my %next_list; foreach $pos (@list) { my ($p, $nr, $from, $len) = _parse_pos($pos, 1); - my $next = sprintf("$basedir/version-%09d-*", $nr + 1); - my @candidates = glob($next); - foreach my $cand (@candidates) { - my $vers = get_link($cand, 1); - $vers =~ s/^.*://; - my ($cp, $cnr, $cfrom, $clen) = _parse_pos($vers, 0); - if (int($cnr) == int($nr) && $cfrom eq $from && $clen == $len) { - $next_list{$cand} = 1; - } + my $cand = sprintf("$basedir/version-%09d-$peer", $nr + 1); + my $vers = get_link($cand, 2); + next unless defined($vers) && $vers ne ""; + $vers =~ s/^.*://; + my ($cp, $cnr, $cfrom, $clen) = _parse_pos($vers, 0); + if (int($cnr) == int($nr) && $cfrom eq $from && $clen == $len) { + $next_list{$cand} = 1; } } @list = keys(%next_list); @@ -684,20 +705,82 @@ sub _mark_path_transitive { sub log_purge_res { my ($cmd, $res) = @_; + lwarn "DANGEROUS OPERATION: $cmd on resource '$res'\n"; + my %start_logs; + my $start_count = 0; my $basedir = "$mars/resource-$res"; - my @files = glob("$basedir/{log,version}-*"); - foreach my $replay (glob("$basedir/replay-*")) { + foreach my $data (glob("$basedir/{data,replay}-*")) { + $data =~ m:/(data|replay)-(.+):; + my $peer = $2; + my $replay = "$basedir/replay-$peer"; my $target = get_link($replay, 1); - _mark_path_transitive($basedir, $target); + lprint "found replay link '$replay' -> '$target'\n"; + $target =~ s/,.*//; + $start_logs{$target}++; + $start_count++; + _mark_path_transitive($basedir, $target, $peer); } - foreach my $file (@files) { - $file =~ m:/((log|version)-([0-9]+)-([^,]+)): or ldie "bad path '$file'\n"; - next if (!$force && $4 ne $host); - my $visit = "$3,$4"; - lprint "checking '$1'\n"; - next if $visited_pos{$visit}; + if (!$start_count) { + ldie "Resource contains no valid information - refusing to delete everything for safety reasons\n"; + } + my %logs; + foreach my $file (glob("$basedir/version-*")) { + $file =~ m:/(version-([0-9]+)-([^,]+)): or ldie "bad path '$file'\n"; + my $cand = $1; + my $nr = $2; + my $from = $3; + lprint "checking '$cand'\n"; + my $vers = get_link($file, 1); + $vers =~ m/(log-[0-9]+-[^,:]+)/; + my $log = $1; + lprint " corresponding logfile is '$log'\n"; + $logs{$log}++; + if (_is_visited($nr, $from)) { + lprint " ok '$cand'\n"; + next; + } + if (!$force && $from ne $host) { + lprint " skipping foreign object '$cand'\n"; + next; + } + lwarn "deleting foreign object from peer '$from' because you said --force\n" if $from ne $host; _create_delete($file); } + foreach my $file (glob("$basedir/log-*")) { + $file =~ m:/(log-[0-9]+-(.*)): or ldie "bad path '$file'\n"; + my $log = $1; + my $from = $2; + lprint "checking '$log'\n"; + if ($logs{$log}) { + lprint " ok '$log'\n"; + $logs{$log} = -1; + next; + } + if ($start_logs{$log}) { + lprint " ok start '$log'\n"; + $logs{$log} = -1; + next; + } + if (!$force && $from ne $host) { + lprint " skipping foreign object '$log'\n"; + next; + } + lwarn "deleting foreign object from peer '$from' because you said --force\n" if $from ne $host; + _create_delete($file); + } + my $count = 0; + foreach my $log (sort(keys(%logs))) { + my $nr = $logs{$log}; + next if $nr < 0 || -e "$basedir/$log"; + lprint_stderr "info: logfile '$log' is referenced ($nr), but not present.\n"; + $count++; + } + if ($count) { + lprint_stderr " Unreferenced logfiles are not necessarily bad.\n"; + lprint_stderr " They can regularly appear after 'leave-resource',\n"; + lprint_stderr " or 'invalidate', or after emergency mode,\n"; + lprint_stderr " or after similar operations.\n"; + } finish_links(); _wait_delete(); }