#! @PERL@
# 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: Carbonite Inc., 756 N Pastoria Ave
# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
use lib '@amperldir@';
use strict;
use warnings;
package main::Interactivity;
use POSIX qw( :errno_h );
use Amanda::MainLoop qw( :GIOCondition );
use vars qw( @ISA );
@ISA = qw( Amanda::Interactivity );
sub new {
my $class = shift;
if (!-r STDIN) {
return undef;
}
my $stdin;
if (!open $stdin, "<&STDIN") {
return undef;
}
close $stdin;
my $self = {
input_src => undef};
return bless ($self, $class);
}
sub abort() {
my $self = shift;
if ($self->{'input_src'}) {
$self->{'input_src'}->remove();
$self->{'input_src'} = undef;
}
}
sub user_request {
my $self = shift;
my %params = @_;
my %subs;
my $buffer = "";
my $message = $params{'message'};
my $label = $params{'label'};
my $err = $params{'err'};
my $chg_name = $params{'chg_name'};
$subs{'data_in'} = sub {
my $b;
my $n_read = POSIX::read(0, $b, 1);
if (!defined $n_read) {
return if ($! == EINTR);
$self->abort();
return $params{'request_cb'}->(
Amanda::Changer::Error->new('fatal',
message => "Fail to read from stdin"));
} elsif ($n_read == 0) {
$self->abort();
return $params{'request_cb'}->(
Amanda::Changer::Error->new('fatal',
message => "Aborted by user"));
} else {
$buffer .= $b;
if ($b eq "\n") {
my $line = $buffer;
chomp $line;
$buffer = "";
$self->abort();
return $params{'request_cb'}->(undef, $line);
}
}
};
print STDERR "$err\n";
print STDERR "Insert volume labeled '$label' in $chg_name\n";
print STDERR "and press enter, or ^D to abort.\n";
$self->{'input_src'} = Amanda::MainLoop::fd_source(0, $G_IO_IN|$G_IO_HUP|$G_IO_ERR);
$self->{'input_src'}->set_callback($subs{'data_in'});
return;
};
package main;
use Amanda::Config qw( :init :getconf );
use Amanda::Debug qw( :logging );
use Amanda::Util qw( :constants );
use Getopt::Long;
use Amanda::Cmdline qw( :constants parse_dumpspecs );
use Amanda::Vault;
sub usage {
my ($msg) = @_;
print STDERR <<EOF;
**NOTE** this interface is under development and will change in future releases!
Usage: amvault [-o configoption...] [-q] [--quiet] [-n] [--dry-run]
[--exact-match] [--export] [--no-interactivity]
[--src-labelstr labelstr] [--src-storage storage]
[--dest-storage storage]
[--fulls-only] [--latest-fulls] [--incrs-only]
[--src-timestamp src-timestamp]
config
[hostname [ disk [ date [ level [ hostname [...] ] ] ] ]]
-o: configuration override (see amanda(8))
-q/--quiet: quiet progress messages
--dest-storage: destination storage for vaulting operation
--exact-match: parse host and disk as exact values
--export: move completed destination volumes to import/export slots
--src-labelstr: only copy dumps from volumes matching labelstr
--src-storage: only copy dumps from specified storage
--fulls-only: only copy full (level-0) dumps
--latest-fulls: copy the latest full of every dle
--incrs-only: only copy incremental (level > 0) dumps
--src-timestamp: the timestamp of the Amanda run that should be vaulted
Copies dumps selected by the specified filters onto volumes on the storage
<dest-storage>. If <src-timestamp> is "latest", then the most recent run of
amdump or amflush will be used. If any dumpspecs are included (<host-expr> and
so on), then only dumps matching those dumpspecs will be dumped. At least one
of --fulls-only, --latest-fulls, --incrs-only, --src-timestamp, or a dumpspec
must be specified.
EOF
if ($msg) {
print STDERR "ERROR: $msg\n";
}
exit(1);
}
Amanda::Util::setup_application("amvault", "server", $CONTEXT_CMDLINE, "amanda", "amanda");
my $config_overrides = new_config_overrides($#ARGV+1);
my @config_overrides_opts;
my $opt_quiet = 0;
my $opt_dry_run = 0;
my $opt_fulls_only = 0;
my $opt_latest_fulls = 0;
my $opt_incrs_only = 0;
my $opt_exact_match = 0;
my $opt_export = 0;
my $opt_src_write_timestamp;
my $opt_src_labelstr;
my $opt_src_storage_name;
my $opt_dest_storage_name;
my $opt_interactivity = 1;
debug("Arguments: " . join(' ', @ARGV));
Getopt::Long::Configure(qw{ bundling });
GetOptions(
'o=s' => sub {
push @config_overrides_opts, "-o" . $_[1];
add_config_override_opt($config_overrides, $_[1]);
},
'q|quiet' => \$opt_quiet,
'n|dry-run' => \$opt_dry_run,
'fulls-only' => \$opt_fulls_only,
'latest-fulls' => \$opt_latest_fulls,
'incrs-only' => \$opt_incrs_only,
'exact-match' => \$opt_exact_match,
'export' => \$opt_export,
'label-template=s' => sub {
usage("--label-templaple is deprecated, use autolabel from the 'dest-storage'"); },
'autolabel=s' => sub {
usage("--autolabel is deprecated, use autolabel from the 'dest-storage'"); },
'dst-changer=s' => sub {
usage("--dst-changer is deprecated, use tpchanger from the 'dest-storage'"); },
'src-timestamp=s' => \$opt_src_write_timestamp,
'src-labelstr=s' => \$opt_src_labelstr,
'src-storage=s' => \$opt_src_storage_name,
'dest-storage=s' => \$opt_dest_storage_name,
'interactivity!' => \$opt_interactivity,
'version' => \&Amanda::Util::version_opt,
'help' => \&usage,
) or usage("usage error");
usage("not enough arguments") unless (@ARGV >= 1);
my $config_name = shift @ARGV;
my $cmd_flags = $CMDLINE_PARSE_DATESTAMP|$CMDLINE_PARSE_LEVEL;
$cmd_flags |= $CMDLINE_EXACT_MATCH if $opt_exact_match;
my @opt_dumpspecs = parse_dumpspecs(\@ARGV, $cmd_flags)
if (@ARGV);
usage("specify something to select the source dumps") unless
$opt_src_write_timestamp or $opt_fulls_only or $opt_latest_fulls or
$opt_incrs_only or @opt_dumpspecs;
usage("The following options are incompatible: --fulls-only, --latest-fulls and --incrs-only") if
($opt_fulls_only + $opt_latest_fulls + $opt_incrs_only) > 1;
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) {
print STDERR "errors processing config file\n";
exit(1);
}
}
Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
# 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) {
print STDERR "errors processing disklist\n";
exit(1);
}
my $exit_status = 0;
my $exit_cb = sub {
($exit_status) = @_;
Amanda::MainLoop::quit();
};
my $is_tty = -t STDOUT;
my $last_is_size = 0;
my $delay;
if ($is_tty) {
$delay = 1000; # 1 second
} else {
$delay = 15000; # 15 seconds
}
$|++;
sub user_msg {
my $msg = shift;
if ($msg->{'code'} == 2500008) {
if ($is_tty) {
if (!$last_is_size) {
#print STDOUT "\n";
$last_is_size = 1;
}
print STDOUT "\r" . $msg . " ";
}
} else {
if ($is_tty) {
if ($last_is_size) {
print STDOUT "\n";
$last_is_size = 0;
}
}
print STDOUT $msg . "\n";
}
}
my $interactivity;
if ($opt_interactivity) {
$interactivity = main::Interactivity->new();
}
my $messages;
(my $vault, $messages) = Amanda::Vault->new(
config => $config_name,
src_write_timestamp => $opt_src_write_timestamp,
dst_write_timestamp => Amanda::Util::generate_timestamp(),
src_labelstr => $opt_src_labelstr,
opt_dumpspecs => @opt_dumpspecs? \@opt_dumpspecs : undef,
opt_dry_run => $opt_dry_run,
quiet => $opt_quiet,
fulls_only => $opt_fulls_only,
latest_fulls=> $opt_latest_fulls,
incrs_only => $opt_incrs_only,
opt_export => $opt_export,
interactivity => $interactivity,
config_overrides_opts => \@config_overrides_opts,
user_msg => \&user_msg,
delay => $delay,
is_tty => $is_tty,
src_storage_name => $opt_src_storage_name,
dest_storage_name => $opt_dest_storage_name,
);
if (!$vault) {
foreach my $message (@$messages) {
print $message, "\n";
}
$exit_status = 1;
} else {
Amanda::MainLoop::call_later(sub { $vault->run($exit_cb) });
Amanda::MainLoop::run();
}
Amanda::Util::finish_application();
exit($exit_status);