Imported Upstream version 3.3.2
[debian/amanda] / server-src / amtape.pl
index d733d7016828be287b685e24394d69dfec75d640..dec31ea6e62c7fad61f3913a009f95689b72a2d0 100644 (file)
@@ -1,5 +1,5 @@
 #! @PERL@
-# Copyright (c) 2009, 2010 Zmanda, Inc.  All Rights Reserved.
+# Copyright (c) 2009-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
@@ -19,6 +19,7 @@
 
 use lib '@amperldir@';
 use strict;
+use warnings;
 
 use File::Basename;
 use Getopt::Long;
@@ -33,9 +34,11 @@ use Amanda::Constants;
 use Amanda::MainLoop;
 use Amanda::Taper::Scan;
 use Amanda::Recovery::Scan;
-use Amanda::Interactive;
+use Amanda::Interactivity;
+use Amanda::Tapelist;
 
 my $exit_status = 0;
+my $tl;
 
 ##
 # Subcommand handling
@@ -44,10 +47,11 @@ my %subcommands;
 
 sub usage {
     my ($finished_cb) = @_;
-    $finished_cb ||= sub { exit(1); };
+
+    $finished_cb = sub { exit(1); } if (!$finished_cb or !(ref($finished_cb) eq "CODE"));
 
     print STDERR <<EOF;
-Usage: amtape <conf> <command> {<args>} [-o configoption]*
+Usage: amtape [-o configoption]* <conf> <command> {<args>}
   Valid commands are:
 EOF
     local $Text::Wrap::columns = 80 - 20;
@@ -80,7 +84,7 @@ subcommand("usage", "usage", "this message",
 sub {
     my ($finished_cb, @args) = @_;
 
-    usage($finished_cb);
+    return usage($finished_cb);
 });
 
 subcommand("reset", "reset", "reset changer to known state",
@@ -91,6 +95,7 @@ sub {
 
     $chg->reset(finished_cb => sub {
            my ($err) = @_;
+           $chg->quit();
            return failure($err, $finished_cb) if $err;
 
            print STDERR "changer is reset\n";
@@ -111,6 +116,7 @@ sub {
     $chg->eject(@drive_args,
        finished_cb => sub {
            my ($err) = @_;
+           $chg->quit();
            return failure($err, $finished_cb) if $err;
 
            print STDERR "drive ejected\n";
@@ -134,6 +140,7 @@ sub {
     $chg->clean(@drive_args,
        finished_cb => sub {
            my ($err) = @_;
+           $chg->quit();
            return failure($err, $finished_cb) if $err;
 
            print STDERR "drive cleaned\n";
@@ -141,21 +148,44 @@ sub {
        });
 });
 
-subcommand("show", "show", "scan all slots in the changer, starting with the current slot",
+subcommand("show", "show [<slots>]", "scan all slots (or listed slots) in the changer, starting with the current slot",
 sub {
     my ($finished_cb, @args) = @_;
     my $last_slot;
     my %seen_slots;
-    my $gres;
-
-    my $steps = define_steps
-       cb_ref => \$finished_cb;
+    my $chg;
 
-    if (@args != 0) {
+    if (@args > 1) {
        return usage($finished_cb);
     }
 
-    my $chg = load_changer($finished_cb) or return;
+    my $what = $args[0];
+    my @slots;
+
+    if (defined $what) {
+       my @what1 = split /,/, $what;
+       foreach my $what1 (@what1) {
+           if ($what1 =~ /^(\d*)-(\d*)$/) {
+               my $begin = $1;
+               my $end = $2;
+               $end = $begin if $begin > $end;
+               while ($begin <= $end) {
+                   push @slots, $begin;
+                   $begin++;
+               }
+           } else {
+               push @slots, $what1;
+           }
+       }
+    }
+
+    my $use_slots = @slots > 0;
+
+    $chg = load_changer($finished_cb) or return;
+
+    my $steps = define_steps
+       cb_ref => \$finished_cb,
+       finalize => sub { $chg->quit() if defined $chg };
 
     step start => sub {
        $chg->info(info => [ 'num_slots' ], info_cb => $steps->{'info_cb'});
@@ -165,13 +195,19 @@ sub {
        my ($err, %info) = @_;
        return failure($err, $finished_cb) if $err;
 
-       print STDERR "amtape: scanning all $info{num_slots} slots in changer:\n";
+       if ($use_slots) {
+          my $slot = shift @slots;
+          $chg->load(slot => $slot,
+                     mode => "read",
+                     res_cb => $steps->{'loaded'});
 
-       $steps->{'load_current'}->();
-    };
+       } else {
+           print STDERR "amtape: scanning all $info{num_slots} slots in changer:\n";
 
-    step load_current => sub {
-       $chg->load(relative_slot => 'current', mode => "read", res_cb => $steps->{'loaded'});
+           $chg->load(relative_slot => 'current',
+                      mode => "read",
+                      res_cb => $steps->{'loaded'});
+       }
     };
 
     step loaded => sub {
@@ -199,9 +235,6 @@ sub {
                print STDERR sprintf("slot %3s: date %-14s label %s\n",
                        $last_slot, $dev->volume_time(),
                        $dev->volume_label());
-               $gres = $res;
-               return $res->set_label(label => $dev->volume_label(),
-                                      finished_cb => $steps->{'set_labeled'});
            } elsif ($st == $DEVICE_STATUS_VOLUME_UNLABELED) {
                print STDERR sprintf("slot %3s: unlabeled volume\n", $last_slot);
            } else {
@@ -218,13 +251,20 @@ sub {
        }
     };
 
-    step set_labeled => sub {
-       $gres->release(finished_cb => $steps->{'released'});
-    };
-
     step released => sub {
-       $chg->load(relative_slot => 'next', slot => $last_slot,
-                  except_slots => { %seen_slots }, res_cb => $steps->{'loaded'});
+       if ($use_slots) {
+          return $finished_cb->() if @slots == 0;
+          my $slot = shift @slots;
+          $chg->load(slot => $slot,
+                     mode => "read",
+                     res_cb => $steps->{'loaded'});
+
+       } else {
+           $chg->load(relative_slot => 'next',
+                      slot => $last_slot,
+                      except_slots => { %seen_slots },
+                      res_cb => $steps->{'loaded'});
+       }
     };
 });
 
@@ -244,11 +284,16 @@ sub {
        my ($err, $inv) = @_;
        if ($err) {
            if ($err->notimpl) {
-               print STDERR "inventory not supported by this changer\n";
+               if ($err->{'message'}) {
+                   print STDERR "inventory not supported by this changer: $err->{'message'}\n";
+               } else {
+                   print STDERR "inventory not supported by this changer\n";
+               }
            } else {
                print STDERR "$err\n";
            }
 
+           $chg->quit();
            return $finished_cb->();
        }
 
@@ -256,13 +301,23 @@ sub {
            my $line = "slot $sl->{slot}:";
            if (!defined($sl->{device_status}) && !defined($sl->{label})) {
                $line .= " unknown state";
-           } elsif ($sl->{'status'} == Amanda::Changer::SLOT_EMPTY) {
+           } elsif ($sl->{'state'} == Amanda::Changer::SLOT_EMPTY) {
                $line .= " empty";
            } else {
                if (defined $sl->{label}) {
                    $line .= " label $sl->{label}";
+                   my $tle = $tl->lookup_tapelabel($sl->{label});
+                   if ($tle->{'meta'}) {
+                       $line .= " ($tle->{'meta'})";
+                   }
+               } elsif ($sl->{'device_status'} == $DEVICE_STATUS_VOLUME_UNLABELED) {
+                   $line .= " blank";
                } elsif ($sl->{'device_status'} != $DEVICE_STATUS_SUCCESS) {
-                   $line .= "device error";
+                   if (defined $sl->{'device_error'}) {
+                       $line .= " " . $sl->{'device_error'};
+                   } else {
+                       $line .= "device error";
+                   }
                } elsif ($sl->{'f_type'} != $Amanda::Header::F_TAPESTART) {
                    $line .= " blank";
                } else {
@@ -281,11 +336,15 @@ sub {
            if ($sl->{'import_export'}) {
                $line .= " (import/export slot)";
            }
+           if ($sl->{'current'}) {
+               $line .= " (current)";
+           }
 
            # note that inventory goes to stdout
            print "$line\n";
        }
 
+       $chg->quit();
        $finished_cb->();
     });
     $chg->inventory(inventory_cb => $inventory_cb);
@@ -306,19 +365,20 @@ subcommand("slot", "slot <slot>",
 sub {
     my ($finished_cb, @args) = @_;
     my @slotarg;
-    my $gres;
+    my $chg;
 
     my $steps = define_steps
-       cb_ref => \$finished_cb;
+       cb_ref => \$finished_cb,
+       finalize => sub { $chg->quit() if defined $chg };
 
     # NOTE: the syntax of this subcommand precludes actual slots named
     # 'current' or 'next' ..  when we have a changer using such slot names,
     # this subcommand will need to support a --literal flag
 
-    usage($finished_cb) unless (@args == 1);
+    return usage($finished_cb) unless (@args == 1);
     my $slot = shift @args;
 
-    my $chg = load_changer($finished_cb) or return;
+    $chg = load_changer($finished_cb) or return;
 
     step get_slot => sub {
        if ($slot eq 'current' or $slot eq 'next') {
@@ -364,17 +424,7 @@ sub {
        my $gotslot = $res->{'this_slot'};
        print STDERR "changed to slot $gotslot\n";
 
-       if ($res->{device}->volume_label) {
-           $gres = $res;
-           $res->set_label(label => $res->{device}->volume_label(),
-                           finished_cb => $steps->{'set_labeled'});
-       } else {
-           $res->release(finished_cb => $steps->{'released'});
-       }
-    };
-
-    step set_labeled => sub {
-       $gres->release(finished_cb => $steps->{'released'});
+       $res->release(finished_cb => $steps->{'released'});
     };
 
     step released => sub {
@@ -388,15 +438,17 @@ sub {
 subcommand("label", "label <label>", "load the volume with label <label>",
 sub {
     my ($finished_cb, @args) = @_;
-    my $gres;
-    my $inter;
+    my $interactivity;
     my $scan;
+    my $chg;
 
-    usage($finished_cb) unless (@args == 1);
+    return usage($finished_cb) unless (@args == 1);
     my $label = shift @args;
 
     my $steps = define_steps
-       cb_ref => \$finished_cb;
+       cb_ref => \$finished_cb,
+       finalize => sub { $scan->quit() if defined $scan;
+                         $chg->quit() if defined $chg };
 
     step start => sub {
        my $_user_msg_fn = sub {
@@ -421,8 +473,10 @@ sub {
            }
        };
 
-       $inter = Amanda::Interactive->new(name => 'stdin');
-       $scan = Amanda::Recovery::Scan->new(interactive => $inter);
+       $interactivity = Amanda::Interactivity->new(name => 'stdin');
+       $chg = load_changer($finished_cb) or return;
+       $scan = Amanda::Recovery::Scan->new(chg => $chg,
+                                           interactivity => $interactivity);
        return failure("$scan", $finished_cb)
            if ($scan->isa("Amanda::Changer::Error"));
 
@@ -441,17 +495,7 @@ sub {
        show_slot($res);
        print STDERR "label $label is now loaded from slot $gotslot\n";
 
-       if ($res->{device}->volume_label) {
-           $gres = $res;
-           $res->set_label(label => $res->{device}->volume_label(),
-                           finished_cb => $steps->{'set_labeled'});
-       } else {
-           $res->release(finished_cb => $steps->{'released'});
-       }
-    };
-
-    step set_labeled => sub {
-       $gres->release(finished_cb => $steps->{'released'});
+       $res->release(finished_cb => $steps->{'released'});
     };
 
     step released => sub {
@@ -506,7 +550,12 @@ sub {
                } elsif ($dev->status & $DEVICE_STATUS_VOLUME_UNLABELED and
                         $dev->volume_header and
                         $dev->volume_header->{'type'} == $Amanda::Header::F_WEIRD) {
-                   print STDERR " contains a non-Amanda volume; check and relabel it with 'amlabel -f'\n";
+                   my $autolabel = getconf($CNF_AUTOLABEL);
+                   if ($autolabel->{'non_amanda'}) {
+                       print STDERR " contains a non-Amanda volume\n";
+                   } else {
+                       print STDERR " contains a non-Amanda volume; check and relabel it with 'amlabel -f'\n";
+                   }
                } elsif ($dev->status & $DEVICE_STATUS_VOLUME_ERROR) {
                    my $message = $dev->error_or_status();
                    print STDERR " can't read label: $message\n";
@@ -520,27 +569,52 @@ sub {
        }
     };
 
-    usage($finished_cb) unless (@args == 0);
+    return usage($finished_cb) unless (@args == 0);
     my $label = shift @args;
 
     my $chg = load_changer($finished_cb) or return;
+    my $interactivity = Amanda::Interactivity->new(name => 'tty');
+    my $scan_name = getconf($CNF_TAPERSCAN);
+    my $taperscan = Amanda::Taper::Scan->new(algorithm => $scan_name,
+                                            changer => $chg,
+                                            tapelist => $tl);
 
     my $result_cb = make_cb(result_cb => sub {
        my ($err, $res, $label, $mode) = @_;
-       return failure($err, $finished_cb) if $err;
+       if ($err) {
+           if ($res) {
+               $res->release(finished_cb => sub {
+                   $taperscan->quit() if defined $taperscan;
+                   return failure($err, $finished_cb);
+               });
+               return;
+           } else {
+               $taperscan->quit() if defined $taperscan;
+               return failure($err, $finished_cb);
+           }
+       }
 
        my $modestr = ($mode == $ACCESS_APPEND)? "append" : "write";
        my $slot = $res->{'this_slot'};
-       print STDERR "Will $modestr to volume $label in slot $slot.\n";
+       if (defined $res->{'device'} and defined $res->{'device'}->volume_label()) {
+           print STDERR "Will $modestr to volume '$label' in slot $slot.\n";
+       } else {
+           my $header = $res->{'device'}->volume_header();
+           if ($header->{'type'} == $Amanda::Header::F_WEIRD) {
+               print STDERR "Will $modestr label '$label' to non-Amanda volume in slot $slot.\n";
+           } else {
+               print STDERR "Will $modestr label '$label' to new volume in slot $slot.\n";
+           }
+       }
        $res->release(finished_cb => sub {
            my ($err) = @_;
            die "$err" if $err;
 
+           $taperscan->quit() if defined $taperscan;
            $finished_cb->();
        });
     });
 
-    my $taperscan = Amanda::Taper::Scan->new(changer => $chg);
     $taperscan->scan(
        result_cb => $result_cb,
        user_msg_fn => $taper_user_msg_fn,
@@ -563,6 +637,7 @@ sub {
        },
        finished_cb => sub {
            my ($err) = @_;
+           $chg->quit();
            return failure($err, $finished_cb) if $err;
 
            print STDERR "update complete\n";
@@ -576,7 +651,7 @@ sub {
 sub load_changer {
     my ($finished_cb) = @_;
 
-    my $chg = Amanda::Changer->new();
+    my $chg = Amanda::Changer->new(undef, tapelist => $tl);
     return failure($chg, $finished_cb) if ($chg->isa("Amanda::Changer::Error"));
     return $chg;
 }
@@ -610,8 +685,10 @@ Amanda::Util::setup_application("amtape", "server", $CONTEXT_CMDLINE);
 
 my $config_overrides = new_config_overrides($#ARGV+1);
 
+debug("Arguments: " . join(' ', @ARGV));
 Getopt::Long::Configure(qw(bundling));
 GetOptions(
+    'version' => \&Amanda::Util::version_opt,
     'help|usage|?' => \&usage,
     'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
 ) or usage();
@@ -631,6 +708,9 @@ if ($cfgerr_level >= $CFGERR_WARNINGS) {
 
 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
 
+my $tlf = Amanda::Config::config_dir_relative(getconf($CNF_TAPELIST));
+$tl = Amanda::Tapelist->new($tlf);
+
 #make STDOUT not line buffered
 my $previous_fh = select(STDOUT);
 $| = 1;
@@ -639,10 +719,16 @@ select($previous_fh);
 sub main {
     my ($finished_cb) = @_;
 
-    my $subcmd = shift @ARGV;
-    usage($finished_cb) unless defined($subcmd) and exists ($subcommands{$subcmd});
-    invoke_subcommand($subcmd, $finished_cb, @ARGV);
+    my $steps = define_steps
+       cb_ref => \$finished_cb;
+
+    step start => sub {
+       my $subcmd = shift @ARGV;
+       return usage($finished_cb) unless defined($subcmd) and exists ($subcommands{$subcmd});
+       invoke_subcommand($subcmd, $finished_cb, @ARGV);
+    }
 }
+
 main(\&Amanda::MainLoop::quit);
 Amanda::MainLoop::run();
 Amanda::Util::finish_application();