lintian doesn't like orphan packages with uploaders...
[debian/amanda] / server-src / amvault.pl
index ebfc627e92ad496e1b2d9867f92e5e289937f259..de47562eec2ca23b6cd58e42f6d5ac52773c348a 100644 (file)
@@ -1,9 +1,10 @@
 #! @PERL@
-# Copyright (c) 2008, 2009, 2010 Zmanda, Inc.  All Rights Reserved.
+# Copyright (c) 2008-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
@@ -21,11 +22,11 @@ use lib '@amperldir@';
 use strict;
 use warnings;
 
-package main::Interactive;
+package main::Interactivity;
 use POSIX qw( :errno_h );
 use Amanda::MainLoop qw( :GIOCondition );
 use vars qw( @ISA );
-@ISA = qw( Amanda::Interactive );
+@ISA = qw( Amanda::Interactivity );
 
 sub new {
     my $class = shift;
@@ -61,12 +62,12 @@ sub user_request {
        if (!defined $n_read) {
            return if ($! == EINTR);
            $self->abort();
-           return $params{'finished_cb'}->(
+           return $params{'request_cb'}->(
                Amanda::Changer::Error->new('fatal',
                        message => "Fail to read from stdin"));
        } elsif ($n_read == 0) {
            $self->abort();
-           return $params{'finished_cb'}->(
+           return $params{'request_cb'}->(
                Amanda::Changer::Error->new('fatal',
                        message => "Aborted by user"));
        } else {
@@ -76,7 +77,7 @@ sub user_request {
                chomp $line;
                $buffer = "";
                $self->abort();
-               return $params{'finished_cb'}->(undef, $line);
+               return $params{'request_cb'}->(undef, $line);
            }
        }
     };
@@ -93,7 +94,7 @@ sub user_request {
 package Amvault;
 
 use Amanda::Config qw( :getconf config_dir_relative );
-use Amanda::Debug qw( :logging );
+use Amanda::Debug qw( :logging debug );
 use Amanda::Xfer qw( :constants );
 use Amanda::Header qw( :constants );
 use Amanda::MainLoop;
@@ -108,8 +109,8 @@ use Amanda::Changer qw( :constants );
 use Amanda::Cmdline;
 use Amanda::Paths;
 use Amanda::Logfile qw( :logtype_t log_add log_add_full
-                       log_rename $amanda_log_trace_log make_stats
-                       match_datestamp match_level );
+                       log_rename $amanda_log_trace_log make_stats );
+use Amanda::Util qw ( match_datestamp match_level );
 
 use base qw(
     Amanda::Recovery::Clerk::Feedback
@@ -141,6 +142,7 @@ sub new {
        exporting => 0, # is an export in progress?
        call_after_export => undef, # call this when export complete
        config_overrides_opts => $params{'config_overrides_opts'},
+       trace_log_filename => getconf($CNF_LOGDIR) . "/log",
 
        # called when the operation is complete, with the exit
        # status
@@ -148,6 +150,47 @@ sub new {
     }, $class;
 }
 
+sub run_subprocess {
+    my ($proc, @args) = @_;
+
+    my $pid = POSIX::fork();
+    if ($pid == 0) {
+       my $null = POSIX::open("/dev/null", POSIX::O_RDWR);
+       POSIX::dup2($null, 0);
+       POSIX::dup2($null, 1);
+       POSIX::dup2($null, 2);
+       exec $proc, @args;
+       die "Could not exec $proc: $!";
+    }
+    waitpid($pid, 0);
+    my $s = $? >> 8;
+    debug("$proc exited with code $s: $!");
+}
+
+sub do_amcleanup {
+    my $self = shift;
+
+    return 1 unless -f $self->{'trace_log_filename'};
+
+    # logfiles are still around.  First, try an amcleanup -p to see if
+    # the actual processes are already dead
+    debug("runing amcleanup -p");
+    run_subprocess("$sbindir/amcleanup", '-p', $self->{'config_name'},
+                  $self->{'config_overrides_opts'});
+
+    return 1 unless -f $self->{'trace_log_filename'};
+
+    return 0;
+}
+
+sub bail_already_running() {
+    my $self = shift;
+    my $msg = "An Amanda process is already running - please run amcleanup manually";
+    print "$msg\n";
+    debug($msg);
+    $self->{'exit_cb'}->(1);
+}
+
 sub run {
     my $self = shift;
     my ($exit_cb) = @_;
@@ -163,7 +206,20 @@ sub run {
 
     # open up a trace log file and put our imprimatur on it, unless dry_runing
     if (!$self->{'opt_dry_run'}) {
+       if (!$self->do_amcleanup()) {
+           return $self->bail_already_running();
+       }
        log_add($L_INFO, "amvault pid $$");
+
+       # Check we own the log file
+       open(my $tl, "<", $self->{'trace_log_filename'})
+           or die("could not open trace log file '$self->{'trace_log_filename'}': $!");
+       if (<$tl> !~ /^INFO amvault amvault pid $$/) {
+           debug("another amdump raced with this one, and won");
+           close($tl);
+           return $self->bail_already_running();
+       }
+       close($tl);
        log_add($L_START, "date " . $self->{'dst_write_timestamp'});
        Amanda::Debug::add_amanda_log_handler($amanda_log_trace_log);
        $self->{'cleanup'}{'roll_trace_log'} = 1;
@@ -178,7 +234,7 @@ sub setup_src {
     my $src = $self->{'src'} = {};
 
     # put together a clerk, which of course requires a changer, scan,
-    # interactive, and feedback
+    # interactivity, and feedback
     my $chg = Amanda::Changer->new();
     return $self->failure("Error opening source changer: $chg")
        if $chg->isa('Amanda::Changer::Error');
@@ -186,11 +242,11 @@ sub setup_src {
 
     $src->{'seen_labels'} = {};
 
-    $src->{'interactive'} = main::Interactive->new();
+    $src->{'interactivity'} = main::Interactivity->new();
 
     $src->{'scan'} = Amanda::Recovery::Scan->new(
            chg => $src->{'chg'},
-           interactive => $src->{'interactive'});
+           interactivity => $src->{'interactivity'});
 
     $src->{'clerk'} = Amanda::Recovery::Clerk->new(
            changer => $src->{'chg'},
@@ -249,7 +305,7 @@ sub setup_src {
        # convert the timestamp and level to a dumpspec
        my $level = $self->{'fulls_only'}? "0" : undef;
        push @dumpspecs, Amanda::Cmdline::dumpspec_t->new(
-               undef, undef, $self->{'src_write_timestamp'}, $level, undef);
+               undef, undef, undef, $level, $self->{'src_write_timestamp'});
     }
 
     # if we ignored all of the dumpspecs and didn't create any, then dump
@@ -313,7 +369,7 @@ sub plan_cb {
                      $dump->{'dump_timestamp'} . " " .
                      $dump->{'level'} . "\n";
            }
-           $total_kb += $dump->{'kb'};
+           $total_kb += int $dump->{'kb'};
        }
 
        print STDOUT "Total Size: $total_kb KB\n";
@@ -341,17 +397,28 @@ sub plan_cb {
 sub setup_dst {
     my $self = shift;
     my $dst = $self->{'dst'} = {};
+    my $tlf = Amanda::Config::config_dir_relative(getconf($CNF_TAPELIST));
+    my $tl = Amanda::Tapelist->new($tlf);
 
     $dst->{'label'} = undef;
     $dst->{'tape_num'} = 0;
 
-    my $chg = Amanda::Changer->new($self->{'dst_changer'});
+    my $chg = Amanda::Changer->new($self->{'dst_changer'},
+                                  tapelist => $tl,
+                                  labelstr => getconf($CNF_LABELSTR),
+                                  autolabel => $self->{'dst_autolabel'});
     return $self->failure("Error opening destination changer: $chg")
        if $chg->isa('Amanda::Changer::Error');
     $dst->{'chg'} = $chg;
 
+    my $interactivity = Amanda::Interactivity->new(
+                                       name => getconf($CNF_INTERACTIVITY));
+    my $scan_name = getconf($CNF_TAPERSCAN);
     $dst->{'scan'} = Amanda::Taper::Scan->new(
+       algorithm => $scan_name,
        changer => $dst->{'chg'},
+       interactivity => $interactivity,
+       tapelist => $tl,
        labelstr => getconf($CNF_LABELSTR),
        autolabel => $self->{'dst_autolabel'});
 
@@ -374,7 +441,7 @@ sub scribe_started {
 
     my $xfers_finished = sub {
        my ($err) = @_;
-       $self->failure($err) if $err;
+       return $self->failure($err) if $err;
        $self->quit(0);
     };
 
@@ -461,7 +528,9 @@ sub xfer_dumps {
 
        # create and start the transfer
        $xfer = Amanda::Xfer->new([ $xfer_src, $xfer_dst ]);
-       $xfer->start($steps->{'handle_xmsg'});
+       my $size = 0;
+       $size = $current->{'dump'}->{'bytes'} if exists $current->{'dump'}->{'bytes'};
+       $xfer->start($steps->{'handle_xmsg'}, 0, $size);
 
        # count the "threads" running here (clerk and scribe)
        $n_threads = 2;
@@ -564,6 +633,16 @@ sub quit {
     my $steps = define_steps
            cb_ref => \$exit_cb;
 
+    # the export may not start until we quit the scribe, so wait for it now..
+    step check_exporting => sub {
+       # if we're exporting the final volume, wait for that to complete
+       if ($self->{'exporting'}) {
+           $self->{'call_after_export'} = $steps->{'quit_scribe'};
+       } else {
+           $steps->{'quit_scribe'}->();
+       }
+    };
+
     # we may have several resources to clean up..
     step quit_scribe => sub {
        if ($self->{'cleanup'}{'quit_scribe'}) {
@@ -571,28 +650,19 @@ sub quit {
            $self->{'dst'}{'scribe'}->quit(
                finished_cb => $steps->{'quit_scribe_finished'});
        } else {
-           $steps->{'check_exporting'}->();
+           $steps->{'quit_clerk'}->();
        }
     };
 
     step quit_scribe_finished => sub {
+       $self->{'dst'}{'scan'}->quit();
        my ($err) = @_;
        if ($err) {
            print STDERR "$err\n";
            $exit_status = 1;
        }
 
-       $steps->{'check_exporting'}->();
-    };
-
-    # the export may not start until we quit the scribe, so wait for it now..
-    step check_exporting => sub {
-       # if we're exporting the final volume, wait for that to complete
-       if ($self->{'exporting'}) {
-           $self->{'call_after_export'} = $steps->{'quit_clerk'};
-       } else {
-           $steps->{'quit_clerk'}->();
-       }
+       $steps->{'quit_clerk'}->();
     };
 
     step quit_clerk => sub {
@@ -616,6 +686,14 @@ sub quit {
     };
 
     step roll_log => sub {
+       if (defined $self->{'src'}->{'chg'}) {
+           $self->{'src'}->{'chg'}->quit();
+           $self->{'src'}->{'chg'} = undef;
+       }
+       if (defined $self->{'dst'}->{'chg'}) {
+           $self->{'dst'}->{'chg'}->quit();
+           $self->{'dst'}->{'chg'} = undef;
+       }
        if ($self->{'cleanup'}{'roll_trace_log'}) {
            log_add_full($L_FINISH, "driver", "fake driver finish");
            log_add($L_INFO, "pid-done $$");
@@ -726,6 +804,7 @@ sub scribe_notif_log_info {
     my $self = shift;
     my %params = @_;
 
+    debug("$params{'message'}");
     log_add_full($L_INFO, "taper", $params{'message'});
 }
 
@@ -739,7 +818,7 @@ sub scribe_notif_tape_done {
     # exports are going on simultaneously.
     $self->{'exporting'}++;
 
-    my $finished_cb = sub {};
+    my $finished_cb = $params{'finished_cb'};
     my $steps = define_steps
        cb_ref => \$finished_cb;
 
@@ -860,6 +939,7 @@ sub usage {
 
 Usage: amvault [-o configoption...] [-q] [--quiet] [-n] [--dry-run]
           [--fulls-only] [--export] [--src-timestamp src-timestamp]
+          [--exact-match]
           --label-template label-template --dst-changer dst-changer
           [--autolabel autolabel-arg...]
           config
@@ -895,6 +975,7 @@ my @config_overrides_opts;
 my $opt_quiet = 0;
 my $opt_dry_run = 0;
 my $opt_fulls_only = 0;
+my $opt_exact_match = 0;
 my $opt_export = 0;
 my $opt_autolabel = {};
 my $opt_autolabel_seen = 0;
@@ -928,6 +1009,7 @@ sub add_autolabel {
     usage("unknown --autolabel value '$val'");
 }
 
+debug("Arguments: " . join(' ', @ARGV));
 Getopt::Long::Configure(qw{ bundling });
 GetOptions(
     'o=s' => sub {
@@ -937,6 +1019,7 @@ GetOptions(
     'q|quiet' => \$opt_quiet,
     'n|dry-run' => \$opt_dry_run,
     'fulls-only' => \$opt_fulls_only,
+    'exact-match' => \$opt_exact_match,
     'export' => \$opt_export,
     'label-template=s' => \&set_label_template,
     'autolabel=s' => \&add_autolabel,
@@ -950,7 +1033,9 @@ $opt_autolabel->{'empty'} = 1 unless $opt_autolabel_seen;
 usage("not enough arguments") unless (@ARGV >= 1);
 
 my $config_name = shift @ARGV;
-my @opt_dumpspecs = parse_dumpspecs(\@ARGV, $CMDLINE_PARSE_DATESTAMP|$CMDLINE_PARSE_LEVEL)
+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("no --label-template given") unless $opt_autolabel->{'template'};