Imported Upstream version 3.3.2
[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 modify it
5 # under the terms of the GNU General Public License version 2 as published
6 # by the Free Software Foundation.
7 #
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
11 # for more details.
12 #
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
16 #
17 # Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19
20 use lib '@amperldir@';
21 use strict;
22 use warnings;
23
24 use Getopt::Long;
25
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 );
30 use Amanda::Changer;
31 use Amanda::Constants;
32 use Amanda::MainLoop;
33 use Amanda::Header;
34 use Amanda::Holding;
35 use Amanda::Cmdline;
36 use Amanda::Xfer qw( :constants );
37 use Amanda::Recovery::Planner;
38 use Amanda::Recovery::Clerk;
39 use Amanda::Recovery::Scan;
40
41 # Interactivity package
42 package Amanda::Interactivity::amfetchdump;
43 use POSIX qw( :errno_h );
44 use Amanda::MainLoop qw( :GIOCondition );
45 use vars qw( @ISA );
46 @ISA = qw( Amanda::Interactivity );
47
48 sub new {
49     my $class = shift;
50
51     my $self = {
52         input_src => undef};
53     return bless ($self, $class);
54 }
55
56 sub abort() {
57     my $self = shift;
58
59     if ($self->{'input_src'}) {
60         $self->{'input_src'}->remove();
61         $self->{'input_src'} = undef;
62     }
63 }
64
65 sub user_request {
66     my $self = shift;
67     my %params = @_;
68     my $buffer = "";
69
70     my $message  = $params{'message'};
71     my $label    = $params{'label'};
72     my $err      = $params{'err'};
73     my $chg_name = $params{'chg_name'};
74
75     my $data_in = sub {
76         my $b;
77         my $n_read = POSIX::read(0, $b, 1);
78         if (!defined $n_read) {
79             return if ($! == EINTR);
80             $self->abort();
81             return $params{'request_cb'}->(
82                 Amanda::Changer::Error->new('fatal',
83                         message => "Fail to read from stdin"));
84         } elsif ($n_read == 0) {
85             $self->abort();
86             return $params{'request_cb'}->(
87                 Amanda::Changer::Error->new('fatal',
88                         message => "Aborted by user"));
89         } else {
90             $buffer .= $b;
91             if ($b eq "\n") {
92                 my $line = $buffer;
93                 chomp $line;
94                 $buffer = "";
95                 $self->abort();
96                 return $params{'request_cb'}->(undef, $line);
97             }
98         }
99     };
100
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";
104
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);
107     return;
108 };
109
110 package main;
111
112 sub usage {
113     my ($msg) = @_;
114     print STDERR <<EOF;
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 ... ]]]]]
121 EOF
122     print STDERR "ERROR: $msg\n" if $msg;
123     exit(1);
124 }
125
126 ##
127 # main
128
129 Amanda::Util::setup_application("amfetchdump", "server", $CONTEXT_CMDLINE);
130
131 my $config_overrides = new_config_overrides($#ARGV+1);
132
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);
138
139 my $NEVER = 0;
140 my $ALWAYS = 1;
141 my $ONLY_SERVER = 2;
142 my $ONLY_CLIENT = 3;
143 my $decrypt;
144 my $decompress;
145
146 debug("Arguments: " . join(' ', @ARGV));
147 Getopt::Long::Configure(qw(bundling));
148 GetOptions(
149     'version' => \&Amanda::Util::version_opt,
150     'help|usage|?' => \&usage,
151     'n' => \$opt_no_reassembly,
152     'c' => \$opt_compress,
153     'C' => \$opt_compress_best,
154     'p' => \$opt_pipe,
155     'a' => \$opt_assume,
156     'l' => \$opt_leave,
157     'h' => \$opt_header,
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]); },
170 ) or usage();
171 usage() unless (@ARGV);
172 $opt_config = shift @ARGV;
173
174 if (defined $opt_compress and defined $opt_compress_best) {
175     print STDERR "Can't use -c and -C\n";
176     usage();
177 }
178
179 usage("The -b option is no longer supported; set readblocksize in the tapetype section\n" .
180       "of amanda.conf instead.")
181     if ($opt_blocksize);
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));
189
190 if (defined $opt_leave) {
191     if (defined $opt_decrypt and $opt_decrypt) {
192         print STDERR "-l is incompatible with --decrypt\n";
193         usage();
194     }
195     if (defined $opt_server_decrypt) {
196         print STDERR "-l is incompatible with --server-decrypt\n";
197         usage();
198     }
199     if (defined $opt_client_decrypt) {
200         print STDERR "-l is incompatible with --client-decrypt\n";
201         usage();
202     }
203     if (defined $opt_decompress and $opt_decompress) {
204         print STDERR "-l is incompatible with --decompress\n";
205         usage();
206     }
207     if (defined $opt_server_decompress) {
208         print STDERR "-l is incompatible with --server-decompress\n";
209         usage();
210     }
211     if (defined $opt_client_decompress) {
212         print STDERR "-l is incompatible with --client-decompress\n";
213         usage();
214     }
215 }
216
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";
221     usage();
222 }
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";
227     usage();
228 }
229
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";
235     usage();
236 }
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";
242     usage();
243 }
244
245 $decompress = $ALWAYS;
246 $decrypt = $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;
252
253 $opt_compress = 1 if $opt_compress_best;
254
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;
261
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);
265
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");
273     }
274 }
275
276 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
277
278 my $exit_status = 0;
279 my $clerk;
280 sub failure {
281     my ($msg, $finished_cb) = @_;
282     print STDERR "ERROR: $msg\n";
283     $exit_status = 1;
284     if ($clerk) {
285         $clerk->quit(finished_cb => sub {
286             # ignore error
287             $finished_cb->();
288         });
289     } else {
290         $finished_cb->();
291     }
292 }
293
294 package main::Feedback;
295
296 use base 'Amanda::Recovery::Clerk::Feedback';
297 use Amanda::MainLoop;
298
299 sub new {
300     my $class = shift;
301     my ($chg, $dev_name, $is_tty) = @_;
302
303     return bless {
304         chg => $chg,
305         dev_name => $dev_name,
306         is_tty => $is_tty,
307     }, $class;
308 }
309
310 sub clerk_notif_part {
311     my $self = shift;
312     my ($label, $filenum, $header) = @_;
313
314     print STDERR "\n" if $self->{'is_tty'};
315     print STDERR "amfetchdump: $filenum: restoring ", $header->summary(), "\n";
316 }
317
318 sub clerk_notif_holding {
319     my $self = shift;
320     my ($filename, $header) = @_;
321
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";
325 }
326
327 package main;
328
329 use Amanda::MainLoop qw( :GIOCondition );
330 sub main {
331     my ($finished_cb) = @_;
332     my $current_dump;
333     my $plan;
334     my @xfer_errs;
335     my %all_filter;
336     my $recovery_done;
337     my %recovery_params;
338     my $timer;
339     my $is_tty;
340     my $delay;
341
342     my $steps = define_steps
343         cb_ref => \$finished_cb;
344
345     step start => sub {
346         my $chg;
347
348         # first, go to opt_directory or the original working directory we
349         # were started in
350         my $destdir = $opt_chdir || Amanda::Util::get_original_cwd();
351         if (!chdir($destdir)) {
352             return failure("Cannot chdir to $destdir: $!", $finished_cb);
353         }
354
355         $is_tty = -t STDERR;
356         if($is_tty) {
357             $delay = 1000; # 1 second
358         } else {
359             $delay = 5000; # 5 seconds
360         }
361
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(
369                                 chg => $chg,
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),
374                 scan     => $scan);
375         } else {
376             my $scan = Amanda::Recovery::Scan->new(
377                                 interactivity => $interactivity);
378             return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
379
380             $clerk = Amanda::Recovery::Clerk->new(
381                 changer => $chg,
382                 feedback => main::Feedback->new($chg, undef, $is_tty),
383                 scan     => $scan);
384         }
385
386         # planner gets to plan against the same changer the user specified
387         Amanda::Recovery::Planner::make_plan(
388             dumpspecs => [ @opt_dumpspecs ],
389             changer => $chg,
390             plan_cb => $steps->{'plan_cb'},
391             $opt_no_reassembly? (one_dump_per_part => 1) : ());
392     };
393
394     step plan_cb => sub {
395         (my $err, $plan) = @_;
396         return failure($err, $finished_cb) if $err;
397
398         if (!@{$plan->{'dumps'}}) {
399             return failure("No matching dumps found", $finished_cb);
400         }
401
402         # if we are doing a -p operation, only keep the first dump
403         if ($opt_pipe) {
404             print STDERR "WARNING: Fetch first dump only because of -p argument\n" if @{$plan->{'dumps'}} > 1;
405             @{$plan->{'dumps'}} = ($plan->{'dumps'}[0]);
406         }
407
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";
414         }
415         if (@needed_holding) {
416             print STDERR (scalar @needed_holding), " holding file(s) needed for restoration\n";
417             for my $hf (@needed_holding) {
418                 print "  $hf\n";
419             }
420         }
421
422         unless ($opt_assume) {
423             print STDERR "Press enter when ready\n";
424             my $resp = <STDIN>;
425         }
426
427         $steps->{'start_dump'}->();
428     };
429
430     step start_dump => sub {
431         $current_dump = shift @{$plan->{'dumps'}};
432
433         if (!$current_dump) {
434             return $steps->{'finished'}->();
435         }
436
437         $recovery_done = 0;
438         %recovery_params = ();
439
440         $clerk->get_xfer_src(
441             dump => $current_dump,
442             xfer_src_cb => $steps->{'xfer_src_cb'});
443     };
444
445     step xfer_src_cb => sub {
446         my ($errs, $hdr, $xfer_src, $directtcp_supported) = @_;
447         return failure(join("; ", @$errs), $finished_cb) if $errs;
448
449         # and set up the destination..
450         my $dest_fh;
451         if ($opt_pipe) {
452             $dest_fh = \*STDOUT;
453         } else {
454             my $filename = sprintf("%s.%s.%s.%d",
455                     $hdr->{'name'},
456                     Amanda::Util::sanitise_filename("".$hdr->{'disk'}), # workaround SWIG bug
457                     $hdr->{'datestamp'},
458                     $hdr->{'dumplevel'});
459             if ($opt_no_reassembly) {
460                 $filename .= sprintf(".%07d", $hdr->{'partnum'});
461             }
462
463             # add an appropriate suffix
464             if ($opt_compress) {
465                 $filename .= ($hdr->{'compressed'} && $hdr->{'comp_suffix'})?
466                     $hdr->{'comp_suffix'} : $Amanda::Constants::COMPRESS_SUFFIX;
467             }
468
469             if (!open($dest_fh, ">", $filename)) {
470                 return failure("Could not open '$filename' for writing: $!", $finished_cb);
471             }
472         }
473
474         $timer = Amanda::MainLoop::timeout_source($delay);
475         $timer->set_callback(sub {
476             my $size = $xfer_src->get_bytes_read();
477             if ($is_tty) {
478                 print STDERR "\r" . int($size/1024) . " kb ";
479             } else {
480                 print STDERR "READ SIZE: " . int($size/1024) . " kb\n";
481             }
482         });
483
484         my $xfer_dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
485
486         my $dle = $hdr->get_dle();
487
488         # set up any filters that need to be applied; decryption first
489         my @filters;
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'}) {
494                 push @filters,
495                     Amanda::Xfer::Filter::Process->new(
496                         [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0);
497             } elsif ($hdr->{'clnt_encrypt'}) {
498                 push @filters,
499                     Amanda::Xfer::Filter::Process->new(
500                         [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0);
501             } else {
502                 return failure("could not decrypt encrypted dump: no program specified",
503                             $finished_cb);
504             }
505
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';
512         }
513
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
526                 push @filters,
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
531                 push @filters,
532                     Amanda::Xfer::Filter::Process->new(
533                         [ $hdr->{'clntcompprog'}, "-d" ], 0);
534             } else {
535                 push @filters,
536                     Amanda::Xfer::Filter::Process->new(
537                         [ $Amanda::Constants::UNCOMPRESS_PATH,
538                           $Amanda::Constants::UNCOMPRESS_OPT ], 0);
539             }
540
541             # adjust the header
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
546
547             my $compress_opt = $opt_compress_best?
548                 $Amanda::Constants::COMPRESS_BEST_OPT :
549                 $Amanda::Constants::COMPRESS_FAST_OPT;
550             push @filters,
551                 Amanda::Xfer::Filter::Process->new(
552                     [ $Amanda::Constants::COMPRESS_PATH,
553                       $compress_opt ], 0);
554
555             # adjust the header
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;
560         }
561
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);
572             }
573             syswrite $hdr_fh, $hdr->to_string(32768, 32768), 32768;
574         }
575
576         # start reading all filter stderr
577         foreach my $filter (@filters) {
578             my $fd = $filter->get_stderr_fd();
579             $fd.="";
580             $fd = int($fd);
581             my $src = Amanda::MainLoop::fd_source($fd,
582                                                  $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
583             my $buffer = "";
584             $all_filter{$src} = 1;
585             $src->set_callback( sub {
586                 my $b;
587                 my $n_read = POSIX::read($fd, $b, 1);
588                 if (!defined $n_read) {
589                     return;
590                 } elsif ($n_read == 0) {
591                     delete $all_filter{$src};
592                     $src->remove();
593                     POSIX::close($fd);
594                     if (!%all_filter and $recovery_done) {
595                         $steps->{'filter_done'}->();
596                     }
597                 } else {
598                     $buffer .= $b;
599                     if ($b eq "\n") {
600                         my $line = $buffer;
601                         print STDERR "filter stderr: $line";
602                         chomp $line;
603                         debug("filter stderr: $line");
604                         $buffer = "";
605                     }
606                 }
607             });
608         }
609
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(
613             xfer => $xfer,
614             recovery_cb => $steps->{'recovery_cb'});
615     };
616
617     step handle_xmsg => sub {
618         my ($src, $msg, $xfer) = @_;
619
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'};
625         }
626     };
627
628     step recovery_cb => sub {
629         %recovery_params = @_;
630         $recovery_done = 1;
631
632         $steps->{'filter_done'}->() if !%all_filter;
633     };
634
635     step filter_done => sub {
636         if ($is_tty) {
637             print STDERR "\r" . int($recovery_params{'bytes_read'}/1024) . " kb ";
638         } else {
639             print STDERR "READ SIZE: " . int($recovery_params{'bytes_read'}/1024) . " kb\n";
640         }
641         @xfer_errs = (@xfer_errs, @{$recovery_params{'errors'}})
642             if $recovery_params{'errors'};
643         return failure(join("; ", @xfer_errs), $finished_cb)
644             if @xfer_errs;
645         return failure("recovery failed", $finished_cb)
646             if $recovery_params{'result'} ne 'DONE';
647
648         $steps->{'start_dump'}->();
649     };
650
651     step finished => sub {
652         if ($clerk) {
653             $clerk->quit(finished_cb => $steps->{'quit'});
654         } else {
655             $steps->{'quit'}->();
656         }
657     };
658
659     step quit => sub {
660         my ($err) = @_;
661
662         if (defined $timer) {
663             $timer->remove();
664             $timer = undef;
665         }
666         print STDERR "\n" if $is_tty;
667         return failure($err, $finished_cb) if $err;
668
669         $finished_cb->();
670     };
671 }
672
673 main(\&Amanda::MainLoop::quit);
674 Amanda::MainLoop::run();
675 Amanda::Util::finish_application();
676 exit $exit_status;