Blob Blame History Raw
#! @PERL@
# 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
# 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: Carbonite Inc., 756 N Pastoria Ave
# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com

use lib '@amperldir@';
use strict;
use warnings;

use Getopt::Long;
use POSIX qw( WIFEXITED WEXITSTATUS strftime :termios_h);
use File::Glob qw( :glob );

use Amanda::Config qw( :init :getconf );
use Amanda::Util qw( :constants match_datestamp );
use Amanda::Logfile qw( :logtype_t log_add );
use Amanda::Debug qw( debug );
use Amanda::Paths;
use Amanda::Amflush;
use Amanda::Tapelist;

##
# Main

sub usage {
    my ($msg) = @_;
    print STDERR <<EOF;
Usage: amflush <conf> [-b] [-f] [-D <datestamps>]* [--exact-match] [-o configoption]* [host/disk]*
EOF
    print STDERR "$msg\n" if $msg;
    exit 1;
}

Amanda::Util::setup_application("amflush", "server", $CONTEXT_DAEMON, "amanda", "amanda");

my $config_overrides = new_config_overrides($#ARGV+1);
my @config_overrides_opts;

my $opt_no_taper    = 0;
my $opt_from_client = 0;
my $opt_exact_match = 0;
my $opt_batch       = 0;
my $opt_foreground  = 0;
my @opt_datestamps;

debug("Arguments: " . join(' ', @ARGV));
Getopt::Long::Configure(qw(bundling));
GetOptions(
    'version' => \&Amanda::Util::version_opt,
    'help|usage|?' => \&usage,
    'no-taper' => \$opt_no_taper,
    'from-client' => \$opt_from_client,
    'exact-match' => \$opt_exact_match,
    'b' => \$opt_batch,
    'f' => \$opt_foreground,
    'D=s' => sub { push @opt_datestamps, $_[1]; },
    'o=s' => sub {
	push @config_overrides_opts, "-o" . $_[1];
	add_config_override_opt($config_overrides, $_[1]);
    },
) or usage();

usage("No config specified") if (@ARGV < 1);

my $config_name = shift @ARGV;
set_config_overrides($config_overrides);
config_init_with_global($CONFIG_INIT_EXPLICIT_NAME, $config_name);
my ($cfgerr_level, @cfgerr_errors) = config_errors();
if ($cfgerr_level >= $CFGERR_WARNINGS) {
    config_print_errors();
    if ($cfgerr_level >= $CFGERR_ERRORS) {
	die("errors processing config file");
    }
}

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";
}

Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);

Amanda::Disklist::add_holding_to_disklist();

sub user_msg {
    my $msg = shift;

    if ($msg->{'code'} != 2000000 and
	$msg->{'code'} != 2000001) {
	print STDOUT $msg->message() . "\n";
    }
}

sub pick_datestamp {
    my @ts = @_;

    my @datestamp;
    while (!@datestamp) {
	print "\nMultiple Amanda runs in holding disks; please pick one by letter:\n";
	my $char = 'A';
	foreach my $ts (@ts) {
	    print "  $char. $ts\n";
	    $char = chr(ord($char)+1);
	    last if $char gt 'Z';
	}
	$char = chr(ord($char)-1);

	print "Select datestamps to flush [A.." . $char ." or <enter> for all]: ";
	my $answer = <STDIN>;

	if (!$answer) {
	    exit(0);
	}
	chomp $answer;
	if ($answer eq "" || $answer eq "ALL") {
	    return @ts;
	}

	for my $c (split //, $answer) {
	    next if $c eq ' ' || $c eq '\t' || $c eq ',';
	    $c = uc $c;
	    if ($c lt 'A' || $c gt $char) {
		undef @datestamp;
		last;
	    }
	    push @datestamp, $ts[ord($c)-ord('A')];
	}
    }
    return @datestamp;
}

