2 # Copyright (c) 2007, 2008, 2009, 2010 Zmanda, Inc. All Rights Reserved.
4 # This program is free software; you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License version 2 as published
6 # by the Free Software Foundation.
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20 use lib '@amperldir@';
29 use Amanda::Device qw( :constants );
30 use Amanda::Debug qw( :logging );
31 use Amanda::Config qw( :init :getconf config_dir_relative );
34 use Amanda::Util qw( :constants );
36 use Amanda::Recovery::Clerk;
37 use Amanda::Recovery::Scan;
38 use Amanda::Recovery::Planner;
39 use Amanda::Constants;
40 use Amanda::DB::Catalog;
43 use Amanda::Xfer qw( :constants );
47 USAGE: amcheckdump [ --timestamp|-t timestamp ] [-o configoption]* <conf>
48 amcheckdump validates Amanda dump images by reading them from storage
49 volume(s), and verifying archive integrity if the proper tool is locally
50 available. amcheckdump does not actually compare the data located in the image
51 to anything; it just validates that the archive stream is valid.
53 config - The Amanda configuration name to use.
54 -t timestamp - The run of amdump or amflush to check. By default, check
55 the most recent dump; if this parameter is specified,
56 check the most recent dump matching the given
58 -o configoption - see the CONFIGURATION OVERRIDE section of amanda(8)
63 ## Application initialization
65 Amanda::Util::setup_application("amcheckdump", "server", $CONTEXT_CMDLINE);
71 my $config_overrides = new_config_overrides($#ARGV+1);
73 Getopt::Long::Configure(qw(bundling));
75 'timestamp|t=s' => \$opt_timestamp,
76 'verbose|v' => \$opt_verbose,
77 'help|usage|?' => \&usage,
78 'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
81 usage() if (@ARGV < 1);
83 my $timestamp = $opt_timestamp;
85 my $config_name = shift @ARGV;
86 set_config_overrides($config_overrides);
87 config_init($CONFIG_INIT_EXPLICIT_NAME, $config_name);
88 my ($cfgerr_level, @cfgerr_errors) = config_errors();
89 if ($cfgerr_level >= $CFGERR_WARNINGS) {
90 config_print_errors();
91 if ($cfgerr_level >= $CFGERR_ERRORS) {
92 die("errors processing config file");
96 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
99 package Amanda::Interactive::amcheckdump;
100 use POSIX qw( :errno_h );
101 use Amanda::MainLoop qw( :GIOCondition );
103 @ISA = qw( Amanda::Interactive );
110 return bless ($self, $class);
116 if ($self->{'input_src'}) {
117 $self->{'input_src'}->remove();
118 $self->{'input_src'} = undef;
127 my $message = $params{'message'};
128 my $label = $params{'label'};
129 my $err = $params{'err'};
130 my $chg_name = $params{'chg_name'};
134 my $n_read = POSIX::read(0, $b, 1);
135 if (!defined $n_read) {
136 return if ($! == EINTR);
138 return $params{'finished_cb'}->(
139 Amanda::Changer::Error->new('fatal',
140 message => "Fail to read from stdin"));
141 } elsif ($n_read == 0) {
143 return $params{'finished_cb'}->(
144 Amanda::Changer::Error->new('fatal',
145 message => "Aborted by user"));
153 return $params{'finished_cb'}->(undef, $line);
158 print STDERR "$err\n";
159 print STDERR "Insert volume labeled '$label' in $chg_name\n";
160 print STDERR "and press enter, or ^D to abort.\n";
162 $self->{'input_src'} = Amanda::MainLoop::fd_source(0, $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
163 $self->{'input_src'}->set_callback($data_in);
167 package main::Feedback;
169 use Amanda::Recovery::Clerk;
170 use base 'Amanda::Recovery::Clerk::Feedback';
171 use Amanda::MainLoop;
175 my ($chg, $dev_name) = @_;
179 dev_name => $dev_name,
183 sub clerk_notif_part {
185 my ($label, $filenum, $header) = @_;
187 print STDERR "Reading volume $label file $filenum\n";
190 sub clerk_notif_holding {
192 my ($filename, $header) = @_;
194 print STDERR "Reading '$filename'\n";
199 # Given a dumpfile_t, figure out the command line to validate, specified
201 sub find_validation_command {
206 # We base the actual archiver on our own table
207 my $program = uc(basename($header->{program}));
209 my $validation_program;
211 if ($program ne "APPLICATION") {
212 my %validation_programs = (
213 "STAR" => [ $Amanda::Constants::STAR, qw(-t -f -) ],
214 "DUMP" => [ $Amanda::Constants::RESTORE, qw(tbf 2 -) ],
215 "VDUMP" => [ $Amanda::Constants::VRESTORE, qw(tf -) ],
216 "VXDUMP" => [ $Amanda::Constants::VXRESTORE, qw(tbf 2 -) ],
217 "XFSDUMP" => [ $Amanda::Constants::XFSRESTORE, qw(-t -v silent -) ],
218 "TAR" => [ $Amanda::Constants::GNUTAR, qw(--ignore-zeros -tf -) ],
219 "GTAR" => [ $Amanda::Constants::GNUTAR, qw(--ignore-zeros -tf -) ],
220 "GNUTAR" => [ $Amanda::Constants::GNUTAR, qw(--ignore-zeros -tf -) ],
221 "SMBCLIENT" => [ $Amanda::Constants::GNUTAR, qw(--ignore-zeros -tf -) ],
224 if (!exists $validation_programs{$program}) {
225 debug("Unknown program '$program' in header; no validation to perform");
228 return $validation_programs{$program};
231 if (!defined $header->{application}) {
232 warning("Application not set");
235 my $program_path = $Amanda::Paths::APPLICATION_DIR . "/" .
236 $header->{application};
237 if (!-x $program_path) {
238 debug("Application '" . $header->{application}.
239 "($program_path)' not available on the server");
242 return [ $program_path, "validate" ];
248 my ($finished_cb) = @_;
260 my $steps = define_steps
261 cb_ref => \$finished_cb;
264 # set up the tapelist
265 my $tapelist_file = config_dir_relative(getconf($CNF_TAPELIST));
266 $tapelist = Amanda::Tapelist->new($tapelist_file);
269 $timestamp = $opt_timestamp;
270 $timestamp = Amanda::DB::Catalog::get_latest_write_timestamp()
271 unless defined $opt_timestamp;
273 # make an interactivity plugin
274 $interactive = Amanda::Interactive::amcheckdump->new();
277 $chg = Amanda::Changer->new();
278 return $steps->{'quit'}->($chg)
279 if $chg->isa("Amanda::Changer::Error");
282 $scan = Amanda::Recovery::Scan->new(
284 interactive => $interactive);
285 return $steps->{'quit'}->($scan)
286 if $scan->isa("Amanda::Changer::Error");
289 $clerk = Amanda::Recovery::Clerk->new(
290 feedback => main::Feedback->new($chg),
294 my $spec = Amanda::Cmdline::dumpspec_t->new(undef, undef, undef, undef, $timestamp);
295 Amanda::Recovery::Planner::make_plan(
296 dumpspecs => [ $spec ],
298 plan_cb => $steps->{'plan_cb'});
301 step plan_cb => sub {
302 (my $err, $plan) = @_;
303 $steps->{'quit'}->($err) if $err;
305 my @tapes = $plan->get_volume_list();
306 my @holding = $plan->get_holding_file_list();
307 if (!@tapes && !@holding) {
308 print "Could not find any matching dumps.\n";
309 return $steps->{'quit'}->();
313 printf("You will need the following volume%s: %s\n", (@tapes > 1) ? "s" : "",
314 join(", ", map { $_->{'label'} } @tapes));
317 printf("You will need the following holding file%s: %s\n", (@tapes > 1) ? "s" : "",
318 join(", ", @holding));
321 # nothing else is going on right now, so a blocking "Press enter.." is OK
322 print "Press enter when ready\n";
325 my $dump = shift @{$plan->{'dumps'}};
327 return $steps->{'quit'}->("No backup written on timestamp $timestamp.");
330 $steps->{'check_dumpfile'}->($dump);
333 step check_dumpfile => sub {
336 print "Validating image " . $dump->{hostname} . ":" .
337 $dump->{diskname} . " dumped " . $dump->{dump_timestamp} . " level ".
339 if ($dump->{'nparts'} > 1) {
340 print " ($dump->{nparts} parts)";
345 $clerk->get_xfer_src(
347 xfer_src_cb => $steps->{'xfer_src_cb'});
350 step xfer_src_cb => sub {
351 my ($errs, $hdr, $xfer_src, $directtcp_supported) = @_;
352 return $steps->{'quit'}->(join("; ", @$errs)) if $errs;
354 # set up any filters that need to be applied; decryption first
356 if ($hdr->{'encrypted'}) {
357 if ($hdr->{'srv_encrypt'}) {
359 Amanda::Xfer::Filter::Process->new(
360 [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0, 0);
361 } elsif ($hdr->{'clnt_encrypt'}) {
363 Amanda::Xfer::Filter::Process->new(
364 [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0, 0);
366 return failure("could not decrypt encrypted dump: no program specified",
370 $hdr->{'encrypted'} = 0;
371 $hdr->{'srv_encrypt'} = '';
372 $hdr->{'srv_decrypt_opt'} = '';
373 $hdr->{'clnt_encrypt'} = '';
374 $hdr->{'clnt_decrypt_opt'} = '';
375 $hdr->{'encrypt_suffix'} = 'N';
378 if ($hdr->{'compressed'}) {
379 # need to uncompress this file
381 if ($hdr->{'srvcompprog'}) {
382 # TODO: this assumes that srvcompprog takes "-d" to decrypt
384 Amanda::Xfer::Filter::Process->new(
385 [ $hdr->{'srvcompprog'}, "-d" ], 0, 0);
386 } elsif ($hdr->{'clntcompprog'}) {
387 # TODO: this assumes that clntcompprog takes "-d" to decrypt
389 Amanda::Xfer::Filter::Process->new(
390 [ $hdr->{'clntcompprog'}, "-d" ], 0, 0);
393 Amanda::Xfer::Filter::Process->new(
394 [ $Amanda::Constants::UNCOMPRESS_PATH,
395 $Amanda::Constants::UNCOMPRESS_OPT ], 0, 0);
399 $hdr->{'compressed'} = 0;
400 $hdr->{'uncompress_cmd'} = '';
403 # and set up the validation command as a filter element, since
404 # we need to throw out its stdout
405 my $argv = find_validation_command($hdr);
407 push @filters, Amanda::Xfer::Filter::Process->new($argv, 0, 0);
410 # we always throw out stdout
411 my $xfer_dest = Amanda::Xfer::Dest::Null->new(0);
413 my $xfer = Amanda::Xfer->new([ $xfer_src, @filters, $xfer_dest ]);
414 $xfer->start($steps->{'handle_xmsg'});
415 $clerk->start_recovery(
417 recovery_cb => $steps->{'recovery_cb'});
420 step handle_xmsg => sub {
421 my ($src, $msg, $xfer) = @_;
423 $clerk->handle_xmsg($src, $msg, $xfer);
424 if ($msg->{'type'} == $XMSG_INFO) {
425 Amanda::Debug::info($msg->{'message'});
426 } elsif ($msg->{'type'} == $XMSG_ERROR) {
427 push @xfer_errs, $msg->{'message'};
431 step recovery_cb => sub {
434 # distinguish device errors from validation errors
435 if (@{$params{'errors'}}) {
436 print STDERR "While reading from volumes:\n";
437 print STDERR "$_\n" for @{$params{'errors'}};
438 return $steps->{'quit'}->("validation aborted");
442 print STDERR "Validation errors:\n";
443 print STDERR "$_\n" for @xfer_errs;
447 my $dump = shift @{$plan->{'dumps'}};
449 return $steps->{'quit'}->();
452 $steps->{'check_dumpfile'}->($dump);
460 print STDERR $err, "\n";
461 return $clerk->quit(finished_cb => $finished_cb);
465 print "All images successfully validated\n";
467 print "Some images failed to be correclty validated.\n";
471 return $clerk->quit(finished_cb => $finished_cb);
475 main(sub { Amanda::MainLoop::quit(); });
476 Amanda::MainLoop::run();
477 Amanda::Util::finish_application();