2 # Copyright (c) 2009-2012 Zmanda, Inc. All Rights Reserved.
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 # Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300
19 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
21 use lib '@amperldir@';
30 use Amanda::Device qw( :constants );
31 use Amanda::Debug qw( :logging );
32 use Amanda::Config qw( :init :getconf config_dir_relative );
33 use Amanda::Util qw( :constants );
35 use Amanda::Constants;
40 use Amanda::Xfer qw( :constants );
41 use Amanda::Recovery::Planner;
42 use Amanda::Recovery::Clerk;
43 use Amanda::Recovery::Scan;
46 # Interactivity package
47 package Amanda::Interactivity::amfetchdump;
48 use POSIX qw( :errno_h );
49 use Amanda::MainLoop qw( :GIOCondition );
51 @ISA = qw( Amanda::Interactivity );
58 return bless ($self, $class);
64 if ($self->{'input_src'}) {
65 $self->{'input_src'}->remove();
66 $self->{'input_src'} = undef;
75 my $message = $params{'message'};
76 my $label = $params{'label'};
77 my $err = $params{'err'};
78 my $chg_name = $params{'chg_name'};
82 my $n_read = POSIX::read(0, $b, 1);
83 if (!defined $n_read) {
84 return if ($! == EINTR);
86 return $params{'request_cb'}->(
87 Amanda::Changer::Error->new('fatal',
88 message => "Fail to read from stdin"));
89 } elsif ($n_read == 0) {
91 return $params{'request_cb'}->(
92 Amanda::Changer::Error->new('fatal',
93 message => "Aborted by user"));
101 return $params{'request_cb'}->(undef, $line);
106 print STDERR "$err\n";
107 print STDERR "Insert volume labeled '$label' in $chg_name\n";
108 print STDERR "and press enter, or ^D to abort.\n";
110 $self->{'input_src'} = Amanda::MainLoop::fd_source(0, $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
111 $self->{'input_src'}->set_callback($data_in);
120 Usage: amfetchdump [-c|-C|-l] [-p|-n] [-a] [-O directory] [-d device]
121 [-h|--header-file file|--header-fd fd]
122 [-decrypt|--no-decrypt|--server-decrypt|--client-decrypt]
123 [--decompress|--no-decompress|--server-decompress|--client-decompress]
124 [--extract --directory directory [--data-path (amanda|directtcp)]
125 [--application-property='NAME=VALUE']*]
126 [-o configoption]* [--exact-match] config
127 hostname [diskname [datestamp [hostname [diskname [datestamp ... ]]]]]
129 print STDERR "ERROR: $msg\n" if $msg;
136 Amanda::Util::setup_application("amfetchdump", "server", $CONTEXT_CMDLINE);
138 my $config_overrides = new_config_overrides($#ARGV+1);
140 my ($opt_config, $opt_no_reassembly, $opt_compress, $opt_compress_best, $opt_pipe,
141 $opt_assume, $opt_leave, $opt_blocksize, $opt_device, $opt_chdir, $opt_header,
142 $opt_header_file, $opt_header_fd, @opt_dumpspecs,
143 $opt_decrypt, $opt_server_decrypt, $opt_client_decrypt,
144 $opt_decompress, $opt_server_decompress, $opt_client_decompress,
145 $opt_extract, $opt_directory, $opt_data_path, %application_property,
155 debug("Arguments: " . join(' ', @ARGV));
156 Getopt::Long::Configure(qw(bundling));
158 'version' => \&Amanda::Util::version_opt,
159 'help|usage|?' => \&usage,
160 'n' => \$opt_no_reassembly,
161 'c' => \$opt_compress,
162 'C' => \$opt_compress_best,
167 'header-file=s' => \$opt_header_file,
168 'header-fd=i' => \$opt_header_fd,
169 'decrypt!' => \$opt_decrypt,
170 'server-decrypt' => \$opt_server_decrypt,
171 'client-decrypt' => \$opt_client_decrypt,
172 'decompress!' => \$opt_decompress,
173 'server-decompress' => \$opt_server_decompress,
174 'client-decompress' => \$opt_client_decompress,
175 'extract' => \$opt_extract,
176 'directory=s' => \$opt_directory,
177 'data-path=s' => \$opt_data_path,
178 'application-property=s' => \%application_property,
179 'exact-match' => \$opt_exact_match,
180 'b=s' => \$opt_blocksize,
181 'd=s' => \$opt_device,
182 'O=s' => \$opt_chdir,
183 'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
185 usage() unless (@ARGV);
186 $opt_config = shift @ARGV;
188 if (defined $opt_compress and defined $opt_compress_best) {
189 print STDERR "Can't use -c and -C\n";
193 usage("The -b option is no longer supported; set readblocksize in the tapetype section\n" .
194 "of amanda.conf instead.")
196 usage("-l is not compatible with -c or -C")
197 if ($opt_leave and $opt_compress);
198 usage("-p is not compatible with -n")
199 if ($opt_leave and $opt_no_reassembly);
200 usage("-h, --header-file, and --header-fd are mutually incompatible")
201 if (($opt_header and ($opt_header_file or $opt_header_fd))
202 or ($opt_header_file and $opt_header_fd));
204 $opt_data_path = lc($opt_data_path) if defined ($opt_data_path);
205 usage("--data_path must be 'amanda' or 'directtcp'")
206 if (defined $opt_data_path and $opt_data_path ne 'directtcp' and $opt_data_path ne 'amanda');
208 if (defined $opt_leave) {
209 if (defined $opt_decrypt and $opt_decrypt) {
210 print STDERR "-l is incompatible with --decrypt\n";
213 if (defined $opt_server_decrypt) {
214 print STDERR "-l is incompatible with --server-decrypt\n";
217 if (defined $opt_client_decrypt) {
218 print STDERR "-l is incompatible with --client-decrypt\n";
221 if (defined $opt_decompress and $opt_decompress) {
222 print STDERR "-l is incompatible with --decompress\n";
225 if (defined $opt_server_decompress) {
226 print STDERR "-l is incompatible with --server-decompress\n";
229 if (defined $opt_client_decompress) {
230 print STDERR "-l is incompatible with --client-decompress\n";
235 if (( defined $opt_directory and !defined $opt_extract) or
236 (!defined $opt_directory and defined $opt_extract)) {
237 print STDERR "Both --directorty and --extract must be set\n";
240 if (defined $opt_directory and defined $opt_extract) {
242 if (defined $opt_server_decrypt or defined $opt_client_decrypt) {
243 print STDERR "--server_decrypt or --client-decrypt is incompatible with --extract\n";
247 if (defined $opt_server_decompress || defined $opt_client_decompress) {
248 print STDERR "--server-decompress r --client-decompress is incompatible with --extract\n";
251 if (defined($opt_leave) +
252 defined($opt_compress) +
253 defined($opt_compress_best)) {
254 print STDERR "Can't use -l -c or -C with --extract\n";
257 if (defined $opt_pipe) {
258 print STDERR "--pipe is incompatible with --extract\n";
261 if (defined $opt_header) {
262 print STDERR "--header is incompatible with --extract\n";
267 if (defined($opt_decrypt) +
268 defined($opt_server_decrypt) +
269 defined($opt_client_decrypt) > 1) {
270 print STDERR "Can't use only on of --decrypt, --no-decrypt, --server-decrypt or --client-decrypt\n";
273 if (defined($opt_decompress) +
274 defined($opt_server_decompress) +
275 defined($opt_client_decompress) > 1) {
276 print STDERR "Can't use only on of --decompress, --no-decompress, --server-decompress or --client-decompress\n";
280 if (defined($opt_compress) and
281 defined($opt_decompress) +
282 defined($opt_server_decompress) +
283 defined($opt_client_decompress) > 0) {
284 print STDERR "Can't specify -c with one of --decompress, --no-decompress, --server-decompress or --client-decompress\n";
287 if (defined($opt_compress_best) and
288 defined($opt_decompress) +
289 defined($opt_server_decompress) +
290 defined($opt_client_decompress) > 0) {
291 print STDERR "Can't specify -C with one of --decompress, --no-decompress, --server-decompress or --client-decompress\n";
295 $decompress = $ALWAYS;
297 $decrypt = $NEVER if defined $opt_leave;
298 $decrypt = $NEVER if defined $opt_decrypt and !$opt_decrypt;
299 $decrypt = $ALWAYS if defined $opt_decrypt and $opt_decrypt;
300 $decrypt = $ONLY_SERVER if defined $opt_server_decrypt;
301 $decrypt = $ONLY_CLIENT if defined $opt_client_decrypt;
303 $opt_compress = 1 if $opt_compress_best;
305 $decompress = $NEVER if defined $opt_compress;
306 $decompress = $NEVER if defined $opt_leave;
307 $decompress = $NEVER if defined $opt_decompress and !$opt_decompress;
308 $decompress = $ALWAYS if defined $opt_decompress and $opt_decompress;
309 $decompress = $ONLY_SERVER if defined $opt_server_decompress;
310 $decompress = $ONLY_CLIENT if defined $opt_client_decompress;
312 usage("must specify at least a hostname") unless @ARGV;
313 my $cmd_flags = $Amanda::Cmdline::CMDLINE_PARSE_DATESTAMP |
314 $Amanda::Cmdline::CMDLINE_PARSE_LEVEL;
315 $cmd_flags |= $Amanda::Cmdline::CMDLINE_EXACT_MATCH if $opt_exact_match;
316 @opt_dumpspecs = Amanda::Cmdline::parse_dumpspecs([@ARGV], $cmd_flags);
318 set_config_overrides($config_overrides);
319 config_init($CONFIG_INIT_EXPLICIT_NAME, $opt_config);
320 my ($cfgerr_level, @cfgerr_errors) = config_errors();
321 if ($cfgerr_level >= $CFGERR_WARNINGS) {
322 config_print_errors();
323 if ($cfgerr_level >= $CFGERR_ERRORS) {
324 die("errors processing config file");
328 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
334 my ($msg, $finished_cb) = @_;
335 print STDERR "ERROR: $msg\n";
336 debug("FAILURE: $msg");
339 $clerk->quit(finished_cb => sub {
348 package main::Feedback;
350 use base 'Amanda::Recovery::Clerk::Feedback';
351 use Amanda::MainLoop;
355 my ($chg, $dev_name, $is_tty) = @_;
359 dev_name => $dev_name,
364 sub clerk_notif_part {
366 my ($label, $filenum, $header) = @_;
368 print STDERR "\n" if $self->{'is_tty'};
369 print STDERR "amfetchdump: $filenum: restoring ", $header->summary(), "\n";
372 sub clerk_notif_holding {
374 my ($filename, $header) = @_;
376 # this used to give the fd from which the holding file was being read.. why??
377 print STDERR "\n" if $self->{'is_tty'};
378 print STDERR "Reading '$filename'\n", $header->summary(), "\n";
383 use Amanda::MainLoop qw( :GIOCondition );
385 my ($finished_cb) = @_;
396 my @directtcp_command;
398 my $steps = define_steps
399 cb_ref => \$finished_cb;
404 # first, go to opt_directory or the original working directory we
406 my $destdir = $opt_chdir || Amanda::Util::get_original_cwd();
407 if (!chdir($destdir)) {
408 return failure("Cannot chdir to $destdir: $!", $finished_cb);
413 $delay = 1000; # 1 second
415 $delay = 5000; # 5 seconds
418 my $interactivity = Amanda::Interactivity::amfetchdump->new();
419 # if we have an explicit device, then the clerk doesn't get a changer --
420 # we operate the changer via Amanda::Recovery::Scan
421 if (defined $opt_device) {
422 $chg = Amanda::Changer->new($opt_device);
423 return failure($chg, $finished_cb) if $chg->isa("Amanda::Changer::Error");
424 my $scan = Amanda::Recovery::Scan->new(
426 interactivity => $interactivity);
427 return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
428 $clerk = Amanda::Recovery::Clerk->new(
429 feedback => main::Feedback->new($chg, $opt_device, $is_tty),
432 my $scan = Amanda::Recovery::Scan->new(
433 interactivity => $interactivity);
434 return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
436 $clerk = Amanda::Recovery::Clerk->new(
438 feedback => main::Feedback->new($chg, undef, $is_tty),
442 # planner gets to plan against the same changer the user specified
443 Amanda::Recovery::Planner::make_plan(
444 dumpspecs => [ @opt_dumpspecs ],
446 plan_cb => $steps->{'plan_cb'},
447 $opt_no_reassembly? (one_dump_per_part => 1) : ());
450 step plan_cb => sub {
451 (my $err, $plan) = @_;
452 return failure($err, $finished_cb) if $err;
454 if (!@{$plan->{'dumps'}}) {
455 return failure("No matching dumps found", $finished_cb);
458 # if we are doing a -p operation, only keep the first dump
460 print STDERR "WARNING: Fetch first dump only because of -p argument\n" if @{$plan->{'dumps'}} > 1;
461 @{$plan->{'dumps'}} = ($plan->{'dumps'}[0]);
464 my @needed_labels = $plan->get_volume_list();
465 my @needed_holding = $plan->get_holding_file_list();
466 if (@needed_labels) {
467 print STDERR (scalar @needed_labels), " volume(s) needed for restoration\n";
468 print STDERR "The following volumes are needed: ",
469 join(" ", map { $_->{'label'} } @needed_labels ), "\n";
471 if (@needed_holding) {
472 print STDERR (scalar @needed_holding), " holding file(s) needed for restoration\n";
473 for my $hf (@needed_holding) {
478 unless ($opt_assume) {
479 print STDERR "Press enter when ready\n";
483 $steps->{'start_dump'}->();
486 step start_dump => sub {
487 $current_dump = shift @{$plan->{'dumps'}};
489 if (!$current_dump) {
490 return $steps->{'finished'}->();
494 %recovery_params = ();
496 $clerk->get_xfer_src(
497 dump => $current_dump,
498 xfer_src_cb => $steps->{'xfer_src_cb'});
501 step xfer_src_cb => sub {
502 my ($errs, $hdr, $xfer_src, $directtcp_supported) = @_;
503 return failure(join("; ", @$errs), $finished_cb) if $errs;
505 my $dle_str = $hdr->{'dle_str'};
506 my $p1 = XML::Simple->new();
507 my $dle = $p1->XMLin($dle_str);
509 # and set up the destination..
514 if (defined $opt_data_path and $opt_data_path eq 'directtcp' and !$directtcp_supported) {
515 return failure("The device can't do directtcp", $finished_cb);
517 $directtcp_supported = 0 if defined $opt_data_path and $opt_data_path eq 'amanda';
519 my $program = uc(basename($hdr->{program}));
521 if ($program ne "APPLICATION") {
522 $directtcp_supported = 0;
523 my %validation_programs = (
524 "STAR" => [ $Amanda::Constants::STAR, qw(-x -f -) ],
525 "DUMP" => [ $Amanda::Constants::RESTORE, qw(xbf 2 -) ],
526 "VDUMP" => [ $Amanda::Constants::VRESTORE, qw(xf -) ],
527 "VXDUMP" => [ $Amanda::Constants::VXRESTORE, qw(xbf 2 -) ],
528 "XFSDUMP" => [ $Amanda::Constants::XFSRESTORE, qw(-v silent) ],
529 "TAR" => [ $Amanda::Constants::GNUTAR, qw(--ignore-zeros -xf -) ],
530 "GTAR" => [ $Amanda::Constants::GNUTAR, qw(--ignore-zeros -xf -) ],
531 "GNUTAR" => [ $Amanda::Constants::GNUTAR, qw(--ignore-zeros -xf -) ],
532 "SMBCLIENT" => [ $Amanda::Constants::GNUTAR, qw(-xf -) ],
535 if (!exists $validation_programs{$program}) {
536 return failure("Unknown program '$program' in header; no validation to perform",
539 @argv = $validation_programs{$program};
541 if (!defined $hdr->{application}) {
542 return failure("Application not set", $finished_cb);
544 my $program_path = $Amanda::Paths::APPLICATION_DIR . "/" .
546 if (!-x $program_path) {
547 return failure("Application '" . $hdr->{application} .
548 "($program_path)' not available on the server",
552 $bsu_argv{'application'} = $hdr->{application};
553 $bsu_argv{'config'} = $opt_config;
554 $bsu_argv{'host'} = $hdr->{'name'};
555 $bsu_argv{'disk'} = $hdr->{'disk'};
556 $bsu_argv{'device'} = $dle->{'diskdevice'} if defined $dle->{'diskdevice'};
557 my ($bsu, $err) = Amanda::Extract::BSU(%bsu_argv);
558 if (defined $opt_data_path and $opt_data_path eq 'directtcp' and
559 !$bsu->{'data-path-directtcp'}) {
560 return failure("The application can't do directtcp", $finished_cb);
562 if ($directtcp_supported and !$bsu->{'data-path-directtcp'}) {
563 # application do not support directtcp
564 $directtcp_supported = 0;
567 push @argv, $program_path, "restore";
568 push @argv, "--config", $opt_config;
569 push @argv, "--host", $hdr->{'name'};
570 push @argv, "--disk", $hdr->{'disk'};
571 push @argv, "--device", $dle->{'diskdevice'} if defined ($dle->{'diskdevice'});
572 push @argv, "--level", $hdr->{'dumplevel'};
573 push @argv, "--directory", $opt_directory;
575 # add application_property
576 while (my($name, $value) = each(%application_property)) {
577 push @argv, "--".$name, $value if $value;
580 #merge property from header;
581 while (my($name, $value) = each (%{$dle->{'backup-program'}->{'property'}})) {
582 if (!exists $application_property{$name}) {
583 push @argv, "--".$name, $value->{'value'};
588 $directtcp = $directtcp_supported;
589 if ($directtcp_supported) {
590 $xfer_dest = Amanda::Xfer::Dest::DirectTCPListen->new();
591 @directtcp_command = @argv;
593 # set up the extraction command as a filter element, since
594 # we need its stderr.
595 debug("Running: ". join(' ',@argv));
596 push @filters, Amanda::Xfer::Filter::Process->new(\@argv, 0);
599 $xfer_dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
601 } elsif ($opt_pipe) {
603 $xfer_dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
605 my $filename = sprintf("%s.%s.%s.%d",
607 Amanda::Util::sanitise_filename("".$hdr->{'disk'}), # workaround SWIG bug
609 $hdr->{'dumplevel'});
610 if ($opt_no_reassembly) {
611 $filename .= sprintf(".%07d", $hdr->{'partnum'});
614 # add an appropriate suffix
616 $filename .= ($hdr->{'compressed'} && $hdr->{'comp_suffix'})?
617 $hdr->{'comp_suffix'} : $Amanda::Constants::COMPRESS_SUFFIX;
620 if (!open($dest_fh, ">", $filename)) {
621 return failure("Could not open '$filename' for writing: $!", $finished_cb);
623 $xfer_dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
626 $timer = Amanda::MainLoop::timeout_source($delay);
627 $timer->set_callback(sub {
628 my $size = $xfer_src->get_bytes_read();
630 print STDERR "\r" . int($size/1024) . " kb ";
632 print STDERR "READ SIZE: " . int($size/1024) . " kb\n";
636 # set up any filters that need to be applied; decryption first
637 if ($hdr->{'encrypted'} and
638 (($hdr->{'srv_encrypt'} and ($decrypt == $ALWAYS || $decrypt == $ONLY_SERVER)) ||
639 ($hdr->{'clnt_encrypt'} and ($decrypt == $ALWAYS || $decrypt == $ONLY_CLIENT)))) {
640 if ($hdr->{'srv_encrypt'}) {
642 Amanda::Xfer::Filter::Process->new(
643 [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0);
644 } elsif ($hdr->{'clnt_encrypt'}) {
646 Amanda::Xfer::Filter::Process->new(
647 [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0);
649 return failure("could not decrypt encrypted dump: no program specified",
653 $hdr->{'encrypted'} = 0;
654 $hdr->{'srv_encrypt'} = '';
655 $hdr->{'srv_decrypt_opt'} = '';
656 $hdr->{'clnt_encrypt'} = '';
657 $hdr->{'clnt_decrypt_opt'} = '';
658 $hdr->{'encrypt_suffix'} = 'N';
661 if ($hdr->{'compressed'} and not $opt_compress and
662 (($hdr->{'srvcompprog'} and ($decompress == $ALWAYS || $decompress == $ONLY_SERVER)) ||
663 ($hdr->{'clntcompprog'} and ($decompress == $ALWAYS || $decompress == $ONLY_CLIENT)) ||
664 ($dle->{'compress'} and $dle->{'compress'} eq "SERVER-FAST" and ($decompress == $ALWAYS || $decompress == $ONLY_SERVER)) ||
665 ($dle->{'compress'} and $dle->{'compress'} eq "SERVER-BEST" and ($decompress == $ALWAYS || $decompress == $ONLY_SERVER)) ||
666 ($dle->{'compress'} and $dle->{'compress'} eq "FAST" and ($decompress == $ALWAYS || $decompress == $ONLY_CLIENT)) ||
667 ($dle->{'compress'} and $dle->{'compress'} eq "BEST" and ($decompress == $ALWAYS || $decompress == $ONLY_CLIENT)))) {
668 # need to uncompress this file
669 if ($hdr->{'encrypted'}) {
670 print "Not decompressing because the backup image is not decrypted\n";
671 } elsif ($hdr->{'srvcompprog'}) {
672 # TODO: this assumes that srvcompprog takes "-d" to decompress
674 Amanda::Xfer::Filter::Process->new(
675 [ $hdr->{'srvcompprog'}, "-d" ], 0);
676 } elsif ($hdr->{'clntcompprog'}) {
677 # TODO: this assumes that clntcompprog takes "-d" to decompress
679 Amanda::Xfer::Filter::Process->new(
680 [ $hdr->{'clntcompprog'}, "-d" ], 0);
683 Amanda::Xfer::Filter::Process->new(
684 [ $Amanda::Constants::UNCOMPRESS_PATH,
685 $Amanda::Constants::UNCOMPRESS_OPT ], 0);
689 $hdr->{'compressed'} = 0;
690 $hdr->{'uncompress_cmd'} = '';
691 } elsif (!$hdr->{'compressed'} and $opt_compress and not $opt_leave) {
692 # need to compress this file
694 my $compress_opt = $opt_compress_best?
695 $Amanda::Constants::COMPRESS_BEST_OPT :
696 $Amanda::Constants::COMPRESS_FAST_OPT;
698 Amanda::Xfer::Filter::Process->new(
699 [ $Amanda::Constants::COMPRESS_PATH,
703 $hdr->{'compressed'} = 1;
704 $hdr->{'uncompress_cmd'} = " $Amanda::Constants::UNCOMPRESS_PATH " .
705 "$Amanda::Constants::UNCOMPRESS_OPT |";
706 $hdr->{'comp_suffix'} = $Amanda::Constants::COMPRESS_SUFFIX;
709 # write the header to the destination if requested
710 $hdr->{'blocksize'} = Amanda::Holding::DISK_BLOCK_BYTES;
711 if (defined $opt_header or defined $opt_header_file or defined $opt_header_fd) {
712 my $hdr_fh = $dest_fh;
713 if (defined $opt_header_file) {
714 open($hdr_fh, ">", $opt_header_file)
715 or return failure("could not open '$opt_header_file': $!", $finished_cb);
716 } elsif (defined $opt_header_fd) {
717 open($hdr_fh, "<&".($opt_header_fd+0))
718 or return failure("could not open fd $opt_header_fd: $!", $finished_cb);
720 syswrite $hdr_fh, $hdr->to_string(32768, 32768), 32768;
723 # start reading all filter stderr
724 foreach my $filter (@filters) {
725 my $fd = $filter->get_stderr_fd();
728 my $src = Amanda::MainLoop::fd_source($fd,
729 $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
731 $all_filter{$src} = 1;
732 $src->set_callback( sub {
734 my $n_read = POSIX::read($fd, $b, 1);
735 if (!defined $n_read) {
737 } elsif ($n_read == 0) {
738 delete $all_filter{$src};
741 if (!%all_filter and $recovery_done) {
742 $steps->{'filter_done'}->();
749 if (length($line) > 1) {
750 print STDERR "filter stderr: $line\n";
751 debug("filter stderr: $line");
761 $xfer = Amanda::Xfer->new([ $xfer_src, @filters, $xfer_dest ]);
763 $xfer = Amanda::Xfer->new([ $xfer_src, $xfer_dest ]);
765 $xfer->start($steps->{'handle_xmsg'}, 0, $current_dump->{'bytes'});
766 $clerk->start_recovery(
768 recovery_cb => $steps->{'recovery_cb'});
770 my $addr = $xfer_dest->get_addrs();
771 push @directtcp_command, "--data-path", "DIRECTTCP";
772 push @directtcp_command, "--direct-tcp", "$addr->[0]->[0]:$addr->[0]->[1]";
773 debug("Running: ". join(' ', @directtcp_command));
776 my $err = Symbol::gensym;
777 my $amndmp_pid = open3($wtr, $rdr, $err, @directtcp_command);
778 $amndmp_pid = $amndmp_pid;
779 my $file_to_close = 2;
780 my $amndmp_stdout_src = Amanda::MainLoop::fd_source($rdr,
781 $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
782 my $amndmp_stderr_src = Amanda::MainLoop::fd_source($err,
783 $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
785 $amndmp_stdout_src->set_callback( sub {
787 if (!defined $line) {
789 $amndmp_stdout_src->remove();
790 if ($file_to_close == 0) {
792 $xfer->cancel() if $xfer->get_status != $XFER_DONE;
797 debug("amndmp stdout: $line");
800 $amndmp_stderr_src->set_callback( sub {
802 if (!defined $line) {
804 $amndmp_stderr_src->remove();
805 if ($file_to_close == 0) {
807 $xfer->cancel() if $xfer->get_status != $XFER_DONE;
812 debug("amndmp stderr: $line");
813 print STDERR "$line\n";
818 step handle_xmsg => sub {
819 my ($src, $msg, $xfer) = @_;
821 $clerk->handle_xmsg($src, $msg, $xfer);
822 if ($msg->{'type'} == $XMSG_INFO) {
823 Amanda::Debug::info($msg->{'message'});
824 } elsif ($msg->{'type'} == $XMSG_ERROR) {
825 push @xfer_errs, $msg->{'message'};
829 step recovery_cb => sub {
830 %recovery_params = @_;
833 $steps->{'filter_done'}->() if !%all_filter;
836 step filter_done => sub {
838 print STDERR "\r" . int($recovery_params{'bytes_read'}/1024) . " kb ";
840 print STDERR "READ SIZE: " . int($recovery_params{'bytes_read'}/1024) . " kb\n";
842 @xfer_errs = (@xfer_errs, @{$recovery_params{'errors'}})
843 if $recovery_params{'errors'};
844 return failure(join("; ", @xfer_errs), $finished_cb)
846 return failure("recovery failed", $finished_cb)
847 if $recovery_params{'result'} ne 'DONE';
849 $steps->{'start_dump'}->();
852 step finished => sub {
854 $clerk->quit(finished_cb => $steps->{'quit'});
856 $steps->{'quit'}->();
863 if (defined $timer) {
867 print STDERR "\n" if $is_tty;
868 return failure($err, $finished_cb) if $err;
874 main(\&Amanda::MainLoop::quit);
875 Amanda::MainLoop::run();
876 Amanda::Util::finish_application();