#! @PERL@
-# Copyright (c) 2005-2008 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
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
-# Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300
+# Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
use lib '@amperldir@';
use strict;
+use warnings;
use File::Basename;
use Getopt::Long;
+use IPC::Open3;
+use Symbol;
use Amanda::Device qw( :constants );
use Amanda::Debug qw( :logging );
use Amanda::Config qw( :init :getconf config_dir_relative );
+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;
-
-# Have all images been verified successfully so far?
-my $all_success = 1;
+use Amanda::DB::Catalog;
+use Amanda::Cmdline;
+use Amanda::MainLoop;
+use Amanda::Xfer qw( :constants );
sub usage {
print <<EOF;
exit(1);
}
-# Find the most recent logfile name matching the given timestamp
-sub find_logfile_name($) {
- my $timestamp = shift @_;
- my $rval;
- my $config_dir = config_dir_relative(getconf($CNF_LOGDIR));
- # First try log.$datestamp.$seq
- for (my $seq = 0;; $seq ++) {
- my $logfile = sprintf("%s/log.%s.%u", $config_dir, $timestamp, $seq);
- if (-f $logfile) {
- $rval = $logfile;
- } else {
- last;
- }
- }
- return $rval if defined $rval;
-
- # Next try log.$datestamp.amflush
- $rval = sprintf("%s/log.%s.amflush", $config_dir, $timestamp);
+## Application initialization
- return $rval if -f $rval;
+Amanda::Util::setup_application("amcheckdump", "server", $CONTEXT_CMDLINE);
- # Finally try log.datestamp.
- $rval = sprintf("%s/log.%s.amflush", $config_dir, $timestamp);
-
- return $rval if -f $rval;
+my $exit_code = 0;
- # No dice.
- return undef;
-}
+my $opt_timestamp;
+my $opt_verbose = 0;
+my $config_overrides = new_config_overrides($#ARGV+1);
-## Device management
+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 $changer;
-my $reservation;
-my $current_device;
-my $current_device_label;
+usage() if (@ARGV < 1);
-sub find_next_device {
- my $label = shift;
- my $reset_done_cb;
- my $find_done_cb;
- my ($slot, $tapedev);
+my $timestamp = $opt_timestamp;
- # if the changer hasn't been created yet, set it up
- if (!$changer) {
- $changer = Amanda::Changer->new();
+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");
}
+}
- my $load_sub = sub {
- my ($err) = @_;
- if ($err) {
- print STDERR $err, "\n";
- exit 1;
- }
-
- $changer->load(
- label => $label,
- res_cb => sub {
- (my $err, $reservation) = @_;
- if ($err) {
- print STDERR $err, "\n";
- exit 1;
- }
- Amanda::MainLoop::quit();
- },
- );
- };
+Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
- if (defined $reservation) {
- $reservation->release(finished_cb => $load_sub);
- } else {
- $load_sub->(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 );
- # let the mainloop run until the find is done. This is a temporary
- # hack until all of amcheckdump is event-based.
- Amanda::MainLoop::run();
+sub new {
+ my $class = shift;
- return $reservation->{device_name};
+ my $self = {
+ input_src => undef};
+ return bless ($self, $class);
}
-# Try to open a device containing a volume with the given label. Returns undef
-# if there is a problem.
-sub try_open_device {
- my ($label) = @_;
-
- # can we use the same device as last time?
- if ($current_device_label eq $label) {
- return $current_device;
- }
-
- # nope -- get rid of that device
- close_device();
-
- my $device_name = find_next_device($label);
- if ( !$device_name ) {
- print "Could not find a device for label '$label'.\n";
- return undef;
- }
+sub abort() {
+ my $self = shift;
- my $device = Amanda::Device->new($device_name);
- if ($device->status() != $DEVICE_STATUS_SUCCESS) {
- print "Could not open device $device_name: ",
- $device->error(), ".\n";
- return undef;
- }
- if (!$device->configure(1)) {
- print "Could not configure device $device_name: ",
- $device->error(), ".\n";
- return undef;
+ if ($self->{'input_src'}) {
+ $self->{'input_src'}->remove();
+ $self->{'input_src'} = undef;
}
+}
- my $label_status = $device->read_label();
- if ($label_status != $DEVICE_STATUS_SUCCESS) {
- if ($device->error() ) {
- print "Could not read device $device_name: ",
- $device->error(), ".\n";
+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 {
- print "Could not read device $device_name: one of ",
- join(", ", DevicestatusFlags_to_strings($label_status)),
- "\n";
+ $buffer .= $b;
+ if ($b eq "\n") {
+ my $line = $buffer;
+ chomp $line;
+ $buffer = "";
+ $self->abort();
+ return $params{'finished_cb'}->(undef, $line);
+ }
}
- return undef;
- }
+ };
- if ($device->volume_label() ne $label) {
- printf("Labels do not match: Expected '%s', but the device contains '%s'.\n",
- $label, $device->volume_label());
- return undef;
- }
+ print STDERR "$err\n";
+ print STDERR "Insert volume labeled '$label' in $chg_name\n";
+ print STDERR "and press enter, or ^D to abort.\n";
- if (!$device->start($ACCESS_READ, undef, undef)) {
- printf("Error reading device %s: %s.\n", $device_name,
- $device->error_message());
- return undef;
- }
+ $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;
+};
- $current_device = $device;
- $current_device_label = $device->volume_label();
+package main::Feedback;
- return $device;
-}
+use Amanda::Recovery::Clerk;
+use base 'Amanda::Recovery::Clerk::Feedback';
+use Amanda::MainLoop;
-sub close_device {
- $current_device = undef;
- $current_device_label = undef;
-}
+sub new {
+ my $class = shift;
+ my ($chg, $dev_name) = @_;
-## Validation application
-
-my ($current_validation_pid, $current_validation_pipeline, $current_validation_image);
-
-# 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
- print "Continuing with previously started validation process.\n";
- return $current_validation_pipeline;
- }
+ return bless {
+ chg => $chg,
+ dev_name => $dev_name,
+ }, $class;
+}
- # nope, new image. close the previous pipeline
- close_validation_app();
-
- my $validation_command = find_validation_command($header);
- print " using '$validation_command'.\n";
- $current_validation_pid = open($current_validation_pipeline, "|-", $validation_command);
-
- if (!$current_validation_pid) {
- print "Can't execute validation command: $!\n";
- undef $current_validation_pid;
- undef $current_validation_pipeline;
- return undef;
- }
+sub clerk_notif_part {
+ my $self = shift;
+ my ($label, $filenum, $header) = @_;
- $current_validation_image = $image;
- return $current_validation_pipeline;
+ print STDERR "Reading volume $label file $filenum\n";
}
-# 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 {
- if (!defined($current_validation_pipeline)) {
- return;
- }
-
- # first close the applications standard input to signal it to stop
- if (!close($current_validation_pipeline)) {
- my $exit_value = $? >> 8;
- print "Validation process returned $exit_value (full status $?)\n";
- $all_success = 0; # flag this as a failure
- }
+sub clerk_notif_holding {
+ my $self = shift;
+ my ($filename, $header) = @_;
- $current_validation_pid = undef;
- $current_validation_pipeline = undef;
- $current_validation_image = undef;
+ print STDERR "Reading '$filename'\n";
}
-# Given a dumpfile_t, figure out the command line to validate.
+package main;
+
+# Given a dumpfile_t, figure out the command line to validate, specified
+# as an argv array
sub find_validation_command {
my ($header) = @_;
- # We base the actual archiver on our own table, but just trust
- # whatever is listed as the decrypt/uncompress commands.
+ my @result = ();
+
+ # 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 (!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}) {
- print STDERR "Application not set; ".
- "Will send dumps to /dev/null instead.";
- $validation_program = "cat > /dev/null";
+ 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) {
- print STDERR "Application '" , $header->{application},
- "($program_path)' not available on the server; ".
- "Will send dumps to /dev/null instead.";
- $validation_program = "cat > /dev/null";
- } else {
- $validation_program = $program_path . " validate";
- }
+ return [ $program_path, "validate" ];
}
}
- if (!defined $validation_program) {
- print STDERR "Could not determine validation for dumper $program; ".
- "Will send dumps to /dev/null instead.";
- $validation_program = "cat > /dev/null";
- } else {
- # This is to clean up any extra output the program doesn't read.
- $validation_program .= " > /dev/null && cat > /dev/null";
- }
-
- my $cmdline = "";
- if (defined $header->{decrypt_cmd} &&
- length($header->{decrypt_cmd}) > 0) {
- $cmdline .= $header->{decrypt_cmd};
- }
- if (defined $header->{uncompress_cmd} &&
- length($header->{uncompress_cmd}) > 0) {
- $cmdline .= $header->{uncompress_cmd};
- }
- $cmdline .= $validation_program;
-
- return $cmdline;
}
-## Application initialization
-
-Amanda::Util::setup_application("amcheckdump", "server", $CONTEXT_CMDLINE);
+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 $timestamp = undef;
-my $config_overwrites = new_config_overwrites($#ARGV+1);
+ step plan_cb => sub {
+ (my $err, $plan) = @_;
+ $steps->{'quit'}->($err) if $err;
-Getopt::Long::Configure(qw(bundling));
-GetOptions(
- 'timestamp|t=s' => \$timestamp,
- 'help|usage|?' => \&usage,
- 'o=s' => sub { add_config_overwrite_opt($config_overwrites, $_[1]); },
-) or usage();
+ 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'}->();
+ }
-usage() if (@ARGV < 1);
+ 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));
+ }
-my $timestamp_argument = 0;
-if (defined $timestamp) { $timestamp_argument = 1; }
+ # nothing else is going on right now, so a blocking "Press enter.." is OK
+ print "Press enter when ready\n";
+ <STDIN>;
-my $config_name = shift @ARGV;
-config_init($CONFIG_INIT_EXPLICIT_NAME, $config_name);
-apply_config_overwrites($config_overwrites);
-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");
- }
-}
+ my $dump = shift @{$plan->{'dumps'}};
+ if (!$dump) {
+ return $steps->{'quit'}->("No backup written on timestamp $timestamp.");
+ }
-Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
+ $steps->{'check_dumpfile'}->($dump);
+ };
-# 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;
- }
+ step check_dumpfile => sub {
+ my ($dump) = @_;
- # extract the datestamp from the dump log
- open (AMDUMP, "<$logfile") || die();
- while(<AMDUMP>) {
- if (/^amdump: starttime (\d*)$/) {
- $timestamp = $1;
+ print "Validating image " . $dump->{hostname} . ":" .
+ $dump->{diskname} . " dumped " . $dump->{dump_timestamp} . " level ".
+ $dump->{level};
+ if ($dump->{'nparts'} > 1) {
+ print " ($dump->{nparts} parts)";
}
- elsif (/^amflush: starttime (\d*)$/) {
- $timestamp = $1;
- }
- elsif (/^planner: timestamp (\d*)$/) {
- $timestamp = $1;
+ print "\n";
+
+ @xfer_errs = ();
+ $clerk->get_xfer_src(
+ dump => $dump,
+ xfer_src_cb => $steps->{'xfer_src_cb'});
+ };
+
+ 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);
+ }
+
+ $hdr->{'encrypted'} = 0;
+ $hdr->{'srv_encrypt'} = '';
+ $hdr->{'srv_decrypt_opt'} = '';
+ $hdr->{'clnt_encrypt'} = '';
+ $hdr->{'clnt_decrypt_opt'} = '';
+ $hdr->{'encrypt_suffix'} = 'N';
}
- }
- 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";
+ 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);
+ }
+
+ # adjust the header
+ $hdr->{'compressed'} = 0;
+ $hdr->{'uncompress_cmd'} = '';
}
- 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);
-}
+ # 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);
+ }
-# filter only "ok" dumps, removing partial and failed dumps
-@images = Amanda::Logfile::dumps_match([@images],
- undef, undef, undef, undef, 1);
+ # we always throw out stdout
+ my $xfer_dest = Amanda::Xfer::Dest::Null->new(0);
-if (!@images) {
- if ($timestamp_argument) {
- print STDERR "No backup written on timestamp $timestamp.\n";
- } else {
- print STDERR "No backup written on latest run.\n";
- }
- exit 1;
-}
+ 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'});
+ };
-# Find unique tapelist, using a hash to filter duplicate tapes
-my %tapes = map { ($_->{label}, undef) } @images;
-my @tapes = sort { $a cmp $b } keys %tapes;
+ step handle_xmsg => sub {
+ my ($src, $msg, $xfer) = @_;
-if (!@tapes) {
- print STDERR "Could not find any matching dumps.\n";
- exit 1;
-}
+ $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'};
+ }
+ };
-printf("You will need the following tape%s: %s\n", (@tapes > 1) ? "s" : "",
- join(", ", @tapes));
+ step recovery_cb => sub {
+ my %params = @_;
-# Now loop over the images, verifying each one.
+ # 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) {
- # Currently, L_PART results will be n/x, n >= 1, x >= -1
- # In the past (before split dumps), L_PART could be --
- # Headers can give partnum >= 0, where 0 means not split.
- my $logfile_part = 1; # assume this is not a split dump
- if ($image->{partnum} =~ m$(\d+)/(-?\d+)$) {
- $logfile_part = $1;
- }
+ if (@xfer_errs) {
+ print STDERR "Validation errors:\n";
+ print STDERR "$_\n" for @xfer_errs;
+ $all_success = 0;
+ }
- printf("Validating image %s:%s datestamp %s level %s part %s on tape %s file #%d\n",
- $image->{hostname}, $image->{diskname}, $image->{timestamp},
- $image->{level}, $logfile_part, $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 = try_open_device($image->{label});
- if (!defined $device) {
- # error message already printed
- $all_success = 0;
- next IMAGE;
- }
+ my $dump = shift @{$plan->{'dumps'}};
+ if (!$dump) {
+ return $steps->{'quit'}->();
+ }
- # Now get the header from the device
- my $header = $device->seek_file($image->{filenum});
- if (!defined $header) {
- printf("Could not seek to file %d of volume %s.\n",
- $image->{filenum}, $image->{label});
- $all_success = 0;
- next IMAGE;
- }
+ $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} ||
- $logfile_part != $volume_part) {
- printf("Details of dump at file %d of volume %s do not match logfile.\n",
- $image->{filenum}, $image->{label});
- $all_success = 0;
- next IMAGE;
- }
-
- # get the validation application pipeline that will process this dump.
- my $pipeline = 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));
- if (!$device->read_to_fd($queue_fd)) {
- print "Error reading device or writing data to validation command.\n";
- $all_success = 0;
- next IMAGE;
- }
-}
+ if ($all_success) {
+ print "All images successfully validated\n";
+ } else {
+ print "Some images failed to be correclty validated.\n";
+ $exit_code = 1;
+ }
-if (defined $reservation) {
- $reservation->release();
+ return $clerk->quit(finished_cb => $finished_cb);
+ };
}
-# clean up
-close_validation_app();
-close_device();
-
-exit($all_success? 0 : 1);
+main(sub { Amanda::MainLoop::quit(); });
+Amanda::MainLoop::run();
+Amanda::Util::finish_application();
+exit($exit_code);