Imported Upstream version 3.3.3
[debian/amanda] / server-src / amfetchdump.pl
1 #! @PERL@
2 # Copyright (c) 2009-2012 Zmanda, Inc.  All Rights Reserved.
3 #
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.
8 #
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
12 # for more details.
13 #
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
17 #
18 # Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300
19 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20
21 use lib '@amperldir@';
22 use strict;
23 use warnings;
24
25 use Getopt::Long;
26 use File::Basename;
27 use XML::Simple;
28 use IPC::Open3;
29
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 );
34 use Amanda::Changer;
35 use Amanda::Constants;
36 use Amanda::MainLoop;
37 use Amanda::Header;
38 use Amanda::Holding;
39 use Amanda::Cmdline;
40 use Amanda::Xfer qw( :constants );
41 use Amanda::Recovery::Planner;
42 use Amanda::Recovery::Clerk;
43 use Amanda::Recovery::Scan;
44 use Amanda::Extract;
45
46 # Interactivity package
47 package Amanda::Interactivity::amfetchdump;
48 use POSIX qw( :errno_h );
49 use Amanda::MainLoop qw( :GIOCondition );
50 use vars qw( @ISA );
51 @ISA = qw( Amanda::Interactivity );
52
53 sub new {
54     my $class = shift;
55
56     my $self = {
57         input_src => undef};
58     return bless ($self, $class);
59 }
60
61 sub abort() {
62     my $self = shift;
63
64     if ($self->{'input_src'}) {
65         $self->{'input_src'}->remove();
66         $self->{'input_src'} = undef;
67     }
68 }
69
70 sub user_request {
71     my $self = shift;
72     my %params = @_;
73     my $buffer = "";
74
75     my $message  = $params{'message'};
76     my $label    = $params{'label'};
77     my $err      = $params{'err'};
78     my $chg_name = $params{'chg_name'};
79
80     my $data_in = sub {
81         my $b;
82         my $n_read = POSIX::read(0, $b, 1);
83         if (!defined $n_read) {
84             return if ($! == EINTR);
85             $self->abort();
86             return $params{'request_cb'}->(
87                 Amanda::Changer::Error->new('fatal',
88                         message => "Fail to read from stdin"));
89         } elsif ($n_read == 0) {
90             $self->abort();
91             return $params{'request_cb'}->(
92                 Amanda::Changer::Error->new('fatal',
93                         message => "Aborted by user"));
94         } else {
95             $buffer .= $b;
96             if ($b eq "\n") {
97                 my $line = $buffer;
98                 chomp $line;
99                 $buffer = "";
100                 $self->abort();
101                 return $params{'request_cb'}->(undef, $line);
102             }
103         }
104     };
105
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";
109
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);
112     return;
113 };
114
115 package main;
116
117 sub usage {
118     my ($msg) = @_;
119     print STDERR <<EOF;
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 ... ]]]]]
128 EOF
129     print STDERR "ERROR: $msg\n" if $msg;
130     exit(1);
131 }
132
133 ##
134 # main
135
136 Amanda::Util::setup_application("amfetchdump", "server", $CONTEXT_CMDLINE);
137
138 my $config_overrides = new_config_overrides($#ARGV+1);
139
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,
146     $opt_exact_match);
147
148 my $NEVER = 0;
149 my $ALWAYS = 1;
150 my $ONLY_SERVER = 2;
151 my $ONLY_CLIENT = 3;
152 my $decrypt;
153 my $decompress;
154
155 debug("Arguments: " . join(' ', @ARGV));
156 Getopt::Long::Configure(qw(bundling));
157 GetOptions(
158     'version' => \&Amanda::Util::version_opt,
159     'help|usage|?' => \&usage,
160     'n' => \$opt_no_reassembly,
161     'c' => \$opt_compress,
162     'C' => \$opt_compress_best,
163     'p' => \$opt_pipe,
164     'a' => \$opt_assume,
165     'l' => \$opt_leave,
166     'h' => \$opt_header,
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]); },
184 ) or usage();
185 usage() unless (@ARGV);
186 $opt_config = shift @ARGV;
187
188 if (defined $opt_compress and defined $opt_compress_best) {
189     print STDERR "Can't use -c and -C\n";
190     usage();
191 }
192
193 usage("The -b option is no longer supported; set readblocksize in the tapetype section\n" .
194       "of amanda.conf instead.")
195     if ($opt_blocksize);
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));
203
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');
207
208 if (defined $opt_leave) {
209     if (defined $opt_decrypt and $opt_decrypt) {
210         print STDERR "-l is incompatible with --decrypt\n";
211         usage();
212     }
213     if (defined $opt_server_decrypt) {
214         print STDERR "-l is incompatible with --server-decrypt\n";
215         usage();
216     }
217     if (defined $opt_client_decrypt) {
218         print STDERR "-l is incompatible with --client-decrypt\n";
219         usage();
220     }
221     if (defined $opt_decompress and $opt_decompress) {
222         print STDERR "-l is incompatible with --decompress\n";
223         usage();
224     }
225     if (defined $opt_server_decompress) {
226         print STDERR "-l is incompatible with --server-decompress\n";
227         usage();
228     }
229     if (defined $opt_client_decompress) {
230         print STDERR "-l is incompatible with --client-decompress\n";
231         usage();
232     }
233 }
234
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";
238     usage();
239 }
240 if (defined $opt_directory and defined $opt_extract) {
241     $opt_decrypt = 1;
242     if (defined $opt_server_decrypt or defined $opt_client_decrypt) {
243         print STDERR "--server_decrypt or --client-decrypt is incompatible with --extract\n";
244         usage();
245     }
246     $opt_decompress = 1;
247     if (defined $opt_server_decompress || defined $opt_client_decompress) {
248         print STDERR "--server-decompress r --client-decompress is incompatible with --extract\n";
249         usage();
250     }
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";
255         usage();
256     }
257     if (defined $opt_pipe) {
258         print STDERR "--pipe is incompatible with --extract\n";
259         usage();
260     }
261     if (defined $opt_header) {
262         print STDERR "--header is incompatible with --extract\n";
263         usage();
264     }
265 }
266
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";
271     usage();
272 }
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";
277     usage();
278 }
279
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";
285     usage();
286 }
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";
292     usage();
293 }
294
295 $decompress = $ALWAYS;
296 $decrypt = $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;
302
303 $opt_compress = 1 if $opt_compress_best;
304
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;
311
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);
317
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");
325     }
326 }
327
328 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
329
330 my $exit_status = 0;
331 my $clerk;
332 use Data::Dumper;
333 sub failure {
334     my ($msg, $finished_cb) = @_;
335     print STDERR "ERROR: $msg\n";
336     debug("FAILURE: $msg");
337     $exit_status = 1;
338     if ($clerk) {
339         $clerk->quit(finished_cb => sub {
340             # ignore error
341             $finished_cb->();
342         });
343     } else {
344         $finished_cb->();
345     }
346 }
347
348 package main::Feedback;
349
350 use base 'Amanda::Recovery::Clerk::Feedback';
351 use Amanda::MainLoop;
352
353 sub new {
354     my $class = shift;
355     my ($chg, $dev_name, $is_tty) = @_;
356
357     return bless {
358         chg => $chg,
359         dev_name => $dev_name,
360         is_tty => $is_tty,
361     }, $class;
362 }
363
364 sub clerk_notif_part {
365     my $self = shift;
366     my ($label, $filenum, $header) = @_;
367
368     print STDERR "\n" if $self->{'is_tty'};
369     print STDERR "amfetchdump: $filenum: restoring ", $header->summary(), "\n";
370 }
371
372 sub clerk_notif_holding {
373     my $self = shift;
374     my ($filename, $header) = @_;
375
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";
379 }
380
381 package main;
382
383 use Amanda::MainLoop qw( :GIOCondition );
384 sub main {
385     my ($finished_cb) = @_;
386     my $current_dump;
387     my $plan;
388     my @xfer_errs;
389     my %all_filter;
390     my $recovery_done;
391     my %recovery_params;
392     my $timer;
393     my $is_tty;
394     my $delay;
395     my $directtcp = 0;
396     my @directtcp_command;
397
398     my $steps = define_steps
399         cb_ref => \$finished_cb;
400
401     step start => sub {
402         my $chg;
403
404         # first, go to opt_directory or the original working directory we
405         # were started in
406         my $destdir = $opt_chdir || Amanda::Util::get_original_cwd();
407         if (!chdir($destdir)) {
408             return failure("Cannot chdir to $destdir: $!", $finished_cb);
409         }
410
411         $is_tty = -t STDERR;
412         if($is_tty) {
413             $delay = 1000; # 1 second
414         } else {
415             $delay = 5000; # 5 seconds
416         }
417
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(
425                                 chg => $chg,
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),
430                 scan     => $scan);
431         } else {
432             my $scan = Amanda::Recovery::Scan->new(
433                                 interactivity => $interactivity);
434             return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
435
436             $clerk = Amanda::Recovery::Clerk->new(
437                 changer => $chg,
438                 feedback => main::Feedback->new($chg, undef, $is_tty),
439                 scan     => $scan);
440         }
441
442         # planner gets to plan against the same changer the user specified
443         Amanda::Recovery::Planner::make_plan(
444             dumpspecs => [ @opt_dumpspecs ],
445             changer => $chg,
446             plan_cb => $steps->{'plan_cb'},
447             $opt_no_reassembly? (one_dump_per_part => 1) : ());
448     };
449
450     step plan_cb => sub {
451         (my $err, $plan) = @_;
452         return failure($err, $finished_cb) if $err;
453
454         if (!@{$plan->{'dumps'}}) {
455             return failure("No matching dumps found", $finished_cb);
456         }
457
458         # if we are doing a -p operation, only keep the first dump
459         if ($opt_pipe) {
460             print STDERR "WARNING: Fetch first dump only because of -p argument\n" if @{$plan->{'dumps'}} > 1;
461             @{$plan->{'dumps'}} = ($plan->{'dumps'}[0]);
462         }
463
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";
470         }
471         if (@needed_holding) {
472             print STDERR (scalar @needed_holding), " holding file(s) needed for restoration\n";
473             for my $hf (@needed_holding) {
474                 print "  $hf\n";
475             }
476         }
477
478         unless ($opt_assume) {
479             print STDERR "Press enter when ready\n";
480             my $resp = <STDIN>;
481         }
482
483         $steps->{'start_dump'}->();
484     };
485
486     step start_dump => sub {
487         $current_dump = shift @{$plan->{'dumps'}};
488
489         if (!$current_dump) {
490             return $steps->{'finished'}->();
491         }
492
493         $recovery_done = 0;
494         %recovery_params = ();
495
496         $clerk->get_xfer_src(
497             dump => $current_dump,
498             xfer_src_cb => $steps->{'xfer_src_cb'});
499     };
500
501     step xfer_src_cb => sub {
502         my ($errs, $hdr, $xfer_src, $directtcp_supported) = @_;
503         return failure(join("; ", @$errs), $finished_cb) if $errs;
504
505         my $dle_str = $hdr->{'dle_str'};
506         my $p1 = XML::Simple->new();
507         my $dle = $p1->XMLin($dle_str);
508
509         # and set up the destination..
510         my $dest_fh;
511         my $xfer_dest;
512         my @filters;
513
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);
516         }
517         $directtcp_supported = 0 if defined $opt_data_path and $opt_data_path eq 'amanda';
518         if ($opt_extract) {
519             my $program = uc(basename($hdr->{program}));
520             my @argv;
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 -) ],
533                         "PKZIP" => undef,
534                 );
535                 if (!exists $validation_programs{$program}) {
536                     return failure("Unknown program '$program' in header; no validation to perform",
537                                    $finished_cb);
538                 }
539                 @argv = $validation_programs{$program};
540             } else {
541                 if (!defined $hdr->{application}) {
542                     return failure("Application not set", $finished_cb);
543                 }
544                 my $program_path = $Amanda::Paths::APPLICATION_DIR . "/" .
545                                    $hdr->{application};
546                 if (!-x $program_path) {
547                     return failure("Application '" . $hdr->{application} .
548                                    "($program_path)' not available on the server",
549                                    $finished_cb);
550                 }
551                 my %bsu_argv;
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);
561                 }
562                 if ($directtcp_supported and !$bsu->{'data-path-directtcp'}) {
563                     # application do not support directtcp
564                     $directtcp_supported = 0;
565                 }
566
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;
574
575                 # add application_property
576                 while (my($name, $value) = each(%application_property)) {
577                     push @argv, "--".$name, $value if $value;
578                 }
579
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'};
584                     }
585                 }
586
587             }
588             $directtcp = $directtcp_supported;
589             if ($directtcp_supported) {
590                 $xfer_dest = Amanda::Xfer::Dest::DirectTCPListen->new();
591                 @directtcp_command = @argv;
592             } else {
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);
597
598                 $dest_fh = \*STDOUT;
599                 $xfer_dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
600             }
601         } elsif ($opt_pipe) {
602             $dest_fh = \*STDOUT;
603             $xfer_dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
604         } else {
605             my $filename = sprintf("%s.%s.%s.%d",
606                     $hdr->{'name'},
607                     Amanda::Util::sanitise_filename("".$hdr->{'disk'}), # workaround SWIG bug
608                     $hdr->{'datestamp'},
609                     $hdr->{'dumplevel'});
610             if ($opt_no_reassembly) {
611                 $filename .= sprintf(".%07d", $hdr->{'partnum'});
612             }
613
614             # add an appropriate suffix
615             if ($opt_compress) {
616                 $filename .= ($hdr->{'compressed'} && $hdr->{'comp_suffix'})?
617                     $hdr->{'comp_suffix'} : $Amanda::Constants::COMPRESS_SUFFIX;
618             }
619
620             if (!open($dest_fh, ">", $filename)) {
621                 return failure("Could not open '$filename' for writing: $!", $finished_cb);
622             }
623             $xfer_dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
624         }
625
626         $timer = Amanda::MainLoop::timeout_source($delay);
627         $timer->set_callback(sub {
628             my $size = $xfer_src->get_bytes_read();
629             if ($is_tty) {
630                 print STDERR "\r" . int($size/1024) . " kb ";
631             } else {
632                 print STDERR "READ SIZE: " . int($size/1024) . " kb\n";
633             }
634         });
635
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'}) {
641                 push @filters,
642                     Amanda::Xfer::Filter::Process->new(
643                         [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0);
644             } elsif ($hdr->{'clnt_encrypt'}) {
645                 push @filters,
646                     Amanda::Xfer::Filter::Process->new(
647                         [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0);
648             } else {
649                 return failure("could not decrypt encrypted dump: no program specified",
650                             $finished_cb);
651             }
652
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';
659         }
660
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
673                 push @filters,
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
678                 push @filters,
679                     Amanda::Xfer::Filter::Process->new(
680                         [ $hdr->{'clntcompprog'}, "-d" ], 0);
681             } else {
682                 push @filters,
683                     Amanda::Xfer::Filter::Process->new(
684                         [ $Amanda::Constants::UNCOMPRESS_PATH,
685                           $Amanda::Constants::UNCOMPRESS_OPT ], 0);
686             }
687
688             # adjust the header
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
693
694             my $compress_opt = $opt_compress_best?
695                 $Amanda::Constants::COMPRESS_BEST_OPT :
696                 $Amanda::Constants::COMPRESS_FAST_OPT;
697             push @filters,
698                 Amanda::Xfer::Filter::Process->new(
699                     [ $Amanda::Constants::COMPRESS_PATH,
700                       $compress_opt ], 0);
701
702             # adjust the header
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;
707         }
708
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);
719             }
720             syswrite $hdr_fh, $hdr->to_string(32768, 32768), 32768;
721         }
722
723         # start reading all filter stderr
724         foreach my $filter (@filters) {
725             my $fd = $filter->get_stderr_fd();
726             $fd.="";
727             $fd = int($fd);
728             my $src = Amanda::MainLoop::fd_source($fd,
729                                                  $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
730             my $buffer = "";
731             $all_filter{$src} = 1;
732             $src->set_callback( sub {
733                 my $b;
734                 my $n_read = POSIX::read($fd, $b, 1);
735                 if (!defined $n_read) {
736                     return;
737                 } elsif ($n_read == 0) {
738                     delete $all_filter{$src};
739                     $src->remove();
740                     POSIX::close($fd);
741                     if (!%all_filter and $recovery_done) {
742                         $steps->{'filter_done'}->();
743                     }
744                 } else {
745                     $buffer .= $b;
746                     if ($b eq "\n") {
747                         my $line = $buffer;
748                         chomp $line;
749                         if (length($line) > 1) {
750                             print STDERR "filter stderr: $line\n";
751                             debug("filter stderr: $line");
752                         }
753                         $buffer = "";
754                     }
755                 }
756             });
757         }
758
759         my $xfer;
760         if (@filters) {
761             $xfer = Amanda::Xfer->new([ $xfer_src, @filters, $xfer_dest ]);
762         } else {
763             $xfer = Amanda::Xfer->new([ $xfer_src, $xfer_dest ]);
764         }
765         $xfer->start($steps->{'handle_xmsg'}, 0, $current_dump->{'bytes'});
766         $clerk->start_recovery(
767             xfer => $xfer,
768             recovery_cb => $steps->{'recovery_cb'});
769         if ($directtcp) {
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));
774
775             my ($wtr, $rdr);
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);
784
785             $amndmp_stdout_src->set_callback( sub {
786                 my $line = <$rdr>;
787                 if (!defined $line) {
788                     $file_to_close--;
789                     $amndmp_stdout_src->remove();
790                     if ($file_to_close == 0) {
791                         #abort the xfer
792                         $xfer->cancel() if $xfer->get_status != $XFER_DONE;
793                     }
794                     return;
795                 }
796                 chomp $line;
797                 debug("amndmp stdout: $line");
798                 print "$line\n";
799             });
800             $amndmp_stderr_src->set_callback( sub {
801                 my $line = <$err>;
802                 if (!defined $line) {
803                     $file_to_close--;
804                     $amndmp_stderr_src->remove();
805                     if ($file_to_close == 0) {
806                         #abort the xfer
807                         $xfer->cancel() if $xfer->get_status != $XFER_DONE;
808                     }
809                     return;
810                 }
811                 chomp $line;
812                 debug("amndmp stderr: $line");
813                 print STDERR "$line\n";
814             });
815         }
816     };
817
818     step handle_xmsg => sub {
819         my ($src, $msg, $xfer) = @_;
820
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'};
826         }
827     };
828
829     step recovery_cb => sub {
830         %recovery_params = @_;
831         $recovery_done = 1;
832
833         $steps->{'filter_done'}->() if !%all_filter;
834     };
835
836     step filter_done => sub {
837         if ($is_tty) {
838             print STDERR "\r" . int($recovery_params{'bytes_read'}/1024) . " kb ";
839         } else {
840             print STDERR "READ SIZE: " . int($recovery_params{'bytes_read'}/1024) . " kb\n";
841         }
842         @xfer_errs = (@xfer_errs, @{$recovery_params{'errors'}})
843             if $recovery_params{'errors'};
844         return failure(join("; ", @xfer_errs), $finished_cb)
845             if @xfer_errs;
846         return failure("recovery failed", $finished_cb)
847             if $recovery_params{'result'} ne 'DONE';
848
849         $steps->{'start_dump'}->();
850     };
851
852     step finished => sub {
853         if ($clerk) {
854             $clerk->quit(finished_cb => $steps->{'quit'});
855         } else {
856             $steps->{'quit'}->();
857         }
858     };
859
860     step quit => sub {
861         my ($err) = @_;
862
863         if (defined $timer) {
864             $timer->remove();
865             $timer = undef;
866         }
867         print STDERR "\n" if $is_tty;
868         return failure($err, $finished_cb) if $err;
869
870         $finished_cb->();
871     };
872 }
873
874 main(\&Amanda::MainLoop::quit);
875 Amanda::MainLoop::run();
876 Amanda::Util::finish_application();
877 exit $exit_status;