use warnings;
##
-# Interactive class
+# Interactivity class
-package main::Interactive;
-use base 'Amanda::Interactive';
+package main::Interactivity;
+use base 'Amanda::Interactivity';
use Amanda::Util qw( weaken_ref );
use Amanda::MainLoop;
use Amanda::Feature;
my $buffer = "";
my $steps = define_steps
- cb_ref => \$params{'finished_cb'};
+ cb_ref => \$params{'request_cb'};
step send_message => sub {
if ($params{'err'}) {
# note that fe_amrecover_FEEDME implies fe_amrecover_splits
if (!$self->{'clientservice'}->{'their_features'}->has(
$Amanda::Feature::fe_amrecover_FEEDME)) {
- return $params{'finished_cb'}->("remote cannot prompt for volumes", undef);
+ return $params{'request_cb'}->("remote cannot prompt for volumes", undef);
}
$steps->{'send_feedme'}->();
};
step read_response => sub {
my ($err, $written) = @_;
- return $params{'finished_cb'}->($err, undef) if $err;
+ return $params{'request_cb'}->($err, undef) if $err;
$self->{'clientservice'}->getline_async(
$self->{'clientservice'}->{'ctl_stream'}, $steps->{'got_response'});
step got_response => sub {
my ($err, $line) = @_;
- return $params{'finished_cb'}->($err, undef) if $err;
+ return $params{'request_cb'}->($err, undef) if $err;
if ($line eq "OK\r\n") {
- return $params{'finished_cb'}->(undef, undef); # carry on as you were
+ return $params{'request_cb'}->(undef, undef); # carry on as you were
} elsif ($line =~ /^TAPE (.*)\r\n$/) {
my $tape = $1;
if ($tape eq getconf($CNF_AMRECOVER_CHANGER)) {
$tape = $Amanda::Recovery::Scan::DEFAULT_CHANGER;
}
- return $params{'finished_cb'}->(undef, $tape); # use this device
+ return $params{'request_cb'}->(undef, $tape); # use this device
} else {
- return $params{'finished_cb'}->("got invalid response from remote", undef);
+ return $params{'request_cb'}->("got invalid response from remote", undef);
}
};
};
-##
-# Clerk Feedback class
-
-package main::Feedback;
-use Amanda::Recovery::Clerk;
-use Amanda::Util qw( weaken_ref );
-use base 'Amanda::Recovery::Clerk::Feedback';
-
-sub new {
- my $class = shift;
- my %params = @_;
-
- my $self = bless {
- clientservice => $params{'clientservice'}
- }, $class;
-
- # (weak ref here to eliminate reference loop)
- weaken_ref($self->{'clientservice'});
-
- return $self;
-}
-
-sub part_notif {
- my $self = shift;
-
- my ($label, $filenum, $hdr) = @_;
- $self->{'clientservice'}->sendmessage("restoring part $hdr->{'partnum'} " .
- "from '$label' file $filenum");
-}
-
-sub holding_notif {
- my $self = shift;
-
- my ($holding_file, $hdr) = @_;
- $self->{'clientservice'}->sendmessage("restoring from holding " .
- "file $holding_file");
-}
-
##
# ClientService class
package main::ClientService;
use base 'Amanda::ClientService';
+use Sys::Hostname;
+
use Amanda::Debug qw( debug info warning );
-use Amanda::Util qw( :constants );
+use Amanda::MainLoop qw( :GIOCondition );
+use Amanda::Util qw( :constants match_disk match_host );
use Amanda::Feature;
use Amanda::Config qw( :init :getconf );
use Amanda::Changer;
use Amanda::Recovery::Planner;
use Amanda::Recovery::Scan;
use Amanda::DB::Catalog;
+use Amanda::Disklist;
# Note that this class performs its control IO synchronously. This is adequate
# for this service, as it never receives unsolicited input from the remote
$self->{'my_features'} = Amanda::Feature::Set->mine();
$self->{'their_features'} = Amanda::Feature::Set->old();
+ $self->{'all_filter'} = {};
$self->setup_streams();
}
$self->{'their_features'} = Amanda::Feature::Set->from_string($command->{'FEATURES'});
}
- if ($command->{'CONFIG'}) {
- config_init($CONFIG_INIT_EXPLICIT_NAME, $command->{'CONFIG'});
- my ($cfgerr_level, @cfgerr_errors) = config_errors();
- if ($cfgerr_level >= $CFGERR_ERRORS) {
- die "configuration errors; aborting connection";
- }
- Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER_PREFERRED);
+ # load the configuration
+ if (!$command->{'CONFIG'}) {
+ die "no CONFIG line given";
+ }
+ config_init($CONFIG_INIT_EXPLICIT_NAME, $command->{'CONFIG'});
+ my ($cfgerr_level, @cfgerr_errors) = config_errors();
+ if ($cfgerr_level >= $CFGERR_ERRORS) {
+ die "configuration errors; aborting connection";
+ }
+ Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER_PREFERRED);
+
+ # and the disklist
+ my $diskfile = Amanda::Config::config_dir_relative(getconf($CNF_DISKFILE));
+ $cfgerr_level = Amanda::Disklist::read_disklist('filename' => $diskfile);
+ if ($cfgerr_level >= $CFGERR_ERRORS) {
+ die "Errors processing disklist";
}
$self->setup_data_stream();
$self->{'command'}{'HOST'},
$disk,
$self->{'command'}{'DATESTAMP'},
- undef); # amidxtaped protocol does not provide a level (!?)
+ undef, # amidxtaped protocol does not provide a level (!?)
+ undef); # amidxtaped protocol does not provide a write timestamp
}
# figure out if this is a holding-disk recovery
$use_default = 1;
}
+ my $tlf = Amanda::Config::config_dir_relative(getconf($CNF_TAPELIST));
+ my $tl = Amanda::Tapelist->new($tlf);
if ($use_default) {
- $chg = Amanda::Changer->new();
+ $chg = Amanda::Changer->new(undef, tapelist => $tl);
} else {
- $chg = Amanda::Changer->new($self->{'command'}{'DEVICE'});
+ $chg = Amanda::Changer->new($self->{'command'}{'DEVICE'}, tapelist => $tl);
}
# if we got a bogus changer, log it to the debug log, but allow the
$chg = Amanda::Changer->new("chg-null:");
}
}
- my $inter = main::Interactive->new(clientservice => $self);
+ $self->{'chg'} = $chg;
+
+ my $interactivity = main::Interactivity->new(clientservice => $self);
my $scan = Amanda::Recovery::Scan->new(
chg => $chg,
- interactive => $inter);
+ interactivity => $interactivity);
+ $self->{'scan'} = $scan;
+
# XXX temporary
$scan->{'scan_conf'}->{'driveinuse'} = Amanda::Recovery::Scan::SCAN_ASK;
$scan->{'scan_conf'}->{'volinuse'} = Amanda::Recovery::Scan::SCAN_ASK;
$scan->{'scan_conf'}->{'notfound'} = Amanda::Recovery::Scan::SCAN_ASK;
$self->{'clerk'} = Amanda::Recovery::Clerk->new(
- feedback => main::Feedback->new($chg, undef),
+ # note that we don't have any use for clerk_notif's, so we don't pass
+ # a feedback object
scan => $scan);
if ($is_holding) {
return $self->quit();
}
+ # check that the request-limit for this DLE allows this recovery. because
+ # of the bass-ackward way that amrecover specifies the dump to us, we can't
+ # check the results until *after* the plan was created.
+ my $dump = $plan->{'dumps'}->[0];
+ my $dle = Amanda::Disklist::get_disk($dump->{'hostname'}, $dump->{'diskname'});
+ my $recovery_limit;
+ if ($dle && dumptype_seen($dle->{'config'}, $DUMPTYPE_RECOVERY_LIMIT)) {
+ debug("using DLE recovery limit");
+ $recovery_limit = dumptype_getconf($dle->{'config'}, $DUMPTYPE_RECOVERY_LIMIT);
+ } elsif (getconf_seen($CNF_RECOVERY_LIMIT)) {
+ debug("using global recovery limit as default");
+ $recovery_limit = getconf($CNF_RECOVERY_LIMIT);
+ }
+ my $peer = $ENV{'AMANDA_AUTHENTICATED_PEER'};
+ if (defined $recovery_limit) { # undef -> no recovery limit
+ if (!$peer) {
+ warning("a recovery limit is specified for this DLE, but no authenticated ".
+ "peer name is available; rejecting request.");
+ $self->sendmessage("No matching dumps found");
+ return $self->quit();
+ }
+ my $matched = 0;
+ for my $rl (@$recovery_limit) {
+ if ($rl eq $Amanda::Config::LIMIT_SAMEHOST) {
+ # handle same-host with a case-insensitive string compare, not match_host
+ if (lc($peer) eq lc($dump->{'hostname'})) {
+ $matched = 1;
+ last;
+ }
+ } elsif ($rl eq $Amanda::Config::LIMIT_SERVER) {
+ # handle server with a case-insensitive string compare, not match_host
+ my $myhostname = hostname;
+ debug("myhostname: $myhostname");
+ if (lc($peer) eq lc($myhostname)) {
+ $matched = 1;
+ last;
+ }
+ } else {
+ # otherwise use match_host to allow match expressions
+ if (match_host($rl, $peer)) {
+ $matched = 1;
+ last;
+ }
+ }
+ }
+ if (!$matched) {
+ warning("authenticated peer '$peer' did not match recovery-limit ".
+ "config; rejecting request");
+ $self->sendmessage("No matching dumps found");
+ return $self->quit();
+ }
+ }
+
if (!$self->{'their_features'}->has($Amanda::Feature::fe_recover_splits)) {
# if we have greater than one volume, we may need to prompt for a new
# volume in mid-recovery. Sadly, we have no way to inform the client of
}
# now set up the transfer
+ $self->{'dump'} = $plan->{'dumps'}[0];
$self->{'clerk'}->get_xfer_src(
- dump => $plan->{'dumps'}[0],
+ dump => $self->{'dump'},
xfer_src_cb => sub { $self->xfer_src_cb(@_); });
}
push @filters,
Amanda::Xfer::Filter::Process->new(
[ $header->{'srv_encrypt'}, $header->{'srv_decrypt_opt'} ], 0);
+ $header->{'encrypted'} = 0;
+ $header->{'srv_encrypt'} = '';
+ $header->{'srv_decrypt_opt'} = '';
+ $header->{'clnt_encrypt'} = '';
+ $header->{'clnt_decrypt_opt'} = '';
+ $header->{'encrypt_suffix'} = 'N';
} elsif ($header->{'clnt_encrypt'}) {
- push @filters,
- Amanda::Xfer::Filter::Process->new(
- [ $header->{'clnt_encrypt'}, $header->{'clnt_decrypt_opt'} ], 0);
+ if (!$self->{'their_features'}->has($Amanda::Feature::fe_amrecover_receive_unfiltered)) {
+ push @filters,
+ Amanda::Xfer::Filter::Process->new(
+ [ $header->{'clnt_encrypt'},
+ $header->{'clnt_decrypt_opt'} ], 0);
+ $header->{'encrypted'} = 0;
+ $header->{'srv_encrypt'} = '';
+ $header->{'srv_decrypt_opt'} = '';
+ $header->{'clnt_encrypt'} = '';
+ $header->{'clnt_decrypt_opt'} = '';
+ $header->{'encrypt_suffix'} = 'N';
+ } else {
+ debug("Not decrypting client encrypted stream");
+ }
} else {
$self->sendmessage("could not decrypt encrypted dump: no program specified");
return $self->quit();
}
- $header->{'encrypted'} = 0;
- $header->{'srv_encrypt'} = '';
- $header->{'srv_decrypt_opt'} = '';
- $header->{'clnt_encrypt'} = '';
- $header->{'clnt_decrypt_opt'} = '';
- $header->{'encrypt_suffix'} = 'N';
}
if ($header->{'compressed'}) {
# need to uncompress this file
debug("..with decompression applied");
+ my $dle = $header->get_dle();
if ($header->{'srvcompprog'}) {
# TODO: this assumes that srvcompprog takes "-d" to decrypt
push @filters,
Amanda::Xfer::Filter::Process->new(
[ $header->{'srvcompprog'}, "-d" ], 0);
+ # adjust the header
+ $header->{'compressed'} = 0;
+ $header->{'uncompress_cmd'} = '';
+ $header->{'srvcompprog'} = '';
} elsif ($header->{'clntcompprog'}) {
- # TODO: this assumes that clntcompprog takes "-d" to decrypt
- push @filters,
- Amanda::Xfer::Filter::Process->new(
- [ $header->{'clntcompprog'}, "-d" ], 0);
+ if (!$self->{'their_features'}->has($Amanda::Feature::fe_amrecover_receive_unfiltered)) {
+ # TODO: this assumes that clntcompprog takes "-d" to decrypt
+ push @filters,
+ Amanda::Xfer::Filter::Process->new(
+ [ $header->{'clntcompprog'}, "-d" ], 0);
+ # adjust the header
+ $header->{'compressed'} = 0;
+ $header->{'uncompress_cmd'} = '';
+ $header->{'clntcompprog'} = '';
+ }
} else {
- push @filters,
- Amanda::Xfer::Filter::Process->new(
- [ $Amanda::Constants::UNCOMPRESS_PATH,
- $Amanda::Constants::UNCOMPRESS_OPT ], 0);
+ if (!$self->{'their_features'}->has($Amanda::Feature::fe_amrecover_receive_unfiltered) ||
+ $dle->{'compress'} == $Amanda::Config::COMP_SERVER_FAST ||
+ $dle->{'compress'} == $Amanda::Config::COMP_SERVER_BEST) {
+ push @filters,
+ Amanda::Xfer::Filter::Process->new(
+ [ $Amanda::Constants::UNCOMPRESS_PATH,
+ $Amanda::Constants::UNCOMPRESS_OPT ], 0);
+ # adjust the header
+ $header->{'compressed'} = 0;
+ $header->{'uncompress_cmd'} = '';
+ }
}
- # adjust the header
- $header->{'compressed'} = 0;
- $header->{'uncompress_cmd'} = '';
}
$self->{'xfer_filters'} = [ @filters ];
# filter out some things the remote might not be able to process
if (!$self->{'their_features'}->has($Amanda::Feature::fe_amrecover_dle_in_header)) {
$header->{'dle_str'} = undef;
+ } else {
+ $header->{'dle_str'} =
+ Amanda::Disklist::clean_dle_str_for_client($header->{'dle_str'},
+ Amanda::Feature::am_features($self->{'their_features'}));
}
if (!$self->{'their_features'}->has($Amanda::Feature::fe_amrecover_origsize_in_header)) {
$header->{'orig_size'} = 0;
}
}
+ # start reading all filter stderr
+ foreach my $filter (@{$self->{'xfer_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 = "";
+ $self->{'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 $self->{'all_filter'}->{$src};
+ $src->remove();
+ POSIX::close($fd);
+ if (!%{$self->{'all_filter'}} and $self->{'fetch_done'}) {
+ Amanda::MainLoop::quit();
+ }
+ } else {
+ $buffer .= $b;
+ if ($b eq "\n") {
+ my $line = $buffer;
+ #print STDERR "filter stderr: $line";
+ chomp $line;
+ $self->sendmessage("filter stderr: $line");
+ debug("filter stderr: $line");
+ $buffer = "";
+ }
+ }
+ });
+ }
+
# create and start the transfer
$self->{'xfer'} = Amanda::Xfer->new([
$self->{'xfer_src'},
@{$self->{'xfer_filters'}},
$xfer_dest,
]);
- $self->{'xfer'}->start(sub { $self->handle_xmsg(@_); });
+ my $size = 0;
+ $size = $self->{'dump'}->{'bytes'} if exists $self->{'dump'}->{'bytes'};
+ $self->{'xfer'}->start(sub { $self->handle_xmsg(@_); }, 0, $size);
debug("started xfer; datapath=$self->{datapath}");
# send the data-path response, if we have a datapath
if ($self->{'clerk'}) {
$self->{'clerk'}->quit(finished_cb => sub {
my ($err) = @_;
+ $self->{'chg'}->quit() if defined $self->{'chg'};
if ($err) {
# it's *way* too late to report this to amrecover now!
warning("while quitting clerk: $err");
}
- Amanda::MainLoop::quit();
+ $self->quit1();
});
} else {
+ $self->{'scan'}->quit() if defined $self->{'scan'};
+ $self->{'chg'}->quit() if defined $self->{'chg'};
+ $self->quit1();
+ }
+
+}
+
+sub quit1 {
+ my $self = shift;
+
+ $self->{'fetch_done'} = 1;
+ if (!%{$self->{'all_filter'}}) {
Amanda::MainLoop::quit();
}
}