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;
42 package Amanda::Interactive::amfetchdump;
43 use POSIX qw( :errno_h );
44 use Amanda::MainLoop qw( :GIOCondition );
46 @ISA = qw( Amanda::Interactive );
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{'finished_cb'}->(
82 Amanda::Changer::Error->new('fatal',
83 message => "Fail to read from stdin"));
84 } elsif ($n_read == 0) {
86 return $params{'finished_cb'}->(
87 Amanda::Changer::Error->new('fatal',
88 message => "Aborted by user"));
96 return $params{'finished_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);
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,
217 my ($label, $filenum, $header) = @_;
219 print STDERR "amfetchdump: $filenum: restoring ", $header->summary(), "\n";
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";
233 my ($finished_cb) = @_;
238 my $steps = define_steps
239 cb_ref => \$finished_cb;
244 # first, go to opt_directory or the original working directory we
246 my $destdir = $opt_chdir || Amanda::Util::get_original_cwd();
247 if (!chdir($destdir)) {
248 return failure("Cannot chdir to $destdir: $!", $finished_cb);
251 my $interactive = Amanda::Interactive::amfetchdump->new();
252 # if we have an explicit device, then the clerk doesn't get a changer --
253 # we operate the changer via Amanda::Recovery::Scan
254 if (defined $opt_device) {
255 $chg = Amanda::Changer->new($opt_device);
256 return failure($chg, $finished_cb) if $chg->isa("Amanda::Changer::Error");
257 my $scan = Amanda::Recovery::Scan->new(
259 interactive => $interactive);
260 return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
261 $clerk = Amanda::Recovery::Clerk->new(
262 feedback => main::Feedback->new($chg, $opt_device),
265 my $scan = Amanda::Recovery::Scan->new(
266 interactive => $interactive);
267 return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
269 $clerk = Amanda::Recovery::Clerk->new(
271 feedback => main::Feedback->new($chg, undef),
275 # planner gets to plan against the same changer the user specified
276 Amanda::Recovery::Planner::make_plan(
277 dumpspecs => [ @opt_dumpspecs ],
279 plan_cb => $steps->{'plan_cb'},
280 $opt_no_reassembly? (one_dump_per_part => 1) : ());
283 step plan_cb => sub {
284 (my $err, $plan) = @_;
285 return failure($err, $finished_cb) if $err;
287 if (!@{$plan->{'dumps'}}) {
288 return failure("No matching dumps found", $finished_cb);
291 my @needed_labels = $plan->get_volume_list();
292 my @needed_holding = $plan->get_holding_file_list();
293 if (@needed_labels) {
294 print STDERR (scalar @needed_labels), " volume(s) needed for restoration\n";
295 print STDERR "The following volumes are needed: ",
296 join(" ", map { $_->{'label'} } @needed_labels ), "\n";
298 if (@needed_holding) {
299 print STDERR (scalar @needed_holding), " holding file(s) needed for restoration\n";
300 for my $hf (@needed_holding) {
305 unless ($opt_assume) {
306 print STDERR "Press enter when ready\n";
310 $steps->{'start_dump'}->();
313 step start_dump => sub {
314 $current_dump = shift @{$plan->{'dumps'}};
315 if (!$current_dump) {
316 return $steps->{'finished'}->();
319 $clerk->get_xfer_src(
320 dump => $current_dump,
321 xfer_src_cb => $steps->{'xfer_src_cb'});
324 step xfer_src_cb => sub {
325 my ($errs, $hdr, $xfer_src, $directtcp_supported) = @_;
326 return failure(join("; ", @$errs), $finished_cb) if $errs;
328 # and set up the destination..
333 my $filename = sprintf("%s.%s.%s.%d",
335 Amanda::Util::sanitise_filename("".$hdr->{'disk'}), # workaround SWIG bug
337 $hdr->{'dumplevel'});
338 if ($opt_no_reassembly) {
339 $filename .= sprintf(".%07d", $hdr->{'partnum'});
342 # add an appropriate suffix
344 $filename .= ($hdr->{'compressed'} && $hdr->{'comp_suffix'})?
345 $hdr->{'comp_suffix'} : $Amanda::Constants::COMPRESS_SUFFIX;
348 if (!open($dest_fh, ">", $filename)) {
349 return failure("Could not open '$filename' for writing: $!", $finished_cb);
353 my $xfer_dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
355 # set up any filters that need to be applied; decryption first
357 if ($hdr->{'encrypted'} and not $opt_leave) {
358 if ($hdr->{'srv_encrypt'}) {
360 Amanda::Xfer::Filter::Process->new(
361 [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0);
362 } elsif ($hdr->{'clnt_encrypt'}) {
364 Amanda::Xfer::Filter::Process->new(
365 [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0);
367 return failure("could not decrypt encrypted dump: no program specified",
371 $hdr->{'encrypted'} = 0;
372 $hdr->{'srv_encrypt'} = '';
373 $hdr->{'srv_decrypt_opt'} = '';
374 $hdr->{'clnt_encrypt'} = '';
375 $hdr->{'clnt_decrypt_opt'} = '';
376 $hdr->{'encrypt_suffix'} = 'N';
379 if ($hdr->{'compressed'} and not $opt_compress and not $opt_leave) {
380 # need to uncompress this file
382 if ($hdr->{'srvcompprog'}) {
383 # TODO: this assumes that srvcompprog takes "-d" to decrypt
385 Amanda::Xfer::Filter::Process->new(
386 [ $hdr->{'srvcompprog'}, "-d" ], 0);
387 } elsif ($hdr->{'clntcompprog'}) {
388 # TODO: this assumes that clntcompprog takes "-d" to decrypt
390 Amanda::Xfer::Filter::Process->new(
391 [ $hdr->{'clntcompprog'}, "-d" ], 0);
394 Amanda::Xfer::Filter::Process->new(
395 [ $Amanda::Constants::UNCOMPRESS_PATH,
396 $Amanda::Constants::UNCOMPRESS_OPT ], 0);
400 $hdr->{'compressed'} = 0;
401 $hdr->{'uncompress_cmd'} = '';
402 } elsif (!$hdr->{'compressed'} and $opt_compress and not $opt_leave) {
403 # need to compress this file
405 my $compress_opt = $opt_compress_best?
406 $Amanda::Constants::COMPRESS_BEST_OPT :
407 $Amanda::Constants::COMPRESS_FAST_OPT;
409 Amanda::Xfer::Filter::Process->new(
410 [ $Amanda::Constants::COMPRESS_PATH,
414 $hdr->{'compressed'} = 1;
415 $hdr->{'uncompress_cmd'} = " $Amanda::Constants::UNCOMPRESS_PATH " .
416 "$Amanda::Constants::UNCOMPRESS_OPT |";
417 $hdr->{'comp_suffix'} = $Amanda::Constants::COMPRESS_SUFFIX;
420 # write the header to the destination if requested
421 $hdr->{'blocksize'} = Amanda::Holding::DISK_BLOCK_BYTES;
422 if (defined $opt_header or defined $opt_header_file or defined $opt_header_fd) {
423 my $hdr_fh = $dest_fh;
424 if (defined $opt_header_file) {
425 open($hdr_fh, ">", $opt_header_file)
426 or return failure("could not open '$opt_header_file': $!", $finished_cb);
427 } elsif (defined $opt_header_fd) {
428 open($hdr_fh, "<&".($opt_header_fd+0))
429 or return failure("could not open fd $opt_header_fd: $!", $finished_cb);
431 syswrite $hdr_fh, $hdr->to_string(32768, 32768), 32768;
434 my $xfer = Amanda::Xfer->new([ $xfer_src, @filters, $xfer_dest ]);
435 $xfer->start($steps->{'handle_xmsg'});
436 $clerk->start_recovery(
438 recovery_cb => $steps->{'recovery_cb'});
441 step handle_xmsg => sub {
442 my ($src, $msg, $xfer) = @_;
444 $clerk->handle_xmsg($src, $msg, $xfer);
445 if ($msg->{'type'} == $XMSG_INFO) {
446 Amanda::Debug::info($msg->{'message'});
447 } elsif ($msg->{'type'} == $XMSG_ERROR) {
448 push @xfer_errs, $msg->{'message'};
452 step recovery_cb => sub {
455 @xfer_errs = (@xfer_errs, @{$params{'errors'}})
456 if $params{'errors'};
457 return failure(join("; ", @xfer_errs), $finished_cb)
459 return failure("recovery failed", $finished_cb)
460 if $params{'result'} ne 'DONE';
462 $steps->{'start_dump'}->();
465 step finished => sub {
467 $clerk->quit(finished_cb => $steps->{'quit'});
469 $steps->{'quit'}->();
476 return failure($err, $finished_cb) if $err;
482 main(\&Amanda::MainLoop::quit);
483 Amanda::MainLoop::run();
484 Amanda::Util::finish_application();