Blob Blame History Raw
# Copyright (c) 2008-2012 Zmanda, Inc.  All Rights Reserved.
# Copyright (c) 2013-2016 Carbonite, 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
# 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
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
# Contact information: Zmanda Inc., 465 S Mathlida Ave, Suite 300
# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com

use strict;
use warnings;

package Amanda::CheckDump::Message;
use strict;
use warnings;

use Amanda::Message;
use Amanda::Debug;
use vars qw( @ISA );
@ISA = qw( Amanda::Message );

sub local_message {
    my $self = shift;

    if ($self->{'code'} == 2700003) {
	return "Reading volume $self->{'label'} file $self->{'filenum'}";
    } elsif ($self->{'code'} == 2700004) {
	return "Reading '$self->{'filename'}'";
    } elsif ($self->{'code'} == 2700005) {
	return "Validating image " . $self->{hostname} . ":" .
	    $self->{diskname} . " dumped " . $self->{dump_timestamp} . " level ".
	    $self->{level} . ($self->{'nparts'} > 1 ? " ($self->{nparts} parts)" : "");
    } elsif ($self->{'code'} == 2700006) {
	if ($self->{'image_validated'} == 1) {
	    return "All image ($self->{'image_validated'}) successfully validated";
	} else {
	    return "All images ($self->{'image_validated'}) successfully validated";
	}
    } elsif ($self->{'code'} == 2700007) {
	if ($self->{'image_failed'} == 1) {
	    return "$self->{'image_failed'} image failed to be correctly validated.";
	} else {
	    return "$self->{'image_failed'} images failed to be correctly validated.";
	}
    } elsif ($self->{'code'} == 2700008) {
	if ($self->{'image_validated'} == 1) {
	    return "$self->{'image_validated'} image successfully validated.";
	} else {
	    return "$self->{'image_validated'} images successfully validated.";
	}
    } elsif ($self->{'code'} == 2700009) {
	my $count = $self->{'nb_image'} - $self->{'image_validated'} - $self->{'image_failed'};
	if ($count == 1) {
	    return "$count image not validated.";
	} else {
	    return "$count images not validated.";
	}
    } elsif ($self->{'code'} == 2700010) {
	return "No images validated.";
    } elsif ($self->{'code'} == 2700018) {
	return "Running a CheckDump";
    } elsif ($self->{'code'} == 2700019) {
	return "Failed to fork the CheckDump process";
    } elsif ($self->{'code'} == 2700020) {
	return "The message filename is '$self->{'message_filename'}'";
    } else {
	return "No message for code '$self->{'code'}'";
    }
}

package Amanda::CheckDump;

use POSIX qw(strftime);
use Amanda::Debug qw( :logging );
use Amanda::Config qw( :init :getconf config_dir_relative );
use Amanda::Util qw( :constants :quoting );
use Amanda::Cmdline;
use Amanda::MainLoop qw( :GIOCondition );
use Amanda::Recovery::Clerk;
use Amanda::Restore;
use Amanda::Extract;

use parent -norequire, 'Amanda::Recovery::Clerk::Feedback';

sub new {
    my $class = shift;

    my $self = $class->SUPER::new(@_);
    $self->{'is_tty'} = -t STDOUT;
    $self->{'image_validated'} = 0;
    $self->{'image_failed'} = 0;
    $self->{'nb_image'} = 0;

    my $logdir = $self->{'logdir'} = config_dir_relative(getconf($CNF_LOGDIR));
    my @now = localtime;
    my $timestamp = strftime "%Y%m%d%H%M%S", @now;
    $self->{'pid'} = $$; 
    $self->{'timestamp'} = Amanda::Logfile::make_logname("checkdump", $timestamp);
    $self->{'trace_log_filename'} = Amanda::Logfile::get_logname();
    debug("beginning trace log: $self->{'trace_log_filename'}");
    $self->{'message_filename'} = "checkdump.$timestamp";
    $self->{'message_pathname'} = "$logdir/checkdump.$timestamp";

    return $self;
    # must return undef on error
    # must call user_message to print error
}

