2 # Copyright (c) 2009-2012 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 Mathlida Ave, Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20 use lib '@amperldir@';
26 use Amanda::Device qw( :constants );
27 use Amanda::Debug qw( :logging );
28 use Amanda::Config qw( :init :getconf config_dir_relative );
29 use Amanda::Util qw( :constants );
31 use Amanda::Constants;
36 use Amanda::Xfer qw( :constants );
37 use Amanda::Recovery::Planner;
38 use Amanda::Recovery::Clerk;
39 use Amanda::Recovery::Scan;
41 # Interactivity package
42 package Amanda::Interactivity::amfetchdump;
43 use POSIX qw( :errno_h );
44 use Amanda::MainLoop qw( :GIOCondition );
46 @ISA = qw( Amanda::Interactivity );
53 return bless ($self, $class);
59 if ($self->{'input_src'}) {
60 $self->{'input_src'}->remove();
61 $self->{'input_src'} = undef;
70 my $message = $params{'message'};
71 my $label = $params{'label'};
72 my $err = $params{'err'};
73 my $chg_name = $params{'chg_name'};
77 my $n_read = POSIX::read(0, $b, 1);
78 if (!defined $n_read) {
79 return if ($! == EINTR);
81 return $params{'request_cb'}->(
82 Amanda::Changer::Error->new('fatal',
83 message => "Fail to read from stdin"));
84 } elsif ($n_read == 0) {
86 return $params{'request_cb'}->(
87 Amanda::Changer::Error->new('fatal',
88 message => "Aborted by user"));
96 return $params{'request_cb'}->(undef, $line);
101 print STDERR "$err\n";
102 print STDERR "Insert volume labeled '$label' in $chg_name\n";
103 print STDERR "and press enter, or ^D to abort.\n";
105 $self->{'input_src'} = Amanda::MainLoop::fd_source(0, $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
106 $self->{'input_src'}->set_callback($data_in);
115 Usage: amfetchdump [-c|-C|-l] [-p|-n] [-a] [-O directory] [-d device]
116 [-h|--header-file file|--header-fd fd]i
117 [--decrypt|--no-decrypt|--server-decrypt|--client-decrypt]
118 [--decompress|--no-decompress|--server-decompress|--client-decompress]
119 [-o configoption]* config
120 hostname [diskname [datestamp [hostname [diskname [datestamp ... ]]]]]
122 print STDERR "ERROR: $msg\n" if $msg;
129 Amanda::Util::setup_application("amfetchdump", "server", $CONTEXT_CMDLINE);
131 my $config_overrides = new_config_overrides($#ARGV+1);
133 my ($opt_config, $opt_no_reassembly, $opt_compress, $opt_compress_best, $opt_pipe,
134 $opt_assume, $opt_leave, $opt_blocksize, $opt_device, $opt_chdir, $opt_header,
135 $opt_header_file, $opt_header_fd, @opt_dumpspecs,
136 $opt_decrypt, $opt_server_decrypt, $opt_client_decrypt,
137 $opt_decompress, $opt_server_decompress, $opt_client_decompress);
146 debug("Arguments: " . join(' ', @ARGV));
147 Getopt::Long::Configure(qw(bundling));
149 'version' => \&Amanda::Util::version_opt,
150 'help|usage|?' => \&usage,
151 'n' => \$opt_no_reassembly,
152 'c' => \$opt_compress,
153 'C' => \$opt_compress_best,
158 'header-file=s' => \$opt_header_file,
159 'header-fd=i' => \$opt_header_fd,
160 'decrypt!' => \$opt_decrypt,
161 'server-decrypt' => \$opt_server_decrypt,
162 'client-decrypt' => \$opt_client_decrypt,
163 'decompress!' => \$opt_decompress,
164 'server-decompress' => \$opt_server_decompress,
165 'client-decompress' => \$opt_client_decompress,
166 'b=s' => \$opt_blocksize,
167 'd=s' => \$opt_device,
168 'O=s' => \$opt_chdir,
169 'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
171 usage() unless (@ARGV);
172 $opt_config = shift @ARGV;
174 if (defined $opt_compress and defined $opt_compress_best) {
175 print STDERR "Can't use -c and -C\n";
179 usage("The -b option is no longer supported; set readblocksize in the tapetype section\n" .
180 "of amanda.conf instead.")
182 usage("-l is not compatible with -c or -C")
183 if ($opt_leave and $opt_compress);
184 usage("-p is not compatible with -n")
185 if ($opt_leave and $opt_no_reassembly);
186 usage("-h, --header-file, and --header-fd are mutually incompatible")
187 if (($opt_header and ($opt_header_file or $opt_header_fd))
188 or ($opt_header_file and $opt_header_fd));
190 if (defined $opt_leave) {
191 if (defined $opt_decrypt and $opt_decrypt) {
192 print STDERR "-l is incompatible with --decrypt\n";
195 if (defined $opt_server_decrypt) {
196 print STDERR "-l is incompatible with --server-decrypt\n";
199 if (defined $opt_client_decrypt) {
200 print STDERR "-l is incompatible with --client-decrypt\n";
203 if (defined $opt_decompress and $opt_decompress) {
204 print STDERR "-l is incompatible with --decompress\n";
207 if (defined $opt_server_decompress) {
208 print STDERR "-l is incompatible with --server-decompress\n";
211 if (defined $opt_client_decompress) {
212 print STDERR "-l is incompatible with --client-decompress\n";
217 if (defined($opt_decrypt) +
218 defined($opt_server_decrypt) +
219 defined($opt_client_decrypt) > 1) {
220 print STDERR "Can't use only on of --decrypt, --no-decrypt, --server-decrypt or --client-decrypt\n";
223 if (defined($opt_decompress) +
224 defined($opt_server_decompress) +
225 defined($opt_client_decompress) > 1) {
226 print STDERR "Can't use only on of --decompress, --no-decompress, --server-decompress or --client-decompress\n";
230 if (defined($opt_compress) and
231 defined($opt_decompress) +
232 defined($opt_server_decompress) +
233 defined($opt_client_decompress) > 0) {
234 print STDERR "Can't specify -c with one of --decompress, --no-decompress, --server-decompress or --client-decompress\n";
237 if (defined($opt_compress_best) and
238 defined($opt_decompress) +
239 defined($opt_server_decompress) +
240 defined($opt_client_decompress) > 0) {
241 print STDERR "Can't specify -C with one of --decompress, --no-decompress, --server-decompress or --client-decompress\n";
245 $decompress = $ALWAYS;
247 $decrypt = $NEVER if defined $opt_leave;
248 $decrypt = $NEVER if defined $opt_decrypt and !$opt_decrypt;
249 $decrypt = $ALWAYS if defined $opt_decrypt and $opt_decrypt;
250 $decrypt = $ONLY_SERVER if defined $opt_server_decrypt;
251 $decrypt = $ONLY_CLIENT if defined $opt_client_decrypt;
253 $opt_compress = 1 if $opt_compress_best;
255 $decompress = $NEVER if defined $opt_compress;
256 $decompress = $NEVER if defined $opt_leave;
257 $decompress = $NEVER if defined $opt_decompress and !$opt_decompress;
258 $decompress = $ALWAYS if defined $opt_decompress and $opt_decompress;
259 $decompress = $ONLY_SERVER if defined $opt_server_decompress;
260 $decompress = $ONLY_CLIENT if defined $opt_client_decompress;
262 usage("must specify at least a hostname") unless @ARGV;
263 @opt_dumpspecs = Amanda::Cmdline::parse_dumpspecs([@ARGV],
264 $Amanda::Cmdline::CMDLINE_PARSE_DATESTAMP | $Amanda::Cmdline::CMDLINE_PARSE_LEVEL);
266 set_config_overrides($config_overrides);
267 config_init($CONFIG_INIT_EXPLICIT_NAME, $opt_config);
268 my ($cfgerr_level, @cfgerr_errors) = config_errors();
269 if ($cfgerr_level >= $CFGERR_WARNINGS) {
270 config_print_errors();
271 if ($cfgerr_level >= $CFGERR_ERRORS) {
272 die("errors processing config file");
276 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
281 my ($msg, $finished_cb) = @_;
282 print STDERR "ERROR: $msg\n";
285 $clerk->quit(finished_cb => sub {
294 package main::Feedback;
296 use base 'Amanda::Recovery::Clerk::Feedback';
297 use Amanda::MainLoop;
301 my ($chg, $dev_name, $is_tty) = @_;
305 dev_name => $dev_name,
310 sub clerk_notif_part {
312 my ($label, $filenum, $header) = @_;
314 print STDERR "\n" if $self->{'is_tty'};
315 print STDERR "amfetchdump: $filenum: restoring ", $header->summary(), "\n";
318 sub clerk_notif_holding {
320 my ($filename, $header) = @_;
322 # this used to give the fd from which the holding file was being read.. why??
323 print STDERR "\n" if $self->{'is_tty'};
324 print STDERR "Reading '$filename'\n", $header->summary(), "\n";
329 use Amanda::MainLoop qw( :GIOCondition );
331 my ($finished_cb) = @_;
342 my $steps = define_steps
343 cb_ref => \$finished_cb;
348 # first, go to opt_directory or the original working directory we
350 my $destdir = $opt_chdir || Amanda::Util::get_original_cwd();
351 if (!chdir($destdir)) {
352 return failure("Cannot chdir to $destdir: $!", $finished_cb);
357 $delay = 1000; # 1 second
359 $delay = 5000; # 5 seconds
362 my $interactivity = Amanda::Interactivity::amfetchdump->new();
363 # if we have an explicit device, then the clerk doesn't get a changer --
364 # we operate the changer via Amanda::Recovery::Scan
365 if (defined $opt_device) {
366 $chg = Amanda::Changer->new($opt_device);
367 return failure($chg, $finished_cb) if $chg->isa("Amanda::Changer::Error");
368 my $scan = Amanda::Recovery::Scan->new(
370 interactivity => $interactivity);
371 return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
372 $clerk = Amanda::Recovery::Clerk->new(
373 feedback => main::Feedback->new($chg, $opt_device, $is_tty),
376 my $scan = Amanda::Recovery::Scan->new(
377 interactivity => $interactivity);
378 return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
380 $clerk = Amanda::Recovery::Clerk->new(
382 feedback => main::Feedback->new($chg, undef, $is_tty),
386 # planner gets to plan against the same changer the user specified
387 Amanda::Recovery::Planner::make_plan(
388 dumpspecs => [ @opt_dumpspecs ],
390 plan_cb => $steps->{'plan_cb'},
391 $opt_no_reassembly? (one_dump_per_part => 1) : ());
394 step plan_cb => sub {
395 (my $err, $plan) = @_;
396 return failure($err, $finished_cb) if $err;
398 if (!@{$plan->{'dumps'}}) {
399 return failure("No matching dumps found", $finished_cb);
402 # if we are doing a -p operation, only keep the first dump
404 print STDERR "WARNING: Fetch first dump only because of -p argument\n" if @{$plan->{'dumps'}} > 1;
405 @{$plan->{'dumps'}} = ($plan->{'dumps'}[0]);
408 my @needed_labels = $plan->get_volume_list();
409 my @needed_holding = $plan->get_holding_file_list();
410 if (@needed_labels) {
411 print STDERR (scalar @needed_labels), " volume(s) needed for restoration\n";
412 print STDERR "The following volumes are needed: ",
413 join(" ", map { $_->{'label'} } @needed_labels ), "\n";
415 if (@needed_holding) {
416 print STDERR (scalar @needed_holding), " holding file(s) needed for restoration\n";
417 for my $hf (@needed_holding) {
422 unless ($opt_assume) {
423 print STDERR "Press enter when ready\n";
427 $steps->{'start_dump'}->();
430 step start_dump => sub {
431 $current_dump = shift @{$plan->{'dumps'}};
433 if (!$current_dump) {
434 return $steps->{'finished'}->();
438 %recovery_params = ();
440 $clerk->get_xfer_src(
441 dump => $current_dump,
442 xfer_src_cb => $steps->{'xfer_src_cb'});
445 step xfer_src_cb => sub {
446 my ($errs, $hdr, $xfer_src, $directtcp_supported) = @_;
447 return failure(join("; ", @$errs), $finished_cb) if $errs;
449 # and set up the destination..
454 my $filename = sprintf("%s.%s.%s.%d",
456 Amanda::Util::sanitise_filename("".$hdr->{'disk'}), # workaround SWIG bug
458 $hdr->{'dumplevel'});
459 if ($opt_no_reassembly) {
460 $filename .= sprintf(".%07d", $hdr->{'partnum'});
463 # add an appropriate suffix
465 $filename .= ($hdr->{'compressed'} && $hdr->{'comp_suffix'})?
466 $hdr->{'comp_suffix'} : $Amanda::Constants::COMPRESS_SUFFIX;
469 if (!open($dest_fh, ">", $filename)) {
470 return failure("Could not open '$filename' for writing: $!", $finished_cb);
474 $timer = Amanda::MainLoop::timeout_source($delay);
475 $timer->set_callback(sub {
476 my $size = $xfer_src->get_bytes_read();
478 print STDERR "\r" . int($size/1024) . " kb ";
480 print STDERR "READ SIZE: " . int($size/1024) . " kb\n";
484 my $xfer_dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
486 my $dle = $hdr->get_dle();
488 # set up any filters that need to be applied; decryption first
490 if ($hdr->{'encrypted'} and
491 (($hdr->{'srv_encrypt'} and ($decrypt == $ALWAYS || $decrypt == $ONLY_SERVER)) ||
492 ($hdr->{'clnt_encrypt'} and ($decrypt == $ALWAYS || $decrypt == $ONLY_CLIENT)))) {
493 if ($hdr->{'srv_encrypt'}) {
495 Amanda::Xfer::Filter::Process->new(
496 [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0);
497 } elsif ($hdr->{'clnt_encrypt'}) {
499 Amanda::Xfer::Filter::Process->new(
500 [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0);
502 return failure("could not decrypt encrypted dump: no program specified",
506 $hdr->{'encrypted'} = 0;
507 $hdr->{'srv_encrypt'} = '';
508 $hdr->{'srv_decrypt_opt'} = '';
509 $hdr->{'clnt_encrypt'} = '';
510 $hdr->{'clnt_decrypt_opt'} = '';
511 $hdr->{'encrypt_suffix'} = 'N';
514 if ($hdr->{'compressed'} and not $opt_compress and
515 (($hdr->{'srvcompprog'} and ($decompress == $ALWAYS || $decompress == $ONLY_SERVER)) ||
516 ($hdr->{'clntcompprog'} and ($decompress == $ALWAYS || $decompress == $ONLY_CLIENT)) ||
517 ($dle->{'compress'} == $Amanda::Config::COMP_SERVER_FAST and ($decompress == $ALWAYS || $decompress == $ONLY_SERVER)) ||
518 ($dle->{'compress'} == $Amanda::Config::COMP_SERVER_BEST and ($decompress == $ALWAYS || $decompress == $ONLY_SERVER)) ||
519 ($dle->{'compress'} == $Amanda::Config::COMP_FAST and ($decompress == $ALWAYS || $decompress == $ONLY_CLIENT)) ||
520 ($dle->{'compress'} == $Amanda::Config::COMP_BEST and ($decompress == $ALWAYS || $decompress == $ONLY_CLIENT)))) {
521 # need to uncompress this file
522 if ($hdr->{'encrypted'}) {
523 print "Not decompressing because the backup image is not decrypted\n";
524 } elsif ($hdr->{'srvcompprog'}) {
525 # TODO: this assumes that srvcompprog takes "-d" to decompress
527 Amanda::Xfer::Filter::Process->new(
528 [ $hdr->{'srvcompprog'}, "-d" ], 0);
529 } elsif ($hdr->{'clntcompprog'}) {
530 # TODO: this assumes that clntcompprog takes "-d" to decompress
532 Amanda::Xfer::Filter::Process->new(
533 [ $hdr->{'clntcompprog'}, "-d" ], 0);
536 Amanda::Xfer::Filter::Process->new(
537 [ $Amanda::Constants::UNCOMPRESS_PATH,
538 $Amanda::Constants::UNCOMPRESS_OPT ], 0);
542 $hdr->{'compressed'} = 0;
543 $hdr->{'uncompress_cmd'} = '';
544 } elsif (!$hdr->{'compressed'} and $opt_compress and not $opt_leave) {
545 # need to compress this file
547 my $compress_opt = $opt_compress_best?
548 $Amanda::Constants::COMPRESS_BEST_OPT :
549 $Amanda::Constants::COMPRESS_FAST_OPT;
551 Amanda::Xfer::Filter::Process->new(
552 [ $Amanda::Constants::COMPRESS_PATH,
556 $hdr->{'compressed'} = 1;
557 $hdr->{'uncompress_cmd'} = " $Amanda::Constants::UNCOMPRESS_PATH " .
558 "$Amanda::Constants::UNCOMPRESS_OPT |";
559 $hdr->{'comp_suffix'} = $Amanda::Constants::COMPRESS_SUFFIX;
562 # write the header to the destination if requested
563 $hdr->{'blocksize'} = Amanda::Holding::DISK_BLOCK_BYTES;
564 if (defined $opt_header or defined $opt_header_file or defined $opt_header_fd) {
565 my $hdr_fh = $dest_fh;
566 if (defined $opt_header_file) {
567 open($hdr_fh, ">", $opt_header_file)
568 or return failure("could not open '$opt_header_file': $!", $finished_cb);
569 } elsif (defined $opt_header_fd) {
570 open($hdr_fh, "<&".($opt_header_fd+0))
571 or return failure("could not open fd $opt_header_fd: $!", $finished_cb);
573 syswrite $hdr_fh, $hdr->to_string(32768, 32768), 32768;
576 # start reading all filter stderr
577 foreach my $filter (@filters) {
578 my $fd = $filter->get_stderr_fd();
581 my $src = Amanda::MainLoop::fd_source($fd,
582 $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
584 $all_filter{$src} = 1;
585 $src->set_callback( sub {
587 my $n_read = POSIX::read($fd, $b, 1);
588 if (!defined $n_read) {
590 } elsif ($n_read == 0) {
591 delete $all_filter{$src};
594 if (!%all_filter and $recovery_done) {
595 $steps->{'filter_done'}->();
601 print STDERR "filter stderr: $line";
603 debug("filter stderr: $line");
610 my $xfer = Amanda::Xfer->new([ $xfer_src, @filters, $xfer_dest ]);
611 $xfer->start($steps->{'handle_xmsg'}, 0, $current_dump->{'bytes'});
612 $clerk->start_recovery(
614 recovery_cb => $steps->{'recovery_cb'});
617 step handle_xmsg => sub {
618 my ($src, $msg, $xfer) = @_;
620 $clerk->handle_xmsg($src, $msg, $xfer);
621 if ($msg->{'type'} == $XMSG_INFO) {
622 Amanda::Debug::info($msg->{'message'});
623 } elsif ($msg->{'type'} == $XMSG_ERROR) {
624 push @xfer_errs, $msg->{'message'};
628 step recovery_cb => sub {
629 %recovery_params = @_;
632 $steps->{'filter_done'}->() if !%all_filter;
635 step filter_done => sub {
637 print STDERR "\r" . int($recovery_params{'bytes_read'}/1024) . " kb ";
639 print STDERR "READ SIZE: " . int($recovery_params{'bytes_read'}/1024) . " kb\n";
641 @xfer_errs = (@xfer_errs, @{$recovery_params{'errors'}})
642 if $recovery_params{'errors'};
643 return failure(join("; ", @xfer_errs), $finished_cb)
645 return failure("recovery failed", $finished_cb)
646 if $recovery_params{'result'} ne 'DONE';
648 $steps->{'start_dump'}->();
651 step finished => sub {
653 $clerk->quit(finished_cb => $steps->{'quit'});
655 $steps->{'quit'}->();
662 if (defined $timer) {
666 print STDERR "\n" if $is_tty;
667 return failure($err, $finished_cb) if $err;
673 main(\&Amanda::MainLoop::quit);
674 Amanda::MainLoop::run();
675 Amanda::Util::finish_application();