sub confirm {
    my @datestamps = @_;

    my $storages = getconf($CNF_STORAGE);
    foreach my $storage_n (@{$storages}) {
	print "Flushing dumps from " . join(', ', @datestamps);
	my $storage = lookup_storage($storage_n);
	my $tpchanger = storage_getconf($storage, $STORAGE_TPCHANGER);
	print " using storage \"$storage_n\", tape changer \"$tpchanger\".\n";

	my $policy_n = storage_getconf($storage, $STORAGE_POLICY);
	my $labelstr = storage_getconf($storage, $STORAGE_LABELSTR);
	my $l_template = $labelstr->{'template'};
	my $tapepool = storage_getconf($storage, $STORAGE_TAPEPOOL);
	my $retention_tapes;
	my $retention_days;
	my $retention_recover;
	my $retention_full;
	if ($policy_n) {
	    my $policy = lookup_policy($policy_n);
	    if (policy_seen($policy, $POLICY_RETENTION_TAPES)) {
	        $retention_tapes = policy_getconf($policy, $POLICY_RETENTION_TAPES);
	    } else {
	        $retention_tapes = getconf($CNF_TAPECYCLE);
	    }
	    $retention_days = policy_getconf($policy, $POLICY_RETENTION_DAYS);
	    $retention_recover = policy_getconf($policy, $POLICY_RETENTION_RECOVER);
	    $retention_full = policy_getconf($policy, $POLICY_RETENTION_FULL);
	} else {
	    $retention_tapes = getconf($CNF_TAPECYCLE);
	    $retention_days = 0;
	    $retention_recover = 0;
	    $retention_full = 0;
	}
	my $tlf = Amanda::Config::config_dir_relative(getconf($CNF_TAPELIST));
	my ($tapelist, $message) = Amanda::Tapelist->new($tlf);
	my $tp = Amanda::Tapelist::get_last_reusable_tape_label($l_template,
	#my $tp = $tapelist->get_last_reusable_tape_label($l_template,
			$tapepool, $storage_n, $retention_tapes,
			$retention_days, $retention_recover,
			$retention_full, 0);
	if ($tp) {
	    my $tle = $tapelist->lookup_tapelabel($tp);
	    if ($tle) {
		print "To volume $tp or a new volume,";
	    } else {
		print "To a new volume.\n";
	    }
	    $tle = $tapelist->lookup_tapepos(1);
	    if ($tp) {
		print "  (The last dumps were to volume $tle->{'label'}\n";
	    }
	}
    }

    while (1) {
	$| = 1;
	print "\nAre you sure you want to do this [yN]? ";
	my $term = POSIX::Termios->new();
	my $fd_stdin = fileno(STDIN);
	$term->getattr($fd_stdin);
	my $oterm = $term->getlflag();
	my $echo     = ECHO | ECHOK | ICANON;
	my $noecho   = $oterm & ~$echo;

	$term->setlflag($noecho);
	$term->setcc(VTIME, 1);
	$term->setattr($fd_stdin, TCSANOW);
	my $key = '';
	my $r = sysread(STDIN, $key, 1);
	$term->setlflag($oterm);
	$term->setcc(VTIME, 0);
	$term->setattr($fd_stdin, TCSANOW);

	return 1 if $r == 0;
	return 0 if $r != 1;

	$key = uc $key;
	print "$key\n";

	return 1 if $key eq 'Y';
	return 0 if $key eq 'N' || $key eq "\0" || $key eq "\n";
    }

    return 0;
}


Amanda::Disklist::match_disklist(
	user_msg    => \&user_msg,
	exact_match => $opt_exact_match,
	args        => \@ARGV);

my $hostdisk = \@ARGV;
my ($amflush, @messages) = Amanda::Amflush->new(
				config           => $config_name,
				exact_match      => $opt_exact_match,
				config_overrides => \@config_overrides_opts,
				hostdisk         => $hostdisk,
				user_msg         => \&user_msg);

my @ts = Amanda::Holding::get_all_datestamps();
my @datestamps;
if (@opt_datestamps) {
    foreach my $ts (@ts) {
	foreach my $datestamp (@opt_datestamps) {
	    if (match_datestamp($datestamp, $ts)) {
		push @datestamps, $ts;
		last;
	    }
	}
    }
} elsif ($opt_batch || @ts < 2) {
    @datestamps = @ts;
} else {
    @datestamps = pick_datestamp(@ts);
}

if (!@datestamps) {
    print "Could not find any Amanda directories to flush.\n";
    log_add($L_INFO, "pid-done $$");
    exit(1);
}

if (!$opt_batch && !confirm(@datestamps)) {
    print "Ok, quitting.  Run amflush again when you are ready.\n";
    log_add($L_INFO, "pid-done $$");
    exit(1);
}

open STDERR, ">>&", $amflush->{'amdump_log'} || die("stdout: $!");
my $to_flushs = $amflush->get_flush(datestamps => \@datestamps);

if (!$to_flushs || !@$to_flushs) {
    print "Could not find any valid dump image, check directory.\n";
    exit(1);
}

if (!$opt_foreground) {
    my $oldpid = $$;
    my $pid = POSIX::fork();
    if ($pid != 0) {
	print STDOUT "Running in background, you can log off now.\n";
	print STDOUT "You'll get mail when amflush is finished.\n";
	# parent exit;
	exit(0);
    }
    log_add($L_INFO, "fork " . Amanda::Util::get_pname() . " $oldpid $$");
    open STDIN, '/dev/null';
    open STDOUT, ">>&", $amflush->{'amdump_log'} || die("stdout: $!");
    POSIX::setsid();
}
my $exit_code = $amflush->run(1, $to_flushs);
debug("exiting with code $exit_code");
exit($exit_code);