Imported Upstream version 3.3.3
[debian/amanda] / perl / Amanda / Changer / robot.pm
index dae6af016477daa0bf36643be5388c065a4d4eb4..9ba2a5890d63433ea1e40984e8f5a0c442728c77 100644 (file)
@@ -1,8 +1,9 @@
 # Copyright (c) 2009-2012 Zmanda, Inc.  All Rights Reserved.
 #
-# This library is free software; you can redistribute it and/or modify it
-# under the terms of the GNU Lesser General Public License version 2.1 as
-# published by the Free Software Foundation.
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+#* License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
 #
 # This library is distributed in the hope that it will be useful, but
 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
@@ -27,6 +28,7 @@ use vars qw( @ISA );
 use Data::Dumper;
 use File::Path;
 use Amanda::Paths;
+use Amanda::Config qw( :init );
 use Amanda::MainLoop qw( :GIOCondition make_cb define_steps step );
 use Amanda::Config qw( :getconf );
 use Amanda::Debug qw( debug warning );
@@ -59,7 +61,7 @@ See the amanda-changers(7) manpage for usage information.
 #   drives - see below
 #   drive_lru - recently used drives, least recent first
 #   bc2lb - hash mapping known barcodes to known labels
-#   current_slot - the current slot
+#   current_slot - hash of the current slot
 #   last_operation_time - time the last operation finished
 #   last_operation_delay - required delay for that operation
 #   last_status - last time a 'status' command finished
@@ -83,7 +85,10 @@ See the amanda-changers(7) manpage for usage information.
 #   label - volume label
 #   barcode - volume barcode
 #   orig_slot - slot from which this tape was loaded
-
+#
+## The 'current_slot' key is also a hash by config name, the values of
+# which are the current slot for the config.
+# #
 # LOCKING
 #
 # This package uses Amanda::Changer's with_locked_state to lock a statefile and