sub run {
    my $self = shift;
    my %params = @_;

    $self->{'target'} = $params{'target'};
    $self->{'extract-client'} = $params{'extract-client'};
    $self->{'assume'} = $params{'assume'};

    ($self->{'restore'}, my $result_message) = Amanda::Restore->new(
			message_pathname => $self->{'message_pathname'});
    if (@$result_message) {
	foreach my $message (@$result_message) {
	    $self->user_message($message);
	}
	return $params{'finished_cb'}->(1);
    }

    select(STDERR);
    $| = 1;
    select(STDOUT); # default
    $| = 1;

    my $timestamp = $params{'timestamp'};
    $timestamp = Amanda::DB::Catalog::get_latest_write_timestamp()
			unless defined $timestamp;
    my @spec = Amanda::Cmdline::dumpspec_t->new(undef, undef, undef, undef, $timestamp);
    my $validate_finish_cb = sub {

	$self->{'nb_image'} = $self->{'restore'}->{'nb_image'};
	$self->{'image_validated'} = $self->{'restore'}->{'image_restored'};
	$self->{'image_failed'} = $self->{'restore'}->{'image_failed'};

	if (!defined $self->{'nb_image'} || $self->{'nb_image'} == 0) {
	    $self->{'restore'}->user_message(Amanda::CheckDump::Message->new(
			source_filename => __FILE__,
			source_line     => __LINE__,
			code            => 2700010,
			nb_image        => $self->{'nb_image'},
			image_validated => $self->{'image_validate'},
			image_failed    => $self->{'image_failed'},
			severity        => $Amanda::Message::ERROR));
	} elsif ($self->{'image_failed'} == 0 &&
		   $self->{'nb_image'} == $self->{'image_validated'}) {
	    $self->{'restore'}->user_message(Amanda::CheckDump::Message->new(
			source_filename => __FILE__,
			source_line     => __LINE__,
			code            => 2700006,
			image_validated => $self->{'image_validated'},
			severity        => $Amanda::Message::SUCCESS));
	} else {
	    if ($self->{'image_validated'}) {
	        $self->{'restore'}->user_message(Amanda::CheckDump::Message->new(
			source_filename => __FILE__,
			source_line     => __LINE__,
			code            => 2700008,
			image_validated => $self->{'image_validated'},
			severity        => $Amanda::Message::SUCCESS));
	    }
	    if ($self->{'image_failed'}) {
		$self->{'restore'}->user_message(Amanda::CheckDump::Message->new(
			source_filename => __FILE__,
			source_line     => __LINE__,
			code            => 2700007,
			image_failed    => $self->{'image_failed'},
			severity        => $Amanda::Message::ERROR));
	    }
	    if ($self->{'image_validated'} + $self->{'image_failed'} != $self->{'nb_image'}) {
		$self->{'restore'}->user_message(Amanda::CheckDump::Message->new(
			source_filename => __FILE__,
			source_line     => __LINE__,
			code            => 2700009,
			nb_image        => $self->{'nb_image'},
			image_validated => $self->{'image_validated'},
			image_failed    => $self->{'image_failed'},
			severity        => $Amanda::Message::ERROR));
	    }
	}

	$params{'finished_cb'}->(@_);
    };

    $self->{'restore'}->restore(
		#'application_property'  => $params{'application_property'},
		'assume'                => $params{'assume'},
		'decompress'            => 1,
		'decrypt'               => 1,
		'device'                => $params{'device'},
		'target'                => undef,
		'dumpspecs'             => \@spec,
		'exact-match'           => $params{'exact-match'},
		'extract'               => 1,
		'all_copy'              => 1,
		#'init'                  => $params{'init'},
		#'restore'               => $params{'restore'},
		'finished_cb'           => $validate_finish_cb,
		'interactivity'         => $params{'interactivity'},
		'feedback'              => $self);
}

sub set_feedback {
    my $self = shift;
    my %params = @_;

    $self->{'chg'} = $params{'chg'} if exists $params{'chg'};
    $self->{'dev_name'} = $params{'dev_name'} if exists $params{'dev_name'};

    return $self;
}

