#! @PERL@
-# Copyright (c) 2009, 2010 Zmanda, Inc. All Rights Reserved.
+# Copyright (c) 2009-2012 Zmanda, Inc. All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 as published
use Amanda::Recovery::Clerk;
use Amanda::Recovery::Scan;
-# Interactive package
-package Amanda::Interactive::amfetchdump;
+# Interactivity package
+package Amanda::Interactivity::amfetchdump;
use POSIX qw( :errno_h );
use Amanda::MainLoop qw( :GIOCondition );
use vars qw( @ISA );
-@ISA = qw( Amanda::Interactive );
+@ISA = qw( Amanda::Interactivity );
sub new {
my $class = shift;
if (!defined $n_read) {
return if ($! == EINTR);
$self->abort();
- return $params{'finished_cb'}->(
+ return $params{'request_cb'}->(
Amanda::Changer::Error->new('fatal',
message => "Fail to read from stdin"));
} elsif ($n_read == 0) {
$self->abort();
- return $params{'finished_cb'}->(
+ return $params{'request_cb'}->(
Amanda::Changer::Error->new('fatal',
message => "Aborted by user"));
} else {
chomp $line;
$buffer = "";
$self->abort();
- return $params{'finished_cb'}->(undef, $line);
+ return $params{'request_cb'}->(undef, $line);
}
}
};
sub usage {
my ($msg) = @_;
print STDERR <<EOF;
-Usage: amfetchdump config [-c|-C|-l] [-p|-n] [-a]
- [-O directory] [-d device] [-o configoption]*
- [--header-file file] [--header-fd fd]
- hostname [diskname [datestamp [hostname [diskname [datestamp ... ]]]]]"));
+Usage: amfetchdump [-c|-C|-l] [-p|-n] [-a] [-O directory] [-d device]
+ [-h|--header-file file|--header-fd fd]i
+ [--decrypt|--no-decrypt|--server-decrypt|--client-decrypt]
+ [--decompress|--no-decompress|--server-decompress|--client-decompress]
+ [-o configoption]* config
+ hostname [diskname [datestamp [hostname [diskname [datestamp ... ]]]]]
EOF
print STDERR "ERROR: $msg\n" if $msg;
exit(1);
my ($opt_config, $opt_no_reassembly, $opt_compress, $opt_compress_best, $opt_pipe,
$opt_assume, $opt_leave, $opt_blocksize, $opt_device, $opt_chdir, $opt_header,
- $opt_header_file, $opt_header_fd, @opt_dumpspecs);
+ $opt_header_file, $opt_header_fd, @opt_dumpspecs,
+ $opt_decrypt, $opt_server_decrypt, $opt_client_decrypt,
+ $opt_decompress, $opt_server_decompress, $opt_client_decompress);
+
+my $NEVER = 0;
+my $ALWAYS = 1;
+my $ONLY_SERVER = 2;
+my $ONLY_CLIENT = 3;
+my $decrypt;
+my $decompress;
+
+debug("Arguments: " . join(' ', @ARGV));
Getopt::Long::Configure(qw(bundling));
GetOptions(
'version' => \&Amanda::Util::version_opt,
'h' => \$opt_header,
'header-file=s' => \$opt_header_file,
'header-fd=i' => \$opt_header_fd,
+ 'decrypt!' => \$opt_decrypt,
+ 'server-decrypt' => \$opt_server_decrypt,
+ 'client-decrypt' => \$opt_client_decrypt,
+ 'decompress!' => \$opt_decompress,
+ 'server-decompress' => \$opt_server_decompress,
+ 'client-decompress' => \$opt_client_decompress,
'b=s' => \$opt_blocksize,
'd=s' => \$opt_device,
'O=s' => \$opt_chdir,
usage() unless (@ARGV);
$opt_config = shift @ARGV;
-$opt_compress = 1 if $opt_compress_best;
-
-usage("must specify at least a hostname") unless @ARGV;
-@opt_dumpspecs = Amanda::Cmdline::parse_dumpspecs([@ARGV],
- $Amanda::Cmdline::CMDLINE_PARSE_DATESTAMP | $Amanda::Cmdline::CMDLINE_PARSE_LEVEL);
+if (defined $opt_compress and defined $opt_compress_best) {
+ print STDERR "Can't use -c and -C\n";
+ usage();
+}
usage("The -b option is no longer supported; set readblocksize in the tapetype section\n" .
"of amanda.conf instead.")
usage("-p is not compatible with -n")
if ($opt_leave and $opt_no_reassembly);
usage("-h, --header-file, and --header-fd are mutually incompatible")
- if (($opt_header and $opt_header_file or $opt_header_fd)
+ if (($opt_header and ($opt_header_file or $opt_header_fd))
or ($opt_header_file and $opt_header_fd));
+if (defined $opt_leave) {
+ if (defined $opt_decrypt and $opt_decrypt) {
+ print STDERR "-l is incompatible with --decrypt\n";
+ usage();
+ }
+ if (defined $opt_server_decrypt) {
+ print STDERR "-l is incompatible with --server-decrypt\n";
+ usage();
+ }
+ if (defined $opt_client_decrypt) {
+ print STDERR "-l is incompatible with --client-decrypt\n";
+ usage();
+ }
+ if (defined $opt_decompress and $opt_decompress) {
+ print STDERR "-l is incompatible with --decompress\n";
+ usage();
+ }
+ if (defined $opt_server_decompress) {
+ print STDERR "-l is incompatible with --server-decompress\n";
+ usage();
+ }
+ if (defined $opt_client_decompress) {
+ print STDERR "-l is incompatible with --client-decompress\n";
+ usage();
+ }
+}
+
+if (defined($opt_decrypt) +
+ defined($opt_server_decrypt) +
+ defined($opt_client_decrypt) > 1) {
+ print STDERR "Can't use only on of --decrypt, --no-decrypt, --server-decrypt or --client-decrypt\n";
+ usage();
+}
+if (defined($opt_decompress) +
+ defined($opt_server_decompress) +
+ defined($opt_client_decompress) > 1) {
+ print STDERR "Can't use only on of --decompress, --no-decompress, --server-decompress or --client-decompress\n";
+ usage();
+}
+
+if (defined($opt_compress) and
+ defined($opt_decompress) +
+ defined($opt_server_decompress) +
+ defined($opt_client_decompress) > 0) {
+ print STDERR "Can't specify -c with one of --decompress, --no-decompress, --server-decompress or --client-decompress\n";
+ usage();
+}
+if (defined($opt_compress_best) and
+ defined($opt_decompress) +
+ defined($opt_server_decompress) +
+ defined($opt_client_decompress) > 0) {
+ print STDERR "Can't specify -C with one of --decompress, --no-decompress, --server-decompress or --client-decompress\n";
+ usage();
+}
+
+$decompress = $ALWAYS;
+$decrypt = $ALWAYS;
+$decrypt = $NEVER if defined $opt_leave;
+$decrypt = $NEVER if defined $opt_decrypt and !$opt_decrypt;
+$decrypt = $ALWAYS if defined $opt_decrypt and $opt_decrypt;
+$decrypt = $ONLY_SERVER if defined $opt_server_decrypt;
+$decrypt = $ONLY_CLIENT if defined $opt_client_decrypt;
+
+$opt_compress = 1 if $opt_compress_best;
+
+$decompress = $NEVER if defined $opt_compress;
+$decompress = $NEVER if defined $opt_leave;
+$decompress = $NEVER if defined $opt_decompress and !$opt_decompress;
+$decompress = $ALWAYS if defined $opt_decompress and $opt_decompress;
+$decompress = $ONLY_SERVER if defined $opt_server_decompress;
+$decompress = $ONLY_CLIENT if defined $opt_client_decompress;
+
+usage("must specify at least a hostname") unless @ARGV;
+@opt_dumpspecs = Amanda::Cmdline::parse_dumpspecs([@ARGV],
+ $Amanda::Cmdline::CMDLINE_PARSE_DATESTAMP | $Amanda::Cmdline::CMDLINE_PARSE_LEVEL);
+
set_config_overrides($config_overrides);
config_init($CONFIG_INIT_EXPLICIT_NAME, $opt_config);
my ($cfgerr_level, @cfgerr_errors) = config_errors();
sub new {
my $class = shift;
- my ($chg, $dev_name) = @_;
+ my ($chg, $dev_name, $is_tty) = @_;
return bless {
chg => $chg,
dev_name => $dev_name,
+ is_tty => $is_tty,
}, $class;
}
my $self = shift;
my ($label, $filenum, $header) = @_;
+ print STDERR "\n" if $self->{'is_tty'};
print STDERR "amfetchdump: $filenum: restoring ", $header->summary(), "\n";
}
my ($filename, $header) = @_;
# this used to give the fd from which the holding file was being read.. why??
+ print STDERR "\n" if $self->{'is_tty'};
print STDERR "Reading '$filename'\n", $header->summary(), "\n";
}
package main;
+use Amanda::MainLoop qw( :GIOCondition );
sub main {
my ($finished_cb) = @_;
my $current_dump;
my $plan;
my @xfer_errs;
+ my %all_filter;
+ my $recovery_done;
+ my %recovery_params;
+ my $timer;
+ my $is_tty;
+ my $delay;
my $steps = define_steps
cb_ref => \$finished_cb;
return failure("Cannot chdir to $destdir: $!", $finished_cb);
}
- my $interactive = Amanda::Interactive::amfetchdump->new();
+ $is_tty = -t STDERR;
+ if($is_tty) {
+ $delay = 1000; # 1 second
+ } else {
+ $delay = 5000; # 5 seconds
+ }
+
+ my $interactivity = Amanda::Interactivity::amfetchdump->new();
# if we have an explicit device, then the clerk doesn't get a changer --
# we operate the changer via Amanda::Recovery::Scan
if (defined $opt_device) {
return failure($chg, $finished_cb) if $chg->isa("Amanda::Changer::Error");
my $scan = Amanda::Recovery::Scan->new(
chg => $chg,
- interactive => $interactive);
+ interactivity => $interactivity);
return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
$clerk = Amanda::Recovery::Clerk->new(
- feedback => main::Feedback->new($chg, $opt_device),
+ feedback => main::Feedback->new($chg, $opt_device, $is_tty),
scan => $scan);
} else {
my $scan = Amanda::Recovery::Scan->new(
- interactive => $interactive);
+ interactivity => $interactivity);
return failure($scan, $finished_cb) if $scan->isa("Amanda::Changer::Error");
$clerk = Amanda::Recovery::Clerk->new(
changer => $chg,
- feedback => main::Feedback->new($chg, undef),
+ feedback => main::Feedback->new($chg, undef, $is_tty),
scan => $scan);
}
# if we are doing a -p operation, only keep the first dump
if ($opt_pipe) {
+ print STDERR "WARNING: Fetch first dump only because of -p argument\n" if @{$plan->{'dumps'}} > 1;
@{$plan->{'dumps'}} = ($plan->{'dumps'}[0]);
}
step start_dump => sub {
$current_dump = shift @{$plan->{'dumps'}};
+
if (!$current_dump) {
return $steps->{'finished'}->();
}
+ $recovery_done = 0;
+ %recovery_params = ();
+
$clerk->get_xfer_src(
dump => $current_dump,
xfer_src_cb => $steps->{'xfer_src_cb'});
}
}
+ $timer = Amanda::MainLoop::timeout_source($delay);
+ $timer->set_callback(sub {
+ my $size = $xfer_src->get_bytes_read();
+ if ($is_tty) {
+ print STDERR "\r" . int($size/1024) . " kb ";
+ } else {
+ print STDERR "READ SIZE: " . int($size/1024) . " kb\n";
+ }
+ });
+
my $xfer_dest = Amanda::Xfer::Dest::Fd->new($dest_fh);
+ my $dle = $hdr->get_dle();
+
# set up any filters that need to be applied; decryption first
my @filters;
- if ($hdr->{'encrypted'} and not $opt_leave) {
+ if ($hdr->{'encrypted'} and
+ (($hdr->{'srv_encrypt'} and ($decrypt == $ALWAYS || $decrypt == $ONLY_SERVER)) ||
+ ($hdr->{'clnt_encrypt'} and ($decrypt == $ALWAYS || $decrypt == $ONLY_CLIENT)))) {
if ($hdr->{'srv_encrypt'}) {
push @filters,
Amanda::Xfer::Filter::Process->new(
- [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0, 0);
+ [ $hdr->{'srv_encrypt'}, $hdr->{'srv_decrypt_opt'} ], 0);
} elsif ($hdr->{'clnt_encrypt'}) {
push @filters,
Amanda::Xfer::Filter::Process->new(
- [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0, 0);
+ [ $hdr->{'clnt_encrypt'}, $hdr->{'clnt_decrypt_opt'} ], 0);
} else {
return failure("could not decrypt encrypted dump: no program specified",
$finished_cb);
$hdr->{'encrypt_suffix'} = 'N';
}
- if ($hdr->{'compressed'} and not $opt_compress and not $opt_leave) {
+ if ($hdr->{'compressed'} and not $opt_compress and
+ (($hdr->{'srvcompprog'} and ($decompress == $ALWAYS || $decompress == $ONLY_SERVER)) ||
+ ($hdr->{'clntcompprog'} and ($decompress == $ALWAYS || $decompress == $ONLY_CLIENT)) ||
+ ($dle->{'compress'} == $Amanda::Config::COMP_SERVER_FAST and ($decompress == $ALWAYS || $decompress == $ONLY_SERVER)) ||
+ ($dle->{'compress'} == $Amanda::Config::COMP_SERVER_BEST and ($decompress == $ALWAYS || $decompress == $ONLY_SERVER)) ||
+ ($dle->{'compress'} == $Amanda::Config::COMP_FAST and ($decompress == $ALWAYS || $decompress == $ONLY_CLIENT)) ||
+ ($dle->{'compress'} == $Amanda::Config::COMP_BEST and ($decompress == $ALWAYS || $decompress == $ONLY_CLIENT)))) {
# need to uncompress this file
-
- if ($hdr->{'srvcompprog'}) {
- # TODO: this assumes that srvcompprog takes "-d" to decrypt
+ if ($hdr->{'encrypted'}) {
+ print "Not decompressing because the backup image is not decrypted\n";
+ } elsif ($hdr->{'srvcompprog'}) {
+ # TODO: this assumes that srvcompprog takes "-d" to decompress
push @filters,
Amanda::Xfer::Filter::Process->new(
- [ $hdr->{'srvcompprog'}, "-d" ], 0, 0);
+ [ $hdr->{'srvcompprog'}, "-d" ], 0);
} elsif ($hdr->{'clntcompprog'}) {
- # TODO: this assumes that clntcompprog takes "-d" to decrypt
+ # TODO: this assumes that clntcompprog takes "-d" to decompress
push @filters,
Amanda::Xfer::Filter::Process->new(
- [ $hdr->{'clntcompprog'}, "-d" ], 0, 0);
+ [ $hdr->{'clntcompprog'}, "-d" ], 0);
} else {
push @filters,
Amanda::Xfer::Filter::Process->new(
[ $Amanda::Constants::UNCOMPRESS_PATH,
- $Amanda::Constants::UNCOMPRESS_OPT ], 0, 0);
+ $Amanda::Constants::UNCOMPRESS_OPT ], 0);
}
# adjust the header
push @filters,
Amanda::Xfer::Filter::Process->new(
[ $Amanda::Constants::COMPRESS_PATH,
- $compress_opt ], 0, 0);
+ $compress_opt ], 0);
# adjust the header
$hdr->{'compressed'} = 1;
syswrite $hdr_fh, $hdr->to_string(32768, 32768), 32768;
}
+ # start reading all filter stderr
+ foreach my $filter (@filters) {
+ my $fd = $filter->get_stderr_fd();
+ $fd.="";
+ $fd = int($fd);
+ my $src = Amanda::MainLoop::fd_source($fd,
+ $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
+ my $buffer = "";
+ $all_filter{$src} = 1;
+ $src->set_callback( sub {
+ my $b;
+ my $n_read = POSIX::read($fd, $b, 1);
+ if (!defined $n_read) {
+ return;
+ } elsif ($n_read == 0) {
+ delete $all_filter{$src};
+ $src->remove();
+ POSIX::close($fd);
+ if (!%all_filter and $recovery_done) {
+ $steps->{'filter_done'}->();
+ }
+ } else {
+ $buffer .= $b;
+ if ($b eq "\n") {
+ my $line = $buffer;
+ print STDERR "filter stderr: $line";
+ chomp $line;
+ debug("filter stderr: $line");
+ $buffer = "";
+ }
+ }
+ });
+ }
+
my $xfer = Amanda::Xfer->new([ $xfer_src, @filters, $xfer_dest ]);
- $xfer->start($steps->{'handle_xmsg'});
+ $xfer->start($steps->{'handle_xmsg'}, 0, $current_dump->{'bytes'});
$clerk->start_recovery(
xfer => $xfer,
recovery_cb => $steps->{'recovery_cb'});
};
step recovery_cb => sub {
- my %params = @_;
+ %recovery_params = @_;
+ $recovery_done = 1;
- @xfer_errs = (@xfer_errs, @{$params{'errors'}})
- if $params{'errors'};
+ $steps->{'filter_done'}->() if !%all_filter;
+ };
+
+ step filter_done => sub {
+ if ($is_tty) {
+ print STDERR "\r" . int($recovery_params{'bytes_read'}/1024) . " kb ";
+ } else {
+ print STDERR "READ SIZE: " . int($recovery_params{'bytes_read'}/1024) . " kb\n";
+ }
+ @xfer_errs = (@xfer_errs, @{$recovery_params{'errors'}})
+ if $recovery_params{'errors'};
return failure(join("; ", @xfer_errs), $finished_cb)
if @xfer_errs;
return failure("recovery failed", $finished_cb)
- if $params{'result'} ne 'DONE';
+ if $recovery_params{'result'} ne 'DONE';
$steps->{'start_dump'}->();
};
step quit => sub {
my ($err) = @_;
+ if (defined $timer) {
+ $timer->remove();
+ $timer = undef;
+ }
+ print STDERR "\n" if $is_tty;
return failure($err, $finished_cb) if $err;
$finished_cb->();