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 config [-c|-C|-l] [-p|-n] [-a]
116 [-O directory] [-d device] [-o configoption]*
117 [--header-file file] [--header-fd fd]
118 hostname [diskname [datestamp [hostname [diskname [datestamp ... ]]]]]"));
120 print STDERR "ERROR: $msg\n" if $msg;
127 Amanda::Util::setup_application("amfetchdump", "server", $CONTEXT_CMDLINE);
129 my $config_overrides = new_config_overrides($#ARGV+1);
131 my ($opt_config, $opt_no_reassembly, $opt_compress, $opt_compress_best, $opt_pipe,
132 $opt_assume, $opt_leave, $opt_blocksize, $opt_device, $opt_chdir, $opt_header,
133 $opt_header_file, $opt_header_fd, @opt_dumpspecs);
134 Getopt::Long::Configure(qw(bundling));
136 'version' => \&Amanda::Util::version_opt,
137 'help|usage|?' => \&usage,
138 'n' => \$opt_no_reassembly,
139 'c' => \$opt_compress,
140 'C' => \$opt_compress_best,
145 'header-file=s' => \$opt_header_file,
146 'header-fd=i' => \$opt_header_fd,
147 'b=s' => \$opt_blocksize,
148 'd=s' => \$opt_device,
149 'O=s' => \$opt_chdir,
150 'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
152 usage() unless (@ARGV);
153 $opt_config = shift @ARGV;
155 $opt_compress = 1 if $opt_compress_best;
157 usage("must specify at least a hostname") unless @ARGV;
158 @opt_dumpspecs = Amanda::Cmdline::parse_dumpspecs([@ARGV],
159 $Amanda::Cmdline::CMDLINE_PARSE_DATESTAMP | $Amanda::Cmdline::CMDLINE_PARSE_LEVEL);
161 usage("The -b option is no longer supported; set readblocksize in the tapetype section\n" .
162 "of amanda.conf instead.")
164 usage("-l is not compatible with -c or -C")
165 if ($opt_leave and $opt_compress);
166 usage("-p is not compatible with -n")
167 if ($opt_leave and $opt_no_reassembly);
168 usage("-h, --header-file, and --header-fd are mutually incompatible")
169 if (($opt_header and $opt_header_file or $opt_header_fd)
170 or ($opt_header_file and $opt_header_fd));
172 set_config_overrides($config_overrides);
173 config_init($CONFIG_INIT_EXPLICIT_NAME, $opt_config);
174 my ($cfgerr_level, @cfgerr_errors) = config_errors();
175 if ($cfgerr_level >= $CFGERR_WARNINGS) {
176 config_print_errors();
177 if ($cfgerr_level >= $CFGERR_ERRORS) {
178 die("errors processing config file");
182 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
187 my ($msg, $finished_cb) = @_;
188 print STDERR "ERROR: $msg\n";
191 $clerk->quit(finished_cb => sub {
200 package main::Feedback;
202 use base 'Amanda::Recovery::Clerk::Feedback';
203 use Amanda::MainLoop;
207 my ($chg, $dev_name) = @_;
211 dev_name => $dev_name,
215 sub clerk_notif_part {
217 my ($label, $filenum, $header) = @_;
219 print STDERR "amfetchdump: $filenum: restoring ", $header->summary(), "\n";
222 sub clerk_notif_holding {
224 my ($filename, $header) = @_;
226 # this used to give the fd from which the holding file was being read.. why??
227 print STDERR "Reading '$filename'\n", $header->summary(), "\n";
232 use Amanda::MainLoop qw( :GIOCondition );
234 my ($finished_cb) = @_;
241 my $steps = define_steps
242 cb_ref => \$finished_cb;
247 # first, go to opt_directory or the original working directory we
249 my $destdir = $opt_chdir || Amanda::Util::get_original_cwd();
250 if (!chdir($destdir)) {
251 return failure("Cannot chdir to $destdir: $!", $finished_cb);
254 my $interactivity = Amanda::Interactivity::amfetchdump->new();
255 # if we have an explicit device, then the clerk doesn't get a changer --
256 # we operate the changer via Amanda::Recovery::Scan
257 if (defined $opt_device) {
258 $chg = Amanda::Changer->new($opt_device);
259 return failure($chg, $finished_cb) if $chg->isa("Amanda::Changer::Error");
260 my $scan = Amanda::Recovery::Scan->new(
262 interactivity => $interactivity);
263 return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
264 $clerk = Amanda::Recovery::Clerk->new(
265 feedback => main::Feedback->new($chg, $opt_device),
268 my $scan = Amanda::Recovery::Scan->new(
269 interactivity => $interactivity);
270 return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
272 $clerk = Amanda::Recovery::Clerk->new(
274 feedback => main::Feedback->new($chg, undef),
278 # planner gets to plan against the same changer the user specified
279 Amanda::Recovery::Planner::make_plan(
280 dumpspecs => [ @opt_dumpspecs ],
282 plan_cb => $steps->{'plan_cb'},
283 $opt_no_reassembly? (one_dump_per_part => 1) : ());
286 step plan_cb => sub {
287 (my $err, $plan) = @_;
288 return failure($err, $finished_cb) if $err;
290 if (!@{$plan->{'dumps'}}) {
291 return failure("No matching dumps found", $finished_cb);
294 # if we are doing a -p operation, only keep the first dump
296 @{$plan->{'dumps'}} = ($plan->{'dumps'}[0]);
299 my @needed_labels = $plan->get_volume_list();
300 my @needed_holding = $plan->get_holding_file_list();
301 if (@needed_labels) {
302 print STDERR (scalar @needed_labels), " volume(s) needed for restoration\n";
303 print STDERR "The following volumes are needed: ",
304 join(" ", map { $_->{'label'} } @needed_labels ), "\n";
306 if (@needed_holding) {
307 print STDERR (scalar @needed_holding), " holding file(s) needed for restoration\n";
308 for my $hf (@needed_holding) {
313 unless ($opt_assume) {
314 print STDERR "Press enter when ready\n";
318 $steps->{'start_dump'}->();
321 step start_dump => sub {
322 $current_dump = shift @{$plan->{'dumps'}};
323 if (!$current_dump) {
324 return $steps->{'finished'}->();
327 $clerk->get_xfer_src(
328 dump => $current_dump,
329 xfer_src_cb => $steps->{'xfer_src_cb'});
332 step xfer_src_cb => sub {
333 my ($errs, $hdr, $xfer_src, $directtcp_supported) = @_;
334 return failure(join("; ", @$errs), $finished_cb) if $errs;
336 # and set up the destination..
341 my $filename = sprintf("%s.%s.%s.%d",
343 Amanda::Util::sanitise_filename("".$hdr->{'disk'}), # workaround SWIG bug
345 $hdr->{'dumplevel'});
346 if ($opt_no_reassembly) {
347 $filename .= sprintf(".%07d", $hdr->{'partnum'});
350 # add an appropriate suffix
352 $filename .= ($hdr->{'compressed'} && $hdr->{'comp_suffix'})?
353 $hdr->{'comp_suffix'} : $Amanda::Constants::COMPRESS_SUFFIX;
356 if (!open($dest_fh, ">", $filename)) {
357 return failure("Could not open '$filename' for writing: $!", $finished_cb);
361 my $xfer_dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
363 # set up any filters that need to be applied; decryption first
365 if ($hdr->{'encrypted'} and not $opt_leave) {
366 if ($hdr->{'srv_encrypt'}) {
368 Amanda::Xfer::Filter::Process->new(
369 [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0);
370 } elsif ($hdr->{'clnt_encrypt'}) {
372 Amanda::Xfer::Filter::Process->new(
373 [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0);
375 return failure("could not decrypt encrypted dump: no program specified",
379 $hdr->{'encrypted'} = 0;
380 $hdr->{'srv_encrypt'} = '';
381 $hdr->{'srv_decrypt_opt'} = '';
382 $hdr->{'clnt_encrypt'} = '';
383 $hdr->{'clnt_decrypt_opt'} = '';
384 $hdr->{'encrypt_suffix'} = 'N';
387 if ($hdr->{'compressed'} and not $opt_compress and not $opt_leave) {
388 # need to uncompress this file
390 if ($hdr->{'srvcompprog'}) {
391 # TODO: this assumes that srvcompprog takes "-d" to decrypt
393 Amanda::Xfer::Filter::Process->new(
394 [ $hdr->{'srvcompprog'}, "-d" ], 0);
395 } elsif ($hdr->{'clntcompprog'}) {
396 # TODO: this assumes that clntcompprog takes "-d" to decrypt
398 Amanda::Xfer::Filter::Process->new(
399 [ $hdr->{'clntcompprog'}, "-d" ], 0);
402 Amanda::Xfer::Filter::Process->new(
403 [ $Amanda::Constants::UNCOMPRESS_PATH,
404 $Amanda::Constants::UNCOMPRESS_OPT ], 0);
408 $hdr->{'compressed'} = 0;
409 $hdr->{'uncompress_cmd'} = '';
410 } elsif (!$hdr->{'compressed'} and $opt_compress and not $opt_leave) {
411 # need to compress this file
413 my $compress_opt = $opt_compress_best?
414 $Amanda::Constants::COMPRESS_BEST_OPT :
415 $Amanda::Constants::COMPRESS_FAST_OPT;
417 Amanda::Xfer::Filter::Process->new(
418 [ $Amanda::Constants::COMPRESS_PATH,
422 $hdr->{'compressed'} = 1;
423 $hdr->{'uncompress_cmd'} = " $Amanda::Constants::UNCOMPRESS_PATH " .
424 "$Amanda::Constants::UNCOMPRESS_OPT |";
425 $hdr->{'comp_suffix'} = $Amanda::Constants::COMPRESS_SUFFIX;
428 # write the header to the destination if requested
429 $hdr->{'blocksize'} = Amanda::Holding::DISK_BLOCK_BYTES;
430 if (defined $opt_header or defined $opt_header_file or defined $opt_header_fd) {
431 my $hdr_fh = $dest_fh;
432 if (defined $opt_header_file) {
433 open($hdr_fh, ">", $opt_header_file)
434 or return failure("could not open '$opt_header_file': $!", $finished_cb);
435 } elsif (defined $opt_header_fd) {
436 open($hdr_fh, "<&".($opt_header_fd+0))
437 or return failure("could not open fd $opt_header_fd: $!", $finished_cb);
439 syswrite $hdr_fh, $hdr->to_string(32768, 32768), 32768;
442 # start reading all filter stderr
443 foreach my $filter (@filters) {
444 my $fd = $filter->get_stderr_fd();
447 my $src = Amanda::MainLoop::fd_source($fd,
448 $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
450 $all_filter{$src} = 1;
451 $src->set_callback( sub {
453 my $n_read = POSIX::read($fd, $b, 1);
454 if (!defined $n_read) {
456 } elsif ($n_read == 0) {
457 delete $all_filter{$src};
460 if (!%all_filter and $fetch_done) {
467 print STDERR "filter stderr: $line";
469 debug("filter stderr: $line");
476 my $xfer = Amanda::Xfer->new([ $xfer_src, @filters, $xfer_dest ]);
477 $xfer->start($steps->{'handle_xmsg'}, 0, $current_dump->{'bytes'});
478 $clerk->start_recovery(
480 recovery_cb => $steps->{'recovery_cb'});
483 step handle_xmsg => sub {
484 my ($src, $msg, $xfer) = @_;
486 $clerk->handle_xmsg($src, $msg, $xfer);
487 if ($msg->{'type'} == $XMSG_INFO) {
488 Amanda::Debug::info($msg->{'message'});
489 } elsif ($msg->{'type'} == $XMSG_ERROR) {
490 push @xfer_errs, $msg->{'message'};
494 step recovery_cb => sub {
497 @xfer_errs = (@xfer_errs, @{$params{'errors'}})
498 if $params{'errors'};
499 return failure(join("; ", @xfer_errs), $finished_cb)
501 return failure("recovery failed", $finished_cb)
502 if $params{'result'} ne 'DONE';
504 $steps->{'start_dump'}->();
507 step finished => sub {
509 $clerk->quit(finished_cb => $steps->{'quit'});
511 $steps->{'quit'}->();
518 return failure($err, $finished_cb) if $err;
520 #do all filter are done reading stderr
528 main(\&Amanda::MainLoop::quit);
529 Amanda::MainLoop::run();
530 Amanda::Util::finish_application();