sub set {
    my $self = shift;
    my $hdr = shift;;
    my $dle = shift;;
    my $application_property = shift;

    $self->{'hdr'} = $hdr;
    $self->{'dle'} = $dle;
    $self->{'application_property'} = $application_property;

    $self->{'extract'} = Amanda::Extract->new(hdr => $hdr, dle => $dle);
    die("$self->{'extract'}") if $self->{'extract'}->isa('Amanda::Message');
    ($self->{'bsu'}, my $err) = $self->{'extract'}->BSU();
    if ($err && @$err) {
        die("BSU err " . join("\n", @$err));
    }

    return undef;
}

sub transmit_dar {
    my $self = shift;
    my $use_dar = shift;

    return 0;
}

sub get_datapath {
    my $self = shift;
    my $directtcp_supported = shift;

    $self->{'use_directtcp'} = $directtcp_supported && !$self->{'bsu'}->{'data-path-directtcp'};
    return $self->{'use_directtcp'};
}

sub get_xfer_dest {
    my $self = shift;

    $self->{'extract'}->set_validate_argv();

    if ($self->{'extract'}->{'validate_argv'}) {
	$self->{'xfer_dest'} = Amanda::Xfer::Dest::Application->new($self->{'extract'}->{'validate_argv'}, 0, 0, 0, 1);
    } else {
	$self->{'xfer_dest'} = Amanda::Xfer::Dest::Null->new(0);
    }
    return $self->{'xfer_dest'};
}

sub new_dest_fh {
    my $self = shift;

    my $new_dest_fh;
    open ($new_dest_fh, '>/dev/null');
    return $new_dest_fh;
}

sub clerk_notif_part {
    my $self = shift;
    my ($label, $filenum, $header) = @_;

    $self->{'restore'}->user_message(Amanda::CheckDump::Message->new(
		source_filename	=> __FILE__,
		source_line	=> __LINE__,
		code		=> 2700003,
		severity	=> $Amanda::Message::INFO,
		label		=> $label,
		filenum		=> "$filenum"+0));
}

sub clerk_notif_holding {
    my $self = shift;
    my ($filename, $header) = @_;

    # this used to give the fd from which the holding file was being read.. why??
    $self->{'restore'}->user_message(Amanda::CheckDump::Message->new(
		source_filename	=> __FILE__,
		source_line	=> __LINE__,
		code		=> 2700004,
		severity	=> $Amanda::Message::INFO,
		holding_file	=> $filename));
}

sub notif_start {
    my $self = shift;
    my $dump = shift;
    $self->{'restore'}->user_message(Amanda::CheckDump::Message->new(
	source_filename => __FILE__,
	source_line     => __LINE__,
	code            => 2700005,
	severity        => $Amanda::Message::INFO,
	hostname        => $dump->{hostname},
	diskname        => $dump->{diskname},
	dump_timestamp  => $dump->{dump_timestamp},
	level           => "$dump->{level}"+0,
	nparts          => $dump->{nparts}));

}

sub user_message {
    my $self = shift;
    my $message = shift;

    if ($message->{'code'} == 4900000) { #SIZE
	if ($self->{'is_tty'}) {
	    print STDOUT "\r$message    ";
	    $self->{'last_is_size'} = 1;
	} else {
	    print STDOUT "SIZE: $message\n";
	}
    } elsif ($message->{'code'} == 4900012) { #READ SIZE
	if ($self->{'is_tty'}) {
	    print STDOUT "\r$message    ";
	    $self->{'last_is_size'} = 1;
	} else {
	    print STDOUT "READ SIZE: $message\n";
	}
    } elsif ($message->{'code'} == 4900018 && $message->{'text'} eq 'application stdout') {
	# do nothing with application stdout
    } else {
	if ($message->{'code'} == 3300003 || $message->{'code'} == 3300004) {
	    print "\n";
	}
	print STDOUT "\n" if $self->{'is_tty'} and $self->{'last_is_size'};
	print STDOUT "$message\n";
	$self->{'last_is_size'} = 0;

	if ($message->{'code'} == 3300002 && $self->{'assume'}) {
	    print STDOUT "Press enter when ready\n";
	    my $resp = <STDIN>;
	}
    }
}

1;