X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=server-src%2Famidxtaped.pl;h=79ea978767e1bf74ca760ccc1c764e323dbd39cd;hb=HEAD;hp=43648dcc0d13f44d1c6bd44020eb8149ffc07cff;hpb=d5853102f67d85d8e169f9dbe973ad573306c215;p=debian%2Famanda diff --git a/server-src/amidxtaped.pl b/server-src/amidxtaped.pl index 43648dc..79ea978 100644 --- a/server-src/amidxtaped.pl +++ b/server-src/amidxtaped.pl @@ -1,9 +1,10 @@ #! @PERL@ -# Copyright (c) 2010 Zmanda, Inc. All Rights Reserved. +# Copyright (c) 2010-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 -# by the Free Software Foundation. +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY @@ -22,10 +23,10 @@ use strict; 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; @@ -59,7 +60,7 @@ sub user_request { my $buffer = ""; my $steps = define_steps - cb_ref => \$params{'finished_cb'}; + cb_ref => \$params{'request_cb'}; step send_message => sub { if ($params{'err'}) { @@ -73,7 +74,7 @@ sub user_request { # 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'}->(); }; @@ -84,7 +85,7 @@ sub user_request { 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'}); @@ -92,68 +93,33 @@ sub user_request { 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; @@ -164,6 +130,7 @@ use Amanda::Recovery::Clerk; 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 @@ -174,6 +141,7 @@ sub run { $self->{'my_features'} = Amanda::Feature::Set->mine(); $self->{'their_features'} = Amanda::Feature::Set->old(); + $self->{'all_filter'} = {}; $self->setup_streams(); } @@ -266,13 +234,22 @@ sub read_command { $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(); @@ -334,7 +311,8 @@ sub make_plan { $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 @@ -358,10 +336,12 @@ sub make_plan { $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 @@ -371,18 +351,23 @@ sub make_plan { $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) { @@ -419,6 +404,7 @@ sub make_plan { return Amanda::Recovery::Planner::make_plan( filelist => $filelist, + chg => $chg, $spec? (dumpspec => $spec) : (), plan_cb => sub { $self->plan_cb(@_); }); } @@ -438,6 +424,59 @@ sub plan_cb { 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 @@ -448,8 +487,9 @@ sub plan_cb { } # 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(@_); }); } @@ -477,21 +517,32 @@ sub 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'}) { @@ -503,21 +554,37 @@ sub xfer_src_cb { 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); + my $dle = $header->get_dle(); + if ($dle && + (!$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 ]; @@ -537,6 +604,10 @@ sub send_header { # 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; @@ -607,13 +678,50 @@ sub start_xfer { } } + # 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 @@ -682,13 +790,26 @@ sub quit { 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(); } }