2 # Copyright (c) 2009, 2010 Zmanda, Inc. All Rights Reserved.
4 # This program is free software; you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License version 2 as published
6 # by the Free Software Foundation.
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 # Contact information: Zmanda Inc., 465 S 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] [-o configoption]* config
117 hostname [diskname [datestamp [hostname [diskname [datestamp ... ]]]]]
119 print STDERR "ERROR: $msg\n" if $msg;
126 Amanda::Util::setup_application("amfetchdump", "server", $CONTEXT_CMDLINE);
128 my $config_overrides = new_config_overrides($#ARGV+1);
130 my ($opt_config, $opt_no_reassembly, $opt_compress, $opt_compress_best, $opt_pipe,
131 $opt_assume, $opt_leave, $opt_blocksize, $opt_device, $opt_chdir, $opt_header,
132 $opt_header_file, $opt_header_fd, @opt_dumpspecs);
134 debug("Arguments: " . join(' ', @ARGV));
135 Getopt::Long::Configure(qw(bundling));
137 'version' => \&Amanda::Util::version_opt,
138 'help|usage|?' => \&usage,
139 'n' => \$opt_no_reassembly,
140 'c' => \$opt_compress,
141 'C' => \$opt_compress_best,
146 'header-file=s' => \$opt_header_file,
147 'header-fd=i' => \$opt_header_fd,
148 'b=s' => \$opt_blocksize,
149 'd=s' => \$opt_device,
150 'O=s' => \$opt_chdir,
151 'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
153 usage() unless (@ARGV);
154 $opt_config = shift @ARGV;
156 $opt_compress = 1 if $opt_compress_best;
158 usage("must specify at least a hostname") unless @ARGV;
159 @opt_dumpspecs = Amanda::Cmdline::parse_dumpspecs([@ARGV],
160 $Amanda::Cmdline::CMDLINE_PARSE_DATESTAMP | $Amanda::Cmdline::CMDLINE_PARSE_LEVEL);
162 usage("The -b option is no longer supported; set readblocksize in the tapetype section\n" .
163 "of amanda.conf instead.")
165 usage("-l is not compatible with -c or -C")
166 if ($opt_leave and $opt_compress);
167 usage("-p is not compatible with -n")
168 if ($opt_leave and $opt_no_reassembly);
169 usage("-h, --header-file, and --header-fd are mutually incompatible")
170 if (($opt_header and $opt_header_file or $opt_header_fd)
171 or ($opt_header_file and $opt_header_fd));
173 set_config_overrides($config_overrides);
174 config_init($CONFIG_INIT_EXPLICIT_NAME, $opt_config);
175 my ($cfgerr_level, @cfgerr_errors) = config_errors();
176 if ($cfgerr_level >= $CFGERR_WARNINGS) {
177 config_print_errors();
178 if ($cfgerr_level >= $CFGERR_ERRORS) {
179 die("errors processing config file");
183 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
188 my ($msg, $finished_cb) = @_;
189 print STDERR "ERROR: $msg\n";
192 $clerk->quit(finished_cb => sub {
201 package main::Feedback;
203 use base 'Amanda::Recovery::Clerk::Feedback';
204 use Amanda::MainLoop;
208 my ($chg, $dev_name) = @_;
212 dev_name => $dev_name,
216 sub clerk_notif_part {
218 my ($label, $filenum, $header) = @_;
220 print STDERR "amfetchdump: $filenum: restoring ", $header->summary(), "\n";
223 sub clerk_notif_holding {
225 my ($filename, $header) = @_;
227 # this used to give the fd from which the holding file was being read.. why??
228 print STDERR "Reading '$filename'\n", $header->summary(), "\n";
233 use Amanda::MainLoop qw( :GIOCondition );
235 my ($finished_cb) = @_;
242 my $steps = define_steps
243 cb_ref => \$finished_cb;
248 # first, go to opt_directory or the original working directory we
250 my $destdir = $opt_chdir || Amanda::Util::get_original_cwd();
251 if (!chdir($destdir)) {
252 return failure("Cannot chdir to $destdir: $!", $finished_cb);
255 my $interactivity = Amanda::Interactivity::amfetchdump->new();
256 # if we have an explicit device, then the clerk doesn't get a changer --
257 # we operate the changer via Amanda::Recovery::Scan
258 if (defined $opt_device) {
259 $chg = Amanda::Changer->new($opt_device);
260 return failure($chg, $finished_cb) if $chg->isa("Amanda::Changer::Error");
261 my $scan = Amanda::Recovery::Scan->new(
263 interactivity => $interactivity);
264 return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
265 $clerk = Amanda::Recovery::Clerk->new(
266 feedback => main::Feedback->new($chg, $opt_device),
269 my $scan = Amanda::Recovery::Scan->new(
270 interactivity => $interactivity);
271 return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
273 $clerk = Amanda::Recovery::Clerk->new(
275 feedback => main::Feedback->new($chg, undef),
279 # planner gets to plan against the same changer the user specified
280 Amanda::Recovery::Planner::make_plan(
281 dumpspecs => [ @opt_dumpspecs ],
283 plan_cb => $steps->{'plan_cb'},
284 $opt_no_reassembly? (one_dump_per_part => 1) : ());
287 step plan_cb => sub {
288 (my $err, $plan) = @_;
289 return failure($err, $finished_cb) if $err;
291 if (!@{$plan->{'dumps'}}) {
292 return failure("No matching dumps found", $finished_cb);
295 # if we are doing a -p operation, only keep the first dump
297 @{$plan->{'dumps'}} = ($plan->{'dumps'}[0]);
300 my @needed_labels = $plan->get_volume_list();
301 my @needed_holding = $plan->get_holding_file_list();
302 if (@needed_labels) {
303 print STDERR (scalar @needed_labels), " volume(s) needed for restoration\n";
304 print STDERR "The following volumes are needed: ",
305 join(" ", map { $_->{'label'} } @needed_labels ), "\n";
307 if (@needed_holding) {
308 print STDERR (scalar @needed_holding), " holding file(s) needed for restoration\n";
309 for my $hf (@needed_holding) {
314 unless ($opt_assume) {
315 print STDERR "Press enter when ready\n";
319 $steps->{'start_dump'}->();
322 step start_dump => sub {
323 $current_dump = shift @{$plan->{'dumps'}};
324 if (!$current_dump) {
325 return $steps->{'finished'}->();
328 $clerk->get_xfer_src(
329 dump => $current_dump,
330 xfer_src_cb => $steps->{'xfer_src_cb'});
333 step xfer_src_cb => sub {
334 my ($errs, $hdr, $xfer_src, $directtcp_supported) = @_;
335 return failure(join("; ", @$errs), $finished_cb) if $errs;
337 # and set up the destination..
342 my $filename = sprintf("%s.%s.%s.%d",
344 Amanda::Util::sanitise_filename("".$hdr->{'disk'}), # workaround SWIG bug
346 $hdr->{'dumplevel'});
347 if ($opt_no_reassembly) {
348 $filename .= sprintf(".%07d", $hdr->{'partnum'});
351 # add an appropriate suffix
353 $filename .= ($hdr->{'compressed'} && $hdr->{'comp_suffix'})?
354 $hdr->{'comp_suffix'} : $Amanda::Constants::COMPRESS_SUFFIX;
357 if (!open($dest_fh, ">", $filename)) {
358 return failure("Could not open '$filename' for writing: $!", $finished_cb);
362 my $xfer_dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
364 # set up any filters that need to be applied; decryption first
366 if ($hdr->{'encrypted'} and not $opt_leave) {
367 if ($hdr->{'srv_encrypt'}) {
369 Amanda::Xfer::Filter::Process->new(
370 [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0);
371 } elsif ($hdr->{'clnt_encrypt'}) {
373 Amanda::Xfer::Filter::Process->new(
374 [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0);
376 return failure("could not decrypt encrypted dump: no program specified",
380 $hdr->{'encrypted'} = 0;
381 $hdr->{'srv_encrypt'} = '';
382 $hdr->{'srv_decrypt_opt'} = '';
383 $hdr->{'clnt_encrypt'} = '';
384 $hdr->{'clnt_decrypt_opt'} = '';
385 $hdr->{'encrypt_suffix'} = 'N';
388 if ($hdr->{'compressed'} and not $opt_compress and not $opt_leave) {
389 # need to uncompress this file
391 if ($hdr->{'srvcompprog'}) {
392 # TODO: this assumes that srvcompprog takes "-d" to decrypt
394 Amanda::Xfer::Filter::Process->new(
395 [ $hdr->{'srvcompprog'}, "-d" ], 0);
396 } elsif ($hdr->{'clntcompprog'}) {
397 # TODO: this assumes that clntcompprog takes "-d" to decrypt
399 Amanda::Xfer::Filter::Process->new(
400 [ $hdr->{'clntcompprog'}, "-d" ], 0);
403 Amanda::Xfer::Filter::Process->new(
404 [ $Amanda::Constants::UNCOMPRESS_PATH,
405 $Amanda::Constants::UNCOMPRESS_OPT ], 0);
409 $hdr->{'compressed'} = 0;
410 $hdr->{'uncompress_cmd'} = '';
411 } elsif (!$hdr->{'compressed'} and $opt_compress and not $opt_leave) {
412 # need to compress this file
414 my $compress_opt = $opt_compress_best?
415 $Amanda::Constants::COMPRESS_BEST_OPT :
416 $Amanda::Constants::COMPRESS_FAST_OPT;
418 Amanda::Xfer::Filter::Process->new(
419 [ $Amanda::Constants::COMPRESS_PATH,
423 $hdr->{'compressed'} = 1;
424 $hdr->{'uncompress_cmd'} = " $Amanda::Constants::UNCOMPRESS_PATH " .
425 "$Amanda::Constants::UNCOMPRESS_OPT |";
426 $hdr->{'comp_suffix'} = $Amanda::Constants::COMPRESS_SUFFIX;
429 # write the header to the destination if requested
430 $hdr->{'blocksize'} = Amanda::Holding::DISK_BLOCK_BYTES;
431 if (defined $opt_header or defined $opt_header_file or defined $opt_header_fd) {
432 my $hdr_fh = $dest_fh;
433 if (defined $opt_header_file) {
434 open($hdr_fh, ">", $opt_header_file)
435 or return failure("could not open '$opt_header_file': $!", $finished_cb);
436 } elsif (defined $opt_header_fd) {
437 open($hdr_fh, "<&".($opt_header_fd+0))
438 or return failure("could not open fd $opt_header_fd: $!", $finished_cb);
440 syswrite $hdr_fh, $hdr->to_string(32768, 32768), 32768;
443 # start reading all filter stderr
444 foreach my $filter (@filters) {
445 my $fd = $filter->get_stderr_fd();
448 my $src = Amanda::MainLoop::fd_source($fd,
449 $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
451 $all_filter{$src} = 1;
452 $src->set_callback( sub {
454 my $n_read = POSIX::read($fd, $b, 1);
455 if (!defined $n_read) {
457 } elsif ($n_read == 0) {
458 delete $all_filter{$src};
461 if (!%all_filter and $fetch_done) {
468 print STDERR "filter stderr: $line";
470 debug("filter stderr: $line");
477 my $xfer = Amanda::Xfer->new([ $xfer_src, @filters, $xfer_dest ]);
478 $xfer->start($steps->{'handle_xmsg'}, 0, $current_dump->{'bytes'});
479 $clerk->start_recovery(
481 recovery_cb => $steps->{'recovery_cb'});
484 step handle_xmsg => sub {
485 my ($src, $msg, $xfer) = @_;
487 $clerk->handle_xmsg($src, $msg, $xfer);
488 if ($msg->{'type'} == $XMSG_INFO) {
489 Amanda::Debug::info($msg->{'message'});
490 } elsif ($msg->{'type'} == $XMSG_ERROR) {
491 push @xfer_errs, $msg->{'message'};
495 step recovery_cb => sub {
498 @xfer_errs = (@xfer_errs, @{$params{'errors'}})
499 if $params{'errors'};
500 return failure(join("; ", @xfer_errs), $finished_cb)
502 return failure("recovery failed", $finished_cb)
503 if $params{'result'} ne 'DONE';
505 $steps->{'start_dump'}->();
508 step finished => sub {
510 $clerk->quit(finished_cb => $steps->{'quit'});
512 $steps->{'quit'}->();
519 return failure($err, $finished_cb) if $err;
521 #do all filter are done reading stderr
529 main(\&Amanda::MainLoop::quit);
530 Amanda::MainLoop::run();
531 Amanda::Util::finish_application();