@@ -130,6 +135,7 @@ sub new {
 
         # set below from properties
         statefile => undef,
+       'lock-timeout' => undef,
         drive2device => {}, # { drive => device name }
        driveorder => [], # order of tape-device properties
        drive_choice => 'lru',
@@ -161,7 +167,7 @@ sub new {
         $self->{'statefile'} = "$localstatedir/amanda/$safe_filename";
     }
     $self->_debug("using statefile '$self->{statefile}'");
-
+    $self->{'lock-timeout'} = $config->{'lock-timeout'};
     # figure out the drive number to device name mapping
     if (exists $config->{'tapedev'}
            and $config->{'tapedev'} ne ''
@@ -337,24 +343,32 @@ sub load_unlocked {
         if (exists $params{'relative_slot'}) {
             if ($params{'relative_slot'} eq "next") {
                if (exists $params{'slot'}) {
-                   $slot = $self->_get_next_slot($state, $params{'slot'});
-                   $self->_debug("loading next relative to $params{slot}: $slot");
+                   $slot = $self->_get_next_slot($state, $params{'slot'}, $params{'except_slots'});
+                   if (defined $slot) {
+                       $self->_debug("loading next relative to $params{slot}: $slot");
+                   } else {
+                       $self->_debug("no next slot relative to $params{slot}");
+                   }
                } else {
-                   $slot = $self->_get_next_slot($state, $state->{'current_slot'});
-                   $self->_debug("loading next relative to current slot: $slot");
+                   $slot = $self->_get_next_slot($state, $state->{'current_slot'}{get_config_name()}, $params{'except_slots'});
+                   if (defined $slot) {
+                       $self->_debug("loading next relative to current slot: $slot");
+                   } else {
+                       $self->_debug("no next relative to current slot");
+                   }
                }
-               if ($slot == -1) {
+               if (defined $slot && $slot == -1) {
                    return $self->make_error("failed", $params{'res_cb'},
                            reason => "invalid",
                            message => "could not find next slot");
                }
             } elsif ($params{'relative_slot'} eq "current") {
-                $slot = $state->{'current_slot'};
-               if ($slot == -1) {
+                $slot = $state->{'current_slot'}{get_config_name()};
+               if (defined $slot && $slot == -1) {
                    # seek to the first slot
-                   $slot = $self->_get_next_slot($state, $state->{'current_slot'});
+                   $slot = $self->_get_next_slot($state, $state->{'current_slot'}{get_config_name()}, $params{'except_slots'});
                }
-               if ($slot == -1) {
+               if (defined $slot && $slot == -1) {
                    return $self->make_error("failed", $params{'res_cb'},
                            reason => "invalid",
                            message => "no current slot");
@@ -396,6 +410,30 @@ sub load_unlocked {
                     message => "no 'slot' or 'label' specified to load()");
         }
 
+        if (defined $slot and !exists $state->{'slots'}->{$slot}) {
+            return $self->make_error("failed", $params{'res_cb'},
+                    reason => "invalid",
+                    message => "invalid slot '$slot'");
+        }
+
+       if (!defined $slot) {
+           my $all_empty = 1;
+           if (exists $params{'except_slots'}) {
+               for my $xslot (keys %{ $params{'except_slots'} }) {
+                   if ($state->{'slots'}->{$xslot}->{'state'} ne Amanda::Changer::SLOT_EMPTY) {
+                       $all_empty = 0;
+                   }
+               }
+               if ($all_empty) {
+                   return $self->make_error("failed", $params{'res_cb'},
+                       reason => "notfound",
+                       message => "all slots are empty");
+               }
+           }
+           return $self->make_error("failed", $params{'res_cb'},
+                   reason => "notfound",
+                   message => "all slots have been loaded");
+       }
        if (!$self->_is_slot_allowed($slot)) {
            if (exists $params{'label'}) {
                return $self->make_error("failed", $params{'res_cb'},
@@ -410,14 +448,28 @@ sub load_unlocked {
        }
 
        if (exists $params{'except_slots'} and exists $params{'except_slots'}->{$slot}) {
-           return $self->make_error("failed", $params{'res_cb'},
-               reason => "notfound",
-               message => "all slots have been loaded");
+           # if all slots in except_slots are EMPTY
+           my $all_empty = 1;
+           for my $xslot (keys %{ $params{'except_slots'} }) {
+               if ($state->{'slots'}->{$xslot}->{'state'} ne Amanda::Changer::SLOT_EMPTY) {
+                   $all_empty = 0;
+               }
+           }
+           if ($all_empty) {
+               return $self->make_error("failed", $params{'res_cb'},
+                   reason => "notfound",
+                   message => "all slots are empty");
+           } else {
+               return $self->make_error("failed", $params{'res_cb'},
+                   reason => "notfound",
+                   message => "all slots have been loaded");
+           }
        }
 
        if ($state->{'slots'}->{$slot}->{'state'} eq Amanda::Changer::SLOT_EMPTY) {
            return $self->make_error("failed", $params{'res_cb'},
                    reason => "empty",
+                   slot   => $slot,
                    message => "slot $slot is empty");
        }
 
@@ -636,9 +688,13 @@ sub load_unlocked {
            # update metadata with this new information
            $state->{'slots'}->{$slot}->{'state'} = Amanda::Changer::SLOT_FULL;
            $state->{'slots'}->{$slot}->{'device_status'} = $device->status;
-           $state->{'slots'}->{$slot}->{'device_error'} = $device->error;
-           if (defined $device->{'volume_header'}) {
-               $state->{'slots'}->{$slot}->{'f_type'} = $device->{'volume_header'}->{type};
+           if ($device->status == $DEVICE_STATUS_SUCCESS) {
+               $state->{'slots'}->{$slot}->{'device_error'} = undef;
+           } else {
+               $state->{'slots'}->{$slot}->{'device_error'} = $device->error;
+           }
+           if (defined $device->volume_header) {
+               $state->{'slots'}->{$slot}->{'f_type'} = $device->volume_header->{type};
            } else {
                $state->{'slots'}->{$slot}->{'f_type'} = undef;
            }
@@ -665,9 +721,13 @@ sub load_unlocked {
            # update metadata with this new information
            $state->{'slots'}->{$slot}->{'state'} = Amanda::Changer::SLOT_FULL;
            $state->{'slots'}->{$slot}->{'device_status'} = $device->status;
-           $state->{'slots'}->{$slot}->{'device_error'} = $device->error;
-           if (defined $device->{'volume_header'}) {
-               $state->{'slots'}->{$slot}->{'f_type'} = $device->{'volume_header'}->{type};
+           if ($device->status == $DEVICE_STATUS_SUCCESS) {
+               $state->{'slots'}->{$slot}->{'device_error'} = undef;
+           } else {
+               $state->{'slots'}->{$slot}->{'device_error'} = $device->error;
+           }
+           if (defined $device->volume_header) {
+               $state->{'slots'}->{$slot}->{'f_type'} = $device->volume_header->{type};
            } else {
                $state->{'slots'}->{$slot}->{'f_type'} = undef;
            }
@@ -681,6 +741,16 @@ sub load_unlocked {
                    message => "Found unlabeled tape while looking for '$params{label}'");
        }
 
+       if (defined $self->{'tapelist'}) {
+           my $tle = $self->{'tapelist'}->lookup_tapelabel($label);
+           if (defined $tle and defined $tle->{'barcode'} and
+               defined $state->{'slots'}->{$slot}->{'barcode'} and
+               $state->{'slots'}->{$slot}->{'barcode'} ne $tle->{'barcode'}) {
+               return $self->make_error("failed", $params{'res_cb'},
+                   reason => "device",
+                   message => "Slot $slot, label '$label', mismatch barcode between changer '$state->{'slots'}->{$slot}->{'barcode'}' and tapelist file '$tle->{'barcode'}'");
+           }
+       }
         my $res = Amanda::Changer::robot::Reservation->new($self, $slot, $drive,
                                 $device, $state->{'slots'}->{$slot}->{'barcode'});
 
@@ -695,6 +765,16 @@ sub load_unlocked {
        $state->{'drives'}->{$drive}->{'state'} = Amanda::Changer::SLOT_FULL;
        $state->{'drives'}->{$drive}->{'barcode'} = $state->{'slots'}->{$slot}->{'barcode'};
        $state->{'slots'}->{$slot}->{'device_status'} = $device->status;
+       if ($device->status == $DEVICE_STATUS_SUCCESS) {
+           $state->{'slots'}->{$slot}->{'device_error'} = undef;
+       } else {
+           $state->{'slots'}->{$slot}->{'device_error'} = $device->error;
+       }
+       if (defined $device->volume_header) {
+           $state->{'slots'}->{$slot}->{'f_type'} = $device->volume_header->{type};
+       } else {
+           $state->{'slots'}->{$slot}->{'f_type'} = undef;
+       }
        my $barcode = $state->{'slots'}->{$slot}->{'barcode'};
        if ($label and $barcode) {
            my $old_label = $state->{'bc2lb'}->{$barcode};
@@ -706,7 +786,7 @@ sub load_unlocked {
        }
        if ($params{'set_current'}) {
            $self->_debug("setting current slot to $slot");
-           $state->{'current_slot'} = $slot;
+           $state->{'current_slot'}{get_config_name()} = $slot;
        }
 
         return $params{'res_cb'}->(undef, $res);
@@ -804,13 +884,13 @@ sub get_device { # (overridden by subclasses)
     my $device = Amanda::Device->new($device_name);
     if ($device->status != $DEVICE_STATUS_SUCCESS) {
        return Amanda::Changer->make_error("fatal", undef,
-               reason => "unknown",
+               reason => "device",
                message => "opening '$device_name': " . $device->error_or_status());
     }
 
     if (my $err = $self->{'config'}->configure_device($device)) {
        return Amanda::Changer->make_error("fatal", undef,
-               reason => "unknown",
+               reason => "device",
                message => $err);
     }
 
@@ -920,7 +1000,7 @@ sub reset_unlocked {
     my %params = @_;
     my $state = $params{'state'};
 
-    $state->{'current_slot'} = $self->_get_next_slot($state, -1);
+    $state->{'current_slot'}{get_config_name()} = $self->_get_next_slot($state, -1);
 
     $params{'finished_cb'}->();
 }
@@ -1270,7 +1350,7 @@ sub inventory_unlocked {
            if $slot->{'ie'};
 
        $i->{'current'} = 1
-           if $slot_name eq $state->{'current_slot'};
+           if $slot_name eq $state->{'current_slot'}{get_config_name()};
 
        push @inv, $i;
     }
@@ -1387,11 +1467,12 @@ sub move_unlocked {
 # the changer status has been updated)
 sub _get_next_slot {
     my $self = shift;
-    my ($state, $slot) = @_;
+    my ($state, $slot, $except_slots) = @_;
 
     my @nonempty = sort { $a <=> $b } grep {
        $state->{'slots'}->{$_}->{'state'} == Amanda::Changer::SLOT_FULL
        and $self->_is_slot_allowed($_)
+       and (!$except_slots || !$except_slots->{$_})
     } keys(%{$state->{'slots'}});
 
     my @higher = grep { $_ > $slot } @nonempty;
@@ -1418,7 +1499,6 @@ sub _is_slot_allowed {
     return 0;
 }
 
-# add a prefix and call Amanda::Debug::debug
 sub _debug {
     my $self = shift;
     my ($msg) = @_;
@@ -1502,7 +1582,13 @@ sub _with_updated_state {
            $state->{'drives'} = {};
            $state->{'drive_lru'} = [];
            $state->{'bc2lb'} = {};
-           $state->{'current_slot'} = -1;
+           $state->{'current_slot'}{get_config_name()} = -1;
+       }
+
+       if (defined $state->{'current_slot'} &&
+           ref(\$state->{'current_slot'}) eq "SCALAR") {
+           my $current_slot = $state->{'current_slot'};
+           $state->{'current_slot'} = {get_config_name() => $current_slot};
        }
 
        # this is for testing ONLY!
@@ -1698,8 +1784,9 @@ sub _with_updated_state {
            }
        }
 
-       if ($state->{'current_slot'} == -1) {
-           $state->{'current_slot'} = $self->_get_next_slot($state, -1);
+       if (!defined $state->{'current_slot'}{get_config_name()} ||
+           $state->{'current_slot'}{get_config_name()} == -1) {
+           $state->{'current_slot'}{get_config_name()} = $self->_get_next_slot($state, -1);
        }
 
        $steps->{'done'}->();