#! @PERL@
+# 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
+# by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# 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. 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( :running_as_flags );
-use Amanda::Tapefile;
+use Amanda::Util qw( :constants );
use Amanda::Changer;
-
-# Have all images been verified successfully so far?
-my $all_success = 1;
+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;
+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;
+## Application initialization
- # Next try log.$datestamp.amflush
- $rval = sprintf("%s/log.%s.amflush", $config_dir, $timestamp);
+Amanda::Util::setup_application("amcheckdump", "server", $CONTEXT_CMDLINE);
- return $rval if -f $rval;
+my $exit_code = 0;
- # Finally try log.datestamp.
- $rval = sprintf("%s/log.%s.amflush", $config_dir, $timestamp);
-
- return $rval if -f $rval;
+my $opt_timestamp;
+my $opt_verbose = 0;
+my $config_overrides = new_config_overrides($#ARGV+1);
- # No dice.
- return undef;
-}
+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();
-## Device management
+usage() if (@ARGV < 1);
-my $changer_init_done = 0;
-my $current_device;
-my $current_device_label;
+my $timestamp = $opt_timestamp;
-sub find_next_device {
- my $label = shift;
- if (getconf_seen($CNF_TPCHANGER)) {
- # We're using a changer script.
- if (!$changer_init_done) {
- my $error = (Amanda::Changer::reset())[0];
- critical($error) if $error;
- $changer_init_done = 1;
- }
- my ($error, $slot, $tapedev) = Amanda::Changer::find($label);
- if ($error) {
- critical("Error operating changer: $error.");
- } elsif ($slot eq "<none>") {
- critical("Could not find tape label $label in changer.");
- } else {
- return $tapedev;
- }
- } else {
- # The user is changing tapes for us.
- my $device_name = getconf($CNF_TAPEDEV);
- printf("Insert volume with label %s in device %s and press ENTER: ",
- $label, $device_name);
- <>;
- return $device_name;
+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");
}
}
-# 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) = @_;
+Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
- # can we use the same device as last time?
- if ($current_device_label eq $label) {
- return $current_device;
- }
+# Interactive package
+package Amanda::Interactive::amcheckdump;
+use POSIX qw( :errno_h );
+use Amanda::MainLoop qw( :GIOCondition );
+use vars qw( @ISA );
+@ISA = qw( Amanda::Interactive );
- # nope -- get rid of that device
- close_device();
+sub new {
+ my $class = shift;
- my $device_name = find_next_device($label);
- if ( !$device_name ) {
- print "Could not find a device for label '$label'.\n";
- return undef;
- }
+ my $self = {
+ input_src => undef};
+ return bless ($self, $class);
+}
+
+sub abort() {
+ my $self = shift;
- my $device = Amanda::Device->new($device_name);
- if ( !$device ) {
- print "Could not open '$device_name'.\n";
- return undef;
+ if ($self->{'input_src'}) {
+ $self->{'input_src'}->remove();
+ $self->{'input_src'} = undef;
}
+}
- $device->set_startup_properties_from_config();
+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 $label_status = $device->read_label();
- if ($label_status != $READ_LABEL_STATUS_SUCCESS) {
- print "Could not read device $device_name: one of ",
- join(", ", ReadLabelStatusFlags_to_strings($label_status)),
- "\n";
- 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->{volume_label} ne $label) {
- printf("Labels do not match: Expected '%s', but the device contains '%s'.\n",
- $label, $device->{volume_label});
- 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;
+};
- if (!$device->start($ACCESS_READ, undef, undef)) {
- printf("Error reading device %s.\n", $device_name);
- return undef;
- }
+package main::Feedback;
- $current_device = $device;
- $current_device_label = $device->{volume_label};
+use Amanda::Recovery::Clerk;
+use base 'Amanda::Recovery::Clerk::Feedback';
+use Amanda::MainLoop;
- return $device;
-}
+sub new {
+ my $class = shift;
+ my ($chg, $dev_name) = @_;
-sub close_device {
- $current_device = undef;
- $current_device_label = undef;
+ return bless {
+ chg => $chg,
+ dev_name => $dev_name,
+ }, $class;
}
-## 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;
- }
+sub clerk_notif_part {
+ my $self = shift;
+ my ($label, $filenum, $header) = @_;
- # 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;
- }
-
- $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;
- my %validation_programs = (
- "DUMP" => "@RESTORE@ tbf 2 -",
- "VDUMP" => "@VRESTORE@ tf -",
- "VXDUMP" => "@VXRESTORE@ tbf 2 -",
- "XFSDUMP" => "@XFSRESTORE@ -t -v silent -",
- "TAR" => "@GNUTAR@ tf -",
- "GTAR" => "@GNUTAR@ tf -",
- "GNUTAR" => "@GNUTAR@ tf -",
- "SMBCLIENT" => "@SAMBA_CLIENT@ tf -",
- );
-
- $validation_program = $validation_programs{$program};
- if (!defined $validation_program) {
- warning("Could not determine validation for dumper $program; ".
- "Will send dumps to /dev/null instead.");
- $validation_program = "cat > /dev/null";
+
+ if ($program ne "APPLICATION") {
+ my %validation_programs = (
+ "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,
+ );
+ if (!exists $validation_programs{$program}) {
+ debug("Unknown program '$program' in header; no validation to perform");
+ return undef;
+ }
+ return $validation_programs{$program};
+
} 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};
+ if (!defined $header->{application}) {
+ 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 {
+ return [ $program_path, "validate" ];
+ }
}
- $cmdline .= $validation_program;
-
- return $cmdline;
}
-## Application initialization
-
-Amanda::Util::setup_application("amcheckdump", "server", "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'});
+ };
+
+ step plan_cb => sub {
+ (my $err, $plan) = @_;
+ $steps->{'quit'}->($err) if $err;
+
+ 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'}->();
+ }
-my $timestamp = undef;
-my $config_overwrites = new_config_overwrites($#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));
+ }
-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();
+ # nothing else is going on right now, so a blocking "Press enter.." is OK
+ print "Press enter when ready\n";
+ <STDIN>;
-usage() if (@ARGV < 1);
-
-my $config_name = shift @ARGV;
-if (!config_init($CONFIG_INIT_EXPLICIT_NAME |
- $CONFIG_INIT_FATAL, $config_name)) {
- critical('errors processing config file "' .
- Amanda::Config::get_config_filename() . '"');
-}
-apply_config_overwrites($config_overwrites);
+ 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);
+ };
-# Read the tape list.
-my $tl = Amanda::Tapefile::read_tapelist(config_dir_relative(getconf($CNF_TAPELIST)));
-
-# 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 any dump log file.\n";
- exit;
- }
+ step check_dumpfile => sub {
+ my ($dump) = @_;
- # extract the datestamp from the dump log
- open (AMDUMP, "<$logfile") || critical();
- while(<AMDUMP>) {
- if (/^amdump: starttime (\d*)$/) {
- $timestamp = $1;
- }
- elsif (/^amflush: 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 (/^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 @logfiles =
- grep { $_ =~ /^log\.$timestamp(?:\.[0-9]+|\.amflush)?$/ }
- Amanda::Logfile::find_log();
+ 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'} = '';
+ }
-if (!@logfiles) {
- critical("Can't find any logfiles with timestamp $timestamp.");
-}
+ # 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);
+ }
-# compile a list of *all* dumps in those logfiles
-my $logfile_dir = config_dir_relative(getconf($CNF_LOGDIR));
-my @images;
-for my $logfile (@logfiles) {
- push @images, Amanda::Logfile::search_logfile(undef, $timestamp,
- "$logfile_dir/$logfile", 1);
-}
+ # we always throw out stdout
+ my $xfer_dest = Amanda::Xfer::Dest::Null->new(0);
-# filter only "ok" dumps, removing partial and failed dumps
-@images = Amanda::Logfile::dumps_match([@images],
- undef, undef, undef, undef, 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'});
+ };
-if (!@images) {
- critical("Could not find any matching dumps");
-}
+ step handle_xmsg => sub {
+ my ($src, $msg, $xfer) = @_;
-# Find unique tapelist, using a hash to filter duplicate tapes
-my %tapes = map { ($_->{label}, undef) } @images;
-my @tapes = sort { $a cmp $b } keys %tapes;
+ $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'};
+ }
+ };
-if (!@tapes) {
- critical("Could not find any matching dumps");
-}
+ step recovery_cb => sub {
+ my %params = @_;
-printf("You will need the following tape%s: %s\n", (@tapes > 1) ? "s" : "",
- join(", ", @tapes));
+ # 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");
+ }
-# Now loop over the images, verifying each one.
+ if (@xfer_errs) {
+ print STDERR "Validation errors:\n";
+ print STDERR "$_\n" for @xfer_errs;
+ $all_success = 0;
+ }
-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;
- }
+ my $dump = shift @{$plan->{'dumps'}};
+ if (!$dump) {
+ return $steps->{'quit'}->();
+ }
- 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;
- }
+ $steps->{'check_dumpfile'}->($dump);
+ };
- # 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;
- }
+ step quit => sub {
+ my ($err) = @_;
- # Make sure that the on-device header matches what the logfile
- # told us we'd find.
+ if ($err) {
+ $exit_code = 1;
+ print STDERR $err, "\n";
+ return $clerk->quit(finished_cb => $finished_cb);
+ }
- my $volume_part = $header->{partnum};
- if ($volume_part == 0) {
- $volume_part = 1;
- }
+ if ($all_success) {
+ print "All images successfully validated\n";
+ } else {
+ print "Some images failed to be correclty validated.\n";
+ $exit_code = 1;
+ }
- 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
- if (!$device->read_to_fd(fileno($pipeline))) {
- print "Error reading device or writing data to validation command.\n";
- $all_success = 0;
- next IMAGE;
- }
+ 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);