X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=server-src%2Famcheckdump.pl;h=b0074562c06c1352f1315cdbd1adfa7db498144c;hb=c21061ed466e7ee71cb21dd35010e33f6cf76424;hp=3e45c1d737739544f5e4a92475a2f87195be39b9;hpb=fd48f3e498442f0cbff5f3606c7c403d0566150e;p=debian%2Famanda diff --git a/server-src/amcheckdump.pl b/server-src/amcheckdump.pl index 3e45c1d..b007456 100644 --- a/server-src/amcheckdump.pl +++ b/server-src/amcheckdump.pl @@ -1,5 +1,5 @@ #! @PERL@ -# Copyright (c) 2007,2008,2009 Zmanda, Inc. All Rights Reserved. +# Copyright (c) 2007, 2008, 2009, 2010 Zmanda, Inc. All Rights Reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License version 2 as published @@ -19,6 +19,7 @@ use lib '@amperldir@'; use strict; +use warnings; use File::Basename; use Getopt::Long; @@ -32,13 +33,14 @@ use Amanda::Tapelist; use Amanda::Logfile; use Amanda::Util qw( :constants ); use Amanda::Changer; +use Amanda::Recovery::Clerk; use Amanda::Recovery::Scan; +use Amanda::Recovery::Planner; use Amanda::Constants; +use Amanda::DB::Catalog; +use Amanda::Cmdline; use Amanda::MainLoop; - -# Have all images been verified successfully so far? -my $all_success = 1; -my $verbose = 0; +use Amanda::Xfer qw( :constants ); sub usage { print <new(name => 'stdin'); - $scan = Amanda::Recovery::Scan->new(interactive => $inter); - if ($scan->isa("Amanda::Changer::Error")) { - print "$scan\n"; - exit 1; - } - } - - my $load_sub = make_cb(load_sub => sub { - my ($err) = @_; - if ($err) { - print STDERR $err, "\n"; - exit 1; - } - - $scan->find_volume( - label => $label, - res_cb => sub { - (my $err, $reservation) = @_; - if ($err) { - print STDERR $err, "\n"; - exit 1; - } - Amanda::MainLoop::quit(); - }, - ); - }); - - my $start = make_cb(start => sub { - if (defined $reservation) { - $reservation->release(finished_cb => $load_sub); - } else { - $load_sub->(undef); - } - }); - - # let the mainloop run until the find is done. This is a temporary - # hack until all of amcheckdump is event-based. - Amanda::MainLoop::call_later($start); - Amanda::MainLoop::run(); - - return $reservation->{'device'}; -} - -# Try to open a device containing a volume with the given label. -# return ($device, undef) on success -# return (undef, $err) on error -sub try_open_device { - my ($label, $timestamp) = @_; +## Application initialization - # can we use the same device as last time? - if ($current_device_label eq $label) { - return $current_device; - } +Amanda::Util::setup_application("amcheckdump", "server", $CONTEXT_CMDLINE); - # nope -- get rid of that device - close_device(); +my $exit_code = 0; - my $device = find_next_device($label); - my $device_name = $device->device_name; +my $opt_timestamp; +my $opt_verbose = 0; +my $config_overrides = new_config_overrides($#ARGV+1); - my $label_status = $device->status; - if ($label_status != $DEVICE_STATUS_SUCCESS) { - if ($device->error_or_status() ) { - return (undef, "Could not read device $device_name: " . - $device->error_or_status()); - } else { - return (undef, "Could not read device $device_name: one of " . - join(", ", DevicestatusFlags_to_strings($label_status))); - } - } +Getopt::Long::Configure(qw(bundling)); +GetOptions( + 'timestamp|t=s' => \$opt_timestamp, + 'verbose|v' => \$opt_verbose, + 'help|usage|?' => \&usage, + 'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); }, +) or usage(); - my $start = make_cb(start => sub { - $reservation->set_label(label => $device->volume_label(), - finished_cb => sub { - Amanda::MainLoop::quit(); - }); - }); +usage() if (@ARGV < 1); - Amanda::MainLoop::call_later($start); - Amanda::MainLoop::run(); +my $timestamp = $opt_timestamp; - if ($device->volume_label() ne $label) { - return (undef, "Labels do not match: Expected '$label', but the " . - "device contains '" . $device->volume_label() . "'"); +my $config_name = shift @ARGV; +set_config_overrides($config_overrides); +config_init($CONFIG_INIT_EXPLICIT_NAME, $config_name); +my ($cfgerr_level, @cfgerr_errors) = config_errors(); +if ($cfgerr_level >= $CFGERR_WARNINGS) { + config_print_errors(); + if ($cfgerr_level >= $CFGERR_ERRORS) { + die("errors processing config file"); } +} - if ($device->volume_time() ne $timestamp) { - return (undef, "Timestamps do not match: Expected '$timestamp', " . - "but the device contains '" . - $device->volume_time() . "'"); - } +Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER); - if (!$device->start($ACCESS_READ, undef, undef)) { - return (undef, "Error reading device $device_name: " . - $device->error_or_status()); - return undef; - } +# Interactive package +package Amanda::Interactive::amcheckdump; +use POSIX qw( :errno_h ); +use Amanda::MainLoop qw( :GIOCondition ); +use vars qw( @ISA ); +@ISA = qw( Amanda::Interactive ); - $current_device = $device; - $current_device_label = $device->volume_label(); +sub new { + my $class = shift; - return ($device, undef); + my $self = { + input_src => undef}; + return bless ($self, $class); } -sub close_device { - $current_device = undef; - $current_device_label = undef; +sub abort() { + my $self = shift; + + if ($self->{'input_src'}) { + $self->{'input_src'}->remove(); + $self->{'input_src'} = undef; + } } -## Validation application +sub user_request { + my $self = shift; + my %params = @_; + my $buffer = ""; + + my $message = $params{'message'}; + my $label = $params{'label'}; + my $err = $params{'err'}; + my $chg_name = $params{'chg_name'}; + + my $data_in = sub { + my $b; + my $n_read = POSIX::read(0, $b, 1); + if (!defined $n_read) { + return if ($! == EINTR); + $self->abort(); + return $params{'finished_cb'}->( + Amanda::Changer::Error->new('fatal', + message => "Fail to read from stdin")); + } elsif ($n_read == 0) { + $self->abort(); + return $params{'finished_cb'}->( + Amanda::Changer::Error->new('fatal', + message => "Aborted by user")); + } else { + $buffer .= $b; + if ($b eq "\n") { + my $line = $buffer; + chomp $line; + $buffer = ""; + $self->abort(); + return $params{'finished_cb'}->(undef, $line); + } + } + }; -my ($current_validation_pid, $current_validation_pipeline, $current_validation_image); + print STDERR "$err\n"; + print STDERR "Insert volume labeled '$label' in $chg_name\n"; + print STDERR "and press enter, or ^D to abort.\n"; -sub is_part_of_same_image { - my ($image, $header) = @_; + $self->{'input_src'} = Amanda::MainLoop::fd_source(0, $G_IO_IN|$G_IO_HUP|$G_IO_ERR); + $self->{'input_src'}->set_callback($data_in); + return; +}; - return ($image->{timestamp} eq $header->{datestamp} - and $image->{hostname} eq $header->{name} - and $image->{diskname} eq $header->{disk} - and $image->{level} == $header->{dumplevel}); -} +package main::Feedback; -# Return a filehandle for the validation application that will handle this -# image. This function takes care of split dumps. At the moment, we have -# a single "current" validation application, and as such assume that split dumps -# are stored contiguously and in order on the volume. -sub open_validation_app { - my ($image, $header) = @_; - - # first, see if this is the same image we were looking at previously - if (defined($current_validation_image) - and $current_validation_image->{timestamp} eq $image->{timestamp} - and $current_validation_image->{hostname} eq $image->{hostname} - and $current_validation_image->{diskname} eq $image->{diskname} - and $current_validation_image->{level} == $image->{level}) { - # TODO: also check that the part number is correct - Amanda::Debug::debug("Continuing with previously started validation process"); - return $current_validation_pipeline, $current_command; - } - - my @command = find_validation_command($header); +use Amanda::Recovery::Clerk; +use base 'Amanda::Recovery::Clerk::Feedback'; +use Amanda::MainLoop; - if ($#command == 0) { - $command[0]->{fd} = Symbol::gensym; - $command[0]->{pid} = open3($current_validation_pipeline, "/dev/null", $command[0]->{stderr}, $command[0]->{pgm}); - } else { - my $nb = $#command; - $command[$nb]->{fd} = "VAL_GLOB_$nb"; - $command[$nb]->{stderr} = Symbol::gensym; - $command[$nb]->{pid} = open3($command[$nb]->{fd}, "/dev/null", $command[$nb]->{stderr}, $command[$nb]->{pgm}); - close($command[$nb]->{stderr}); - while ($nb-- > 1) { - $command[$nb]->{fd} = "VAL_GLOB_$nb"; - $command[$nb]->{stderr} = Symbol::gensym; - $command[$nb]->{pid} = open3($command[$nb]->{fd}, ">&". $command[$nb+1]->{fd}, $command[$nb]->{stderr}, $command[$nb]->{pgm}); - close($command[$nb+1]->{fd}); - } - $command[$nb]->{stderr} = Symbol::gensym; - $command[$nb]->{pid} = open3($current_validation_pipeline, ">&".$command[$nb+1]->{fd}, $command[$nb]->{stderr}, $command[$nb]->{pgm}); - close($command[$nb+1]->{fd}); - } +sub new { + my $class = shift; + my ($chg, $dev_name) = @_; - my @com; - for my $i (0..$#command) { - push @com, $command[$i]->{pgm}; - } - my $validation_command = join (" | ", @com); - Amanda::Debug::debug(" using '$validation_command'"); - print " using '$validation_command'\n" if $verbose; - - $current_validation_image = $image; - return $current_validation_pipeline, \@command; + return bless { + chg => $chg, + dev_name => $dev_name, + }, $class; } -# Close any running validation app, checking its exit status for errors. Sets -# $all_success to false if there is an error. -sub close_validation_app { - my $command = shift; - - if (!defined($current_validation_pipeline)) { - return; - } +sub clerk_notif_part { + my $self = shift; + my ($label, $filenum, $header) = @_; - # first close the applications standard input to signal it to stop - close($current_validation_pipeline); - my $result = 0; - while (my $cmd = shift @$command) { - #read its stderr - my $fd = $cmd->{stderr}; - while(<$fd>) { - print $_; - $result++; - } - waitpid $cmd->{pid}, 0; - my $err = $?; - my $res = $!; - - if ($err == -1) { - Amanda::Debug::debug("failed to execute $cmd->{pgm}: $res"); - print "failed to execute $cmd->{pgm}: $res\n"; - $result++; - } elsif ($err & 127) { - Amanda::Debug::debug(sprintf("$cmd->{pgm} died with signal %d, %s coredump", - ($err & 127), ($err & 128) ? 'with' : 'without')); - printf "$cmd->{pgm} died with signal %d, %s coredump\n", - ($err & 127), ($err & 128) ? 'with' : 'without'; - $result++; - } elsif ($err > 0) { - Amanda::Debug::debug(sprintf("$cmd->{pgm} exited with value %d", $err >> 8)); - printf "$cmd->{pgm} exited with value %d %d\n", $err >> 8, $err; - $result++; - } + print STDERR "Reading volume $label file $filenum\n"; +} - } +sub clerk_notif_holding { + my $self = shift; + my ($filename, $header) = @_; - if ($result) { - Amanda::Debug::debug("Image was not successfully validated"); - print "Image was not successfully validated\n\n"; - $all_success = 0; # flag this as a failure - } else { - Amanda::Debug::debug("Image was successfully validated"); - print("Image was successfully validated.\n\n") if $verbose; - } - - $current_validation_pipeline = undef; - $current_validation_image = undef; + print STDERR "Reading '$filename'\n"; } -# Given a dumpfile_t, figure out the command line to validate. -# return an array of command to execute +package main; + +# Given a dumpfile_t, figure out the command line to validate, specified +# as an argv array sub find_validation_command { my ($header) = @_; my @result = (); - # We base the actual archiver on our own table, but just trust - # whatever is listed as the decrypt/uncompress commands. + # We base the actual archiver on our own table my $program = uc(basename($header->{program})); my $validation_program; if ($program ne "APPLICATION") { my %validation_programs = ( - "STAR" => "$Amanda::Constants::STAR -t -f -", - "DUMP" => "$Amanda::Constants::RESTORE tbf 2 -", - "VDUMP" => "$Amanda::Constants::VRESTORE tf -", - "VXDUMP" => "$Amanda::Constants::VXRESTORE tbf 2 -", - "XFSDUMP" => "$Amanda::Constants::XFSRESTORE -t -v silent -", - "TAR" => "$Amanda::Constants::GNUTAR tf -", - "GTAR" => "$Amanda::Constants::GNUTAR tf -", - "GNUTAR" => "$Amanda::Constants::GNUTAR tf -", - "SMBCLIENT" => "$Amanda::Constants::GNUTAR tf -", + "STAR" => [ $Amanda::Constants::STAR, qw(-t -f -) ], + "DUMP" => [ $Amanda::Constants::RESTORE, qw(tbf 2 -) ], + "VDUMP" => [ $Amanda::Constants::VRESTORE, qw(tf -) ], + "VXDUMP" => [ $Amanda::Constants::VXRESTORE, qw(tbf 2 -) ], + "XFSDUMP" => [ $Amanda::Constants::XFSRESTORE, qw(-t -v silent -) ], + "TAR" => [ $Amanda::Constants::GNUTAR, qw(--ignore-zeros -tf -) ], + "GTAR" => [ $Amanda::Constants::GNUTAR, qw(--ignore-zeros -tf -) ], + "GNUTAR" => [ $Amanda::Constants::GNUTAR, qw(--ignore-zeros -tf -) ], + "SMBCLIENT" => [ $Amanda::Constants::GNUTAR, qw(--ignore-zeros -tf -) ], + "PKZIP" => undef, ); - $validation_program = $validation_programs{$program}; - if (!defined $validation_program) { - Amanda::Debug::debug("Unknown program '$program'"); - print "Unknown program '$program'.\n" if $program ne "PKZIP"; + if (!exists $validation_programs{$program}) { + debug("Unknown program '$program' in header; no validation to perform"); + return undef; } + return $validation_programs{$program}; + } else { if (!defined $header->{application}) { - Amanda::Debug::debug("Application not set"); - print "Application not set\n"; + warning("Application not set"); + return undef; + } + my $program_path = $Amanda::Paths::APPLICATION_DIR . "/" . + $header->{application}; + if (!-x $program_path) { + debug("Application '" . $header->{application}. + "($program_path)' not available on the server"); + return undef; } else { - my $program_path = $Amanda::Paths::APPLICATION_DIR . "/" . - $header->{application}; - if (!-x $program_path) { - Amanda::Debug::debug("Application '" . $header->{application}. - "($program_path)' not available on the server; ". - "Will send dumps to /dev/null instead."); - Amanda::Debug::debug("Application '$header->{application}' in path $program_path not available on server"); - } else { - $validation_program = $program_path . " validate"; - } + return [ $program_path, "validate" ]; } } +} - if (defined $header->{decrypt_cmd} && - length($header->{decrypt_cmd}) > 0) { - if ($header->{dle_str} =~ /CUSTOM/) { - # Can't decrypt client encrypted image - my $cmd; - $cmd->{pgm} = "cat"; - push @result, $cmd; - return @result; - } - my $cmd; - $cmd->{pgm} = $header->{decrypt_cmd}; - $cmd->{pgm} =~ s/ *\|$//g; - push @result, $cmd; - } - if (defined $header->{uncompress_cmd} && - length($header->{uncompress_cmd}) > 0) { - #If the image is not compressed, the decryption is here - if ((!defined $header->{decrypt_cmd} || - length($header->{decrypt_cmd}) == 0 ) and - $header->{dle_str} =~ /CUSTOM/) { - # Can't decrypt client encrypted image - my $cmd; - $cmd->{pgm} = "cat"; - push @result, $cmd; - return @result; - } - my $cmd; - $cmd->{pgm} = $header->{uncompress_cmd}; - $cmd->{pgm} =~ s/ *\|$//g; - push @result, $cmd; - } +sub main { + my ($finished_cb) = @_; + + my $tapelist; + my $chg; + my $interactive; + my $scan; + my $clerk; + my $plan; + my $timestamp; + my $all_success = 1; + my @xfer_errs; + + my $steps = define_steps + cb_ref => \$finished_cb; + + step start => sub { + # set up the tapelist + my $tapelist_file = config_dir_relative(getconf($CNF_TAPELIST)); + $tapelist = Amanda::Tapelist->new($tapelist_file); + + # get the timestamp + $timestamp = $opt_timestamp; + $timestamp = Amanda::DB::Catalog::get_latest_write_timestamp() + unless defined $opt_timestamp; + + # make an interactivity plugin + $interactive = Amanda::Interactive::amcheckdump->new(); + + # make a changer + $chg = Amanda::Changer->new(); + return $steps->{'quit'}->($chg) + if $chg->isa("Amanda::Changer::Error"); + + # make a scan + $scan = Amanda::Recovery::Scan->new( + chg => $chg, + interactive => $interactive); + return $steps->{'quit'}->($scan) + if $scan->isa("Amanda::Changer::Error"); + + # make a clerk + $clerk = Amanda::Recovery::Clerk->new( + feedback => main::Feedback->new($chg), + scan => $scan); + + # make a plan + my $spec = Amanda::Cmdline::dumpspec_t->new(undef, undef, undef, undef, $timestamp); + Amanda::Recovery::Planner::make_plan( + dumpspecs => [ $spec ], + changer => $chg, + plan_cb => $steps->{'plan_cb'}); + }; - my $command; - if (!defined $validation_program) { - $command->{pgm} = "cat"; - } else { - $command->{pgm} = $validation_program; - } + step plan_cb => sub { + (my $err, $plan) = @_; + $steps->{'quit'}->($err) if $err; - push @result, $command; + my @tapes = $plan->get_volume_list(); + my @holding = $plan->get_holding_file_list(); + if (!@tapes && !@holding) { + print "Could not find any matching dumps.\n"; + return $steps->{'quit'}->(); + } - return @result; -} + if (@tapes) { + printf("You will need the following volume%s: %s\n", (@tapes > 1) ? "s" : "", + join(", ", map { $_->{'label'} } @tapes)); + } + if (@holding) { + printf("You will need the following holding file%s: %s\n", (@tapes > 1) ? "s" : "", + join(", ", @holding)); + } -## Application initialization + # nothing else is going on right now, so a blocking "Press enter.." is OK + print "Press enter when ready\n"; + ; -Amanda::Util::setup_application("amcheckdump", "server", $CONTEXT_CMDLINE); + my $dump = shift @{$plan->{'dumps'}}; + if (!$dump) { + return $steps->{'quit'}->("No backup written on timestamp $timestamp."); + } -my $timestamp = undef; -my $config_overrides = new_config_overrides($#ARGV+1); + $steps->{'check_dumpfile'}->($dump); + }; -Getopt::Long::Configure(qw(bundling)); -GetOptions( - 'timestamp|t=s' => \$timestamp, - 'verbose|v' => \$verbose, - 'help|usage|?' => \&usage, - 'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); }, -) or usage(); + step check_dumpfile => sub { + my ($dump) = @_; -usage() if (@ARGV < 1); + print "Validating image " . $dump->{hostname} . ":" . + $dump->{diskname} . " dumped " . $dump->{dump_timestamp} . " level ". + $dump->{level}; + if ($dump->{'nparts'} > 1) { + print " ($dump->{nparts} parts)"; + } + print "\n"; -my $timestamp_argument = 0; -if (defined $timestamp) { $timestamp_argument = 1; } + @xfer_errs = (); + $clerk->get_xfer_src( + dump => $dump, + xfer_src_cb => $steps->{'xfer_src_cb'}); + }; -my $config_name = shift @ARGV; -set_config_overrides($config_overrides); -config_init($CONFIG_INIT_EXPLICIT_NAME, $config_name); -my ($cfgerr_level, @cfgerr_errors) = config_errors(); -if ($cfgerr_level >= $CFGERR_WARNINGS) { - config_print_errors(); - if ($cfgerr_level >= $CFGERR_ERRORS) { - die("errors processing config file"); - } -} + step xfer_src_cb => sub { + my ($errs, $hdr, $xfer_src, $directtcp_supported) = @_; + return $steps->{'quit'}->(join("; ", @$errs)) if $errs; + + # set up any filters that need to be applied; decryption first + my @filters; + if ($hdr->{'encrypted'}) { + if ($hdr->{'srv_encrypt'}) { + push @filters, + Amanda::Xfer::Filter::Process->new( + [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0, 0); + } elsif ($hdr->{'clnt_encrypt'}) { + push @filters, + Amanda::Xfer::Filter::Process->new( + [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0, 0); + } else { + return failure("could not decrypt encrypted dump: no program specified", + $finished_cb); + } -Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER); + $hdr->{'encrypted'} = 0; + $hdr->{'srv_encrypt'} = ''; + $hdr->{'srv_decrypt_opt'} = ''; + $hdr->{'clnt_encrypt'} = ''; + $hdr->{'clnt_decrypt_opt'} = ''; + $hdr->{'encrypt_suffix'} = 'N'; + } -my $tapelist_file = config_dir_relative(getconf($CNF_TAPELIST)); -my $tl = Amanda::Tapelist::read_tapelist($tapelist_file); - -# If we weren't given a timestamp, find the newer of -# amdump.1 or amflush.1 and extract the datestamp from it. -if (!defined $timestamp) { - my $amdump_log = config_dir_relative(getconf($CNF_LOGDIR)) . "/amdump.1"; - my $amflush_log = config_dir_relative(getconf($CNF_LOGDIR)) . "/amflush.1"; - my $logfile; - if (-f $amflush_log && -f $amdump_log && - -M $amflush_log < -M $amdump_log) { - $logfile=$amflush_log; - } elsif (-f $amdump_log) { - $logfile=$amdump_log; - } elsif (-f $amflush_log) { - $logfile=$amflush_log; - } else { - print "Could not find amdump.1 or amflush.1 files.\n"; - exit; - } + if ($hdr->{'compressed'}) { + # need to uncompress this file + + if ($hdr->{'srvcompprog'}) { + # TODO: this assumes that srvcompprog takes "-d" to decrypt + push @filters, + Amanda::Xfer::Filter::Process->new( + [ $hdr->{'srvcompprog'}, "-d" ], 0, 0); + } elsif ($hdr->{'clntcompprog'}) { + # TODO: this assumes that clntcompprog takes "-d" to decrypt + push @filters, + Amanda::Xfer::Filter::Process->new( + [ $hdr->{'clntcompprog'}, "-d" ], 0, 0); + } else { + push @filters, + Amanda::Xfer::Filter::Process->new( + [ $Amanda::Constants::UNCOMPRESS_PATH, + $Amanda::Constants::UNCOMPRESS_OPT ], 0, 0); + } - # extract the datestamp from the dump log - open (AMDUMP, "<$logfile") || die(); - while() { - if (/^amdump: starttime (\d*)$/) { - $timestamp = $1; - } - elsif (/^amflush: starttime (\d*)$/) { - $timestamp = $1; + # adjust the header + $hdr->{'compressed'} = 0; + $hdr->{'uncompress_cmd'} = ''; } - elsif (/^planner: timestamp (\d*)$/) { - $timestamp = $1; - } - } - close AMDUMP; -} -# Find all logfiles matching our timestamp -my $logfile_dir = config_dir_relative(getconf($CNF_LOGDIR)); -my @logfiles = - grep { $_ =~ /^log\.$timestamp(?:\.[0-9]+|\.amflush)?$/ } - Amanda::Logfile::find_log(); - -# Check log file directory if find_log didn't find tape written -# on that tapestamp -if (!@logfiles) { - opendir(DIR, $logfile_dir) || die "can't opendir $logfile_dir: $!"; - @logfiles = grep { /^log.$timestamp\..*/ } readdir(DIR); - closedir DIR; - - if (!@logfiles) { - if ($timestamp_argument) { - print STDERR "Can't find any logfiles with timestamp $timestamp.\n"; - } else { - print STDERR "Can't find the logfile for last run.\n"; + # and set up the validation command as a filter element, since + # we need to throw out its stdout + my $argv = find_validation_command($hdr); + if (defined $argv) { + push @filters, Amanda::Xfer::Filter::Process->new($argv, 0, 0); } - exit 1; - } -} -# compile a list of *all* dumps in those logfiles -my @images; -for my $logfile (@logfiles) { - chomp $logfile; - push @images, Amanda::Logfile::search_logfile(undef, $timestamp, - "$logfile_dir/$logfile", 1); -} -my $nb_images = @images; - -# filter only "ok" dumps, removing partial and failed dumps -@images = Amanda::Logfile::dumps_match([@images], - undef, undef, undef, undef, 1); - -if (!@images) { - if ($nb_images == 0) { - if ($timestamp_argument) { - print STDERR "No backup written on timestamp $timestamp.\n"; - } else { - print STDERR "No backup written on latest run.\n"; - } - } else { - if ($timestamp_argument) { - print STDERR "No complete backup available on timestamp $timestamp.\n"; - } else { - print STDERR "No complete backup available on latest run.\n"; - } - } - exit 1; -} + # we always throw out stdout + my $xfer_dest = Amanda::Xfer::Dest::Null->new(0); -# Find unique tapelist, using a hash to filter duplicate tapes -my %tapes = map { ($_->{label}, undef) } @images; -my @tapes = sort { $a cmp $b } keys %tapes; + my $xfer = Amanda::Xfer->new([ $xfer_src, @filters, $xfer_dest ]); + $xfer->start($steps->{'handle_xmsg'}); + $clerk->start_recovery( + xfer => $xfer, + recovery_cb => $steps->{'recovery_cb'}); + }; -if (!@tapes) { - print STDERR "Could not find any matching dumps.\n"; - exit 1; -} + step handle_xmsg => sub { + my ($src, $msg, $xfer) = @_; -printf("You will need the following tape%s: %s\n", (@tapes > 1) ? "s" : "", - join(", ", @tapes)); -print "Press enter when ready\n"; -; + $clerk->handle_xmsg($src, $msg, $xfer); + if ($msg->{'type'} == $XMSG_INFO) { + Amanda::Debug::info($msg->{'message'}); + } elsif ($msg->{'type'} == $XMSG_ERROR) { + push @xfer_errs, $msg->{'message'}; + } + }; -# Now loop over the images, verifying each one. + step recovery_cb => sub { + my %params = @_; -my $header; + # distinguish device errors from validation errors + if (@{$params{'errors'}}) { + print STDERR "While reading from volumes:\n"; + print STDERR "$_\n" for @{$params{'errors'}}; + return $steps->{'quit'}->("validation aborted"); + } -IMAGE: -for my $image (@images) { - my $check = sub { - my ($ok, $msg) = @_; - if (!$ok) { + if (@xfer_errs) { + print STDERR "Validation errors:\n"; + print STDERR "$_\n" for @xfer_errs; $all_success = 0; - Amanda::Debug::debug("Image was not successfully validated: $msg"); - print "Image was not successfully validated: $msg.\n"; - next IMAGE; } - }; - - # If it's a new image - my $new_image = !(defined $header); - if (!$new_image) { - if (!is_part_of_same_image($image, $header)) { - close_validation_app($current_command); - $new_image = 1; -} - } - Amanda::Debug::debug("Validating image " . $image->{hostname} . ":" . - $image->{diskname} . " datestamp " . $image->{timestamp} . " level ". - $image->{level} . " part " . $image->{partnum} . "/" . - $image->{totalparts} . "on tape " . $image->{label} . " file #" . - $image->{filenum}); - - if ($new_image) { - printf("Validating image %s:%s datestamp %s level %s part %d/%d on tape %s file #%d\n", - $image->{hostname}, $image->{diskname}, $image->{timestamp}, - $image->{level}, $image->{partnum}, $image->{totalparts}, - $image->{label}, $image->{filenum}); - } else { - printf(" part %s:%s datestamp %s level %s part %d/%d on tape %s file #%d\n", - $image->{hostname}, $image->{diskname}, $image->{timestamp}, - $image->{level}, $image->{partnum}, $image->{totalparts}, - $image->{label}, $image->{filenum}); - } - - # note that if there is a device failure, we may try the same device - # again for the next image. That's OK -- it may give a user with an - # intermittent drive some indication of such. - my ($device, $err) = try_open_device($image->{label}, $timestamp); - $check->(defined $device, "Could not open device: $err"); + my $dump = shift @{$plan->{'dumps'}}; + if (!$dump) { + return $steps->{'quit'}->(); + } - # Now get the header from the device - $header = $device->seek_file($image->{filenum}); - $check->(defined $header, - "Could not seek to file $image->{filenum} of volume $image->{label}: " . - $device->error_or_status()); + $steps->{'check_dumpfile'}->($dump); + }; - # Make sure that the on-device header matches what the logfile - # told us we'd find. + step quit => sub { + my ($err) = @_; - my $volume_part = $header->{partnum}; - if ($volume_part == 0) { - $volume_part = 1; - } + if ($err) { + $exit_code = 1; + print STDERR $err, "\n"; + return $clerk->quit(finished_cb => $finished_cb); + } - if ($image->{timestamp} ne $header->{datestamp} || - $image->{hostname} ne $header->{name} || - $image->{diskname} ne $header->{disk} || - $image->{level} != $header->{dumplevel} || - $image->{partnum} != $volume_part) { - printf("Volume image is %s:%s datestamp %s level %s part %s\n", - $header->{name}, $header->{disk}, $header->{datestamp}, - $header->{dumplevel}, $volume_part); - $check->(0, sprintf("Details of dump at file %d of volume %s do not match logfile", - $image->{filenum}, $image->{label})); - } - - # get the validation application pipeline that will process this dump. - (my $pipeline, $current_command) = open_validation_app($image, $header); - - # send the datastream from the device straight to the application - my $queue_fd = Amanda::Device::queue_fd_t->new(fileno($pipeline)); - my $read_ok = $device->read_to_fd($queue_fd); - $check->($device->status() == $DEVICE_STATUS_SUCCESS, - "Error reading device: " . $device->error_or_status()); - # if we make it here, the device was ok, but the read perhaps wasn't - if (!$read_ok) { - my $errmsg = $queue_fd->{errmsg}; - if (defined $errmsg && length($errmsg) > 0) { - $check->($read_ok, "Error writing data to validation command: $errmsg"); + if ($all_success) { + print "All images successfully validated\n"; } else { - $check->($read_ok, "Error writing data to validation command: Unknown reason"); + print "Some images failed to be correclty validated.\n"; + $exit_code = 1; } - } -} - -if (defined $reservation) { - my $release = make_cb(start => sub { - $reservation->release(finished_cb => sub { - Amanda::MainLoop::quit()}); - }); - Amanda::MainLoop::call_later($release); - Amanda::MainLoop::run(); -} - -# clean up -close_validation_app($current_command); -close_device(); - -if ($all_success) { - Amanda::Debug::debug("All images successfully validated"); - print "All images successfully validated\n"; -} else { - Amanda::Debug::debug("Some images failed to be correclty validated"); - print "Some images failed to be correclty validated.\n"; + return $clerk->quit(finished_cb => $finished_cb); + }; } +main(sub { Amanda::MainLoop::quit(); }); +Amanda::MainLoop::run(); Amanda::Util::finish_application(); -exit($all_success? 0 : 1); +exit($exit_code);