2 # Copyright (c) 2010-2012 Zmanda, Inc. All Rights Reserved.
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
19 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
21 use lib '@amperldir@';
30 package main::ClientService;
31 use base 'Amanda::ClientService';
36 use Amanda::Debug qw( debug info warning );
37 use Amanda::Util qw( :constants );
39 use Amanda::Config qw( :init :getconf config_dir_relative );
43 use Amanda::Util qw( match_disk match_host );
45 # Note that this class performs its control IO synchronously. This is adequate
46 # for this service, as it never receives unsolicited input from the remote
52 $self->{'my_features'} = Amanda::Feature::Set->mine();
53 $self->{'their_features'} = Amanda::Feature::Set->old();
55 $self->setup_streams();
61 # always started from amandad.
62 my $req = $self->get_req();
64 # make some sanity checks
66 if (defined $req->{'options'}{'auth'} and defined $self->amandad_auth()
67 and $req->{'options'}{'auth'} ne $self->amandad_auth()) {
68 my $reqauth = $req->{'options'}{'auth'};
69 my $amauth = $self->amandad_auth();
70 push @$errors, "recover program requested auth '$reqauth', " .
71 "but amandad is using auth '$amauth'";
72 $main::exit_status = 1;
75 # and pull out the features, if given
76 if (defined($req->{'features'})) {
77 $self->{'their_features'} = $req->{'features'};
80 $self->send_rep(['CTL' => 'rw'], $errors);
81 return $self->quit() if (@$errors);
83 $self->{'ctl_stream'} = 'CTL';
85 $self->read_command();
91 if (defined $self->{'config'}) {
92 $self->sendctlline("ERROR duplicate CONFIG command");
97 config_init($CONFIG_INIT_EXPLICIT_NAME, $config);
98 my ($cfgerr_level, @cfgerr_errors) = config_errors();
99 if ($cfgerr_level >= $CFGERR_ERRORS) {
100 $self->sendctlline("ERROR configuration errors; aborting connection");
101 $self->{'abort'} = 1;
104 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER_PREFERRED);
107 my $diskfile = Amanda::Config::config_dir_relative(getconf($CNF_DISKFILE));
108 $cfgerr_level = Amanda::Disklist::read_disklist('filename' => $diskfile);
109 if ($cfgerr_level >= $CFGERR_ERRORS) {
110 $self->sendctlline("ERROR Errors processing disklist");
111 $self->{'abort'} = 1;
114 $self->{'config'} = $config;
122 $self->{'their_features'} = Amanda::Feature::Set->from_string($features);
124 my $featurestr = $self->{'my_features'}->as_string();
125 $featreply = "FEATURES $featurestr";
127 $self->sendctlline($featreply);
133 if (!defined $self->{'config'}) {
134 $self->sendctlline("CONFIG must be set before listing the disk");
138 for my $disk (@{$self->{'host'}->{'disks'}}) {
139 $self->sendctlline(Amanda::Util::quote_string($disk));
141 $self->sendctlline("ENDLIST");
146 my $qdiskname = shift;
147 my $diskname = Amanda::Util::unquote_string($qdiskname);
148 if (!defined $self->{'config'}) {
149 $self->sendctlline("CONFIG must be set before setting the disk");
153 for my $disk (@{$self->{'host'}->{'disks'}}) {
154 if ($disk eq $diskname) {
155 push @{$self->{'disk'}}, $diskname;
156 $self->sendctlline("DISK $diskname added");
165 if (!defined $self->{'config'}) {
166 $self->sendctlline("CONFIG must be set before doing a backup");
170 my $logdir = config_dir_relative(getconf($CNF_LOGDIR));
171 if (-f "$logdir/log" || -f "$logdir/amdump" || -f "$logdir/amflush") {
172 $self->sendctlline("BUSY Amanda is busy, retry later");
176 $self->sendctlline("DUMPING");
177 my @command = ("$sbindir/amdump", "--no-taper", "--from-client", $self->{'config'}, $self->{'host'}->{'hostname'});
178 if (defined $self->{'disk'}) {
179 @command = (@command, @{$self->{'disk'}});
182 debug("command: @command");
185 my $pid = open3($amdump_in, $amdump_out, $amdump_out, @command);
187 while (<$amdump_out>) {
189 $self->sendctlline($_);
191 $self->sendctlline("ENDDUMP");
197 if (!defined $self->{'config'}) {
198 $self->sendctlline("CONFIG must be set before doing a backup");
202 my $logdir = config_dir_relative(getconf($CNF_LOGDIR));
203 if (-f "$logdir/log" || -f "$logdir/amdump" || -f "$logdir/amflush") {
204 $self->sendctlline("BUSY Amanda is busy, retry later");
208 $self->sendctlline("CHECKING");
209 my @command = ("$sbindir/amcheck", "-c", $self->{'config'}, $self->{'host'}->{'hostname'});
210 if (defined $self->{'disk'}) {
211 @command = (@command, @{$self->{'disk'}});
214 debug("command: @command");
217 my $pid = open3($amcheck_in, $amcheck_out, $amcheck_out, @command);
219 while (<$amcheck_out>) {
221 $self->sendctlline($_);
223 $self->sendctlline("ENDCHECK");
228 my $ctl_stream = $self->{'ctl_stream'};
229 my $command = $self->{'command'} = {};
231 my @known_commands = qw(
232 CONFIG DUMP FEATURES LIST DISK);
233 while (!$self->{'abort'} and ($_ = $self->getline($ctl_stream))) {
239 if (/^CONFIG (.*)$/) {
240 $self->cmd_config($1);
241 } elsif (/^FEATURES (.*)$/) {
242 $self->cmd_features($1);
245 } elsif (/^DISK (.*)$/) {
247 } elsif (/^CHECK$/) {
252 $self->{'abort'} = 1;
254 $self->sendctlline("invalid command '$_'");
262 my @hosts = Amanda::Disklist::all_hosts();
263 my $peer = $ENV{'AMANDA_AUTHENTICATED_PEER'};
265 if (!defined($peer)) {
266 debug("no authenticated peer name is available; rejecting request.");
267 $self->sendctlline("no authenticated peer name is available; rejecting request.");
271 # try to find the host that match the connection
273 for my $host (@hosts) {
274 if (lc($peer) eq lc($host->{'hostname'})) {
276 $self->{'host'} = $host;
282 debug("The peer host '$peer' doesn't match a host in the disklist.");
283 $self->sendctlline("The peer host '$peer' doesn't match a host in the disklist.");
284 $self->{'abort'} = 1;
293 my $buf = Amanda::Util::full_read($self->rfd('main'), 1024);
297 # we've read main to EOF, so close it
298 $self->close('main', 'r');
300 return $self->{'req'} = $self->parse_req($req_str);
305 my ($streams, $errors) = @_;
308 # first, if there were errors in the REQ, report them
310 for my $err (@$errors) {
311 $rep .= "ERROR $err\n";
314 my $connline = $self->connect_streams(@$streams);
315 $rep .= "$connline\n";
317 # rep needs a empty-line terminator, I think
320 # write the whole rep packet, and close main to signal the end of the packet
321 $self->senddata('main', $rep);
322 $self->close('main', 'w');
325 # helper function to get a line, including the trailing '\n', from a stream. This
326 # reads a character at a time to ensure that no extra characters are consumed. This
327 # could certainly be more efficient! (TODO)
331 my $fd = $self->rfd($stream);
336 my $a = POSIX::read($fd, $c, 1);
344 $chopped =~ s/[\r\n]*$//g;
345 debug("CTL << $chopped");
353 # helper function to write a data to a stream. This does not add newline characters.
356 my ($stream, $data) = @_;
357 my $fd = $self->wfd($stream);
359 Amanda::Util::full_write($fd, $data, length($data))
360 or die "writing to $stream: $!";
363 # send a line on the control stream, or just log it if the ctl stream is gone;
364 # async callback is just like for senddata
369 if ($self->{'ctl_stream'}) {
370 debug("CTL >> $msg");
371 return $self->senddata($self->{'ctl_stream'}, $msg . "\n");
373 debug("not sending CTL message as CTL is closed >> $msg");
381 use Amanda::Debug qw( debug );
382 use Amanda::Util qw( :constants );
383 use Amanda::Config qw( :init );
385 our $exit_status = 0;
388 Amanda::Util::setup_application("amdumpd", "server", $CONTEXT_DAEMON);
389 config_init(0, undef);
390 Amanda::Debug::debug_dup_stderr_to_debug();
392 my $cs = main::ClientService->new();
395 debug("exiting with $exit_status");
396 Amanda::Util::finish_application();