Imported Upstream version 3.3.2
[debian/amanda] / perl / Amanda / Changer / robot.pm
index 00822452887965e6220ba83191dca18dd4ddebb1..dae6af016477daa0bf36643be5388c065a4d4eb4 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2009,2010 Zmanda, Inc.  All Rights Reserved.
+# 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
@@ -68,6 +68,7 @@ See the amanda-changers(7) manpage for usage information.
 # as values.  Each slot's hash has keys
 #   state - SLOT_FULL/SLOT_EMPTY/SLOT_UNKNOWN
 #   device_status - the status of the device
+#   device_error - the error message of the device
 #   f_type - The f_type of the header
 #   label - volume label, if known
 #   barcode - volume barcode, if available
@@ -193,7 +194,7 @@ sub new {
     }
 
     # eject-before-unload
-    my $ebu = $self->get_boolean_property($self->{'config'},
+    my $ebu = $self->{'config'}->get_boolean_property(
                                            "eject-before-unload", 0);
     if (!defined $ebu) {
        return Amanda::Changer->make_error("fatal", undef,
@@ -202,7 +203,7 @@ sub new {
     $self->{'eject_before_unload'} = $ebu;
 
     # fast-search
-    my $fast_search = $self->get_boolean_property($self->{'config'},
+    my $fast_search = $self->{'config'}->get_boolean_property(
                                                "fast-search", 1);
     if (!defined $fast_search) {
        return Amanda::Changer->make_error("fatal", undef,
@@ -266,7 +267,7 @@ sub new {
     }
 
     # status-interval, eject-delay, unload-delay
-    for my $propname qw(status-interval eject-delay unload-delay) {
+    for my $propname (qw(status-interval eject-delay unload-delay)) {
        next unless exists $config->{'properties'}->{$propname};
        if (@{$config->{'properties'}->{$propname}->{'values'}} > 1) {
            return Amanda::Changer->make_error("fatal", undef,
@@ -287,7 +288,7 @@ sub new {
        $self->{$key} = $time;
     }
 
-    my $ignore_barcodes = $self->get_boolean_property($self->{'config'},
+    my $ignore_barcodes = $self->{'config'}->get_boolean_property(
                                            "ignore-barcodes", 0);
     if (!defined $ignore_barcodes) {
        return Amanda::Changer->make_error("fatal", undef,
@@ -416,7 +417,7 @@ sub load_unlocked {
 
        if ($state->{'slots'}->{$slot}->{'state'} eq Amanda::Changer::SLOT_EMPTY) {
            return $self->make_error("failed", $params{'res_cb'},
-                   reason => "notfound",
+                   reason => "empty",
                    message => "slot $slot is empty");
        }
 
@@ -453,7 +454,7 @@ sub load_unlocked {
            }
 
            # otherwise, we can jump all the way to the end of this process
-           return $steps->{'check_device'}->();
+           return $steps->{'start_polling'}->();
        }
 
        # here is where we implement each of the drive-selection algorithms
@@ -470,7 +471,7 @@ sub load_unlocked {
            @check_order = (@{$self->{'driveorder'}});
        } else {
            # the constructor should detect this circumstance
-           die "invalid drive_choice";
+           confess "invalid drive_choice";
        }
 
        my %checked;
@@ -586,7 +587,7 @@ sub load_unlocked {
 
     step check_device => sub {
        my $device_name = $self->{'drive2device'}->{$drive};
-       die "drive $drive not found in drive2device" unless $device_name; # shouldn't happen
+       confess "drive $drive not found in drive2device" unless $device_name; # shouldn't happen
 
        $self->_debug("polling '$device_name' to see if it's ready");
 
@@ -617,9 +618,7 @@ sub load_unlocked {
        } elsif ($device->status & $DEVICE_STATUS_VOLUME_UNLABELED) {
            $label = undef;
        } else {
-           return $self->make_error("fatal", $params{'res_cb'},
-                   message => "while waiting for '$device_name' to become ready: "
-                       . $device->error_or_status());
+           $label = undef;
        }
 
        # success!
@@ -637,14 +636,21 @@ sub load_unlocked {
            # update metadata with this new information
            $state->{'slots'}->{$slot}->{'state'} = Amanda::Changer::SLOT_FULL;
            $state->{'slots'}->{$slot}->{'device_status'} = $device->status;
-           if (defined $device->{'header'}) {
-               $state->{'slots'}->{$slot}->{'f_type'} = $device->{'header'}->{type};
+           $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;
            }
            $state->{'slots'}->{$slot}->{'label'} = $label;
            if ($state->{'slots'}->{$slot}->{'barcode'}) {
-               $state->{'bc2lb'}->{$state->{'slots'}->{$slot}->{'barcode'}} = $label;
+               my $barcode = $state->{'slots'}->{$slot}->{'barcode'};
+               my $old_label = $state->{'bc2lb'}->{$barcode};
+               if ($label ne $old_label) {
+                   $self->_debug("make_res: slot $slot");
+                   $self->_debug("update label '$label' for barcode '$barcode', old label was '$old_label'");
+               }
+               $state->{'bc2lb'}->{$barcode} = $label;
            }
 
            return $self->make_error("failed", $params{'res_cb'},
@@ -659,8 +665,9 @@ sub load_unlocked {
            # update metadata with this new information
            $state->{'slots'}->{$slot}->{'state'} = Amanda::Changer::SLOT_FULL;
            $state->{'slots'}->{$slot}->{'device_status'} = $device->status;
-           if (defined $device->{'header'}) {
-               $state->{'slots'}->{$slot}->{'f_type'} = $device->{'header'}->{type};
+           $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;
            }
@@ -687,12 +694,18 @@ sub load_unlocked {
        $state->{'drives'}->{$drive}->{'label'} = $label;
        $state->{'drives'}->{$drive}->{'state'} = Amanda::Changer::SLOT_FULL;
        $state->{'drives'}->{$drive}->{'barcode'} = $state->{'slots'}->{$slot}->{'barcode'};
-       #$state->{'slots'}->{$slot}->{'device_status'} = 9;
-       if ($label and $state->{'slots'}->{$slot}->{'barcode'}) {
-           $state->{'bc2lb'}->{$state->{'slots'}->{$slot}->{'barcode'}} = $label;
+       $state->{'slots'}->{$slot}->{'device_status'} = $device->status;
+       my $barcode = $state->{'slots'}->{$slot}->{'barcode'};
+       if ($label and $barcode) {
+           my $old_label = $state->{'bc2lb'}->{$barcode};
+           if (defined $old_label and $old_label ne $label) {
+               $self->_debug("load drive $drive slot $slot");
+               $self->_debug("update label '$label' for barcode '$barcode', old label was '$old_label'");
+           }
+           $state->{'bc2lb'}->{$barcode} = $label;
        }
        if ($params{'set_current'}) {
-               $self->_debug("setting current slot to $slot");
+           $self->_debug("setting current slot to $slot");
            $state->{'current_slot'} = $slot;
        }
 
@@ -728,7 +741,8 @@ sub info_key_vendor_string {
 
     $self->{'interface'}->inquiry(make_cb(inquiry_cb => sub {
        my ($err, $info) = @_;
-       return $params{'info_cb'}->($err) if $err;
+       return $self->make_error("fatal", $params{'info_cb'},
+               message => "$err") if $err;
 
        my $vendor_string = sprintf "%s %s",
            ($info->{'vendor id'} or "<unknown>"),
@@ -789,14 +803,14 @@ sub get_device { # (overridden by subclasses)
 
     my $device = Amanda::Device->new($device_name);
     if ($device->status != $DEVICE_STATUS_SUCCESS) {
-       return $self->make_error("failed", undef,
-               reason => "device",
+       return Amanda::Changer->make_error("fatal", undef,
+               reason => "unknown",
                message => "opening '$device_name': " . $device->error_or_status());
     }
 
     if (my $err = $self->{'config'}->configure_device($device)) {
-       return $self->make_error("failed", undef,
-               reason => "device",
+       return Amanda::Changer->make_error("fatal", undef,
+               reason => "unknown",
                message => $err);
     }
 
@@ -827,9 +841,13 @@ sub _set_label_unlocked {
 
     $state->{'drives'}->{$drive}->{'label'} = $label;
     if (defined $slot) {
-       delete $state->{'slots'}->{$slot}->{'unkknown_state'};
        $state->{'slots'}->{$slot}->{'state'} = Amanda::Changer::SLOT_FULL;
        $state->{'slots'}->{$slot}->{'device_status'} = "".$dev->status;
+       if ($dev->status != $DEVICE_STATUS_SUCCESS) {
+           $state->{'slots'}->{$slot}->{'device_error'} = $dev->error;
+       } else {
+           $state->{'slots'}->{$slot}->{'device_error'} = undef;
+       }
        my $volume_header = $dev->volume_header;
        if (defined $volume_header) {
            $state->{'slots'}->{$slot}->{'f_type'} = "".$volume_header->{type};
@@ -839,6 +857,11 @@ sub _set_label_unlocked {
        $state->{'slots'}->{$slot}->{'label'} = $label;
     }
     if (defined $barcode) {
+       if (defined $state->{'bc2lb'}->{$barcode} and
+           $state->{'bc2lb'}->{$barcode} ne $label) {
+           my $old_label = $state->{'bc2lb'}->{$barcode};
+           $self->_debug("update barcode '$barcode' to label '$label', old label was '$old_label'");
+       }
        $state->{'bc2lb'}->{$barcode} = $label;
     }
 
@@ -1089,6 +1112,7 @@ sub update_unlocked {
            while (my ($sl, $inf) = each %{$state->{'slots'}}) {
                if ($inf->{'label'} and $inf->{'label'} eq $label) {
                    delete $inf->{'device_status'};
+                   delete $inf->{'device_error'};
                    delete $inf->{'f_type'};
                    delete $inf->{'label'};
                }
@@ -1151,6 +1175,7 @@ sub update_unlocked {
        if (!defined $state->{'slots'}->{$slot}->{'barcode'}) {
            $state->{'slots'}->{$slot}->{'label'} = undef;
            $state->{'slots'}->{$slot}->{'device_status'} = undef;
+           $state->{'slots'}->{$slot}->{'device_error'} = undef;
            $state->{'slots'}->{$slot}->{'f_type'} = undef;
            if (defined $state->{'slots'}->{$slot}->{'loaded_in'}) {
                my $drive = $state->{'slots'}->{$slot}->{'loaded_in'};
@@ -1229,6 +1254,7 @@ sub inventory_unlocked {
        $i->{'slot'} = $slot_name;
        $i->{'state'} = $slot->{'state'};
        $i->{'device_status'} = $slot->{'device_status'};
+       $i->{'device_error'} = $slot->{'device_error'};
        $i->{'f_type'} = $slot->{'f_type'};
        $i->{'label'} = $slot->{'label'};
        $i->{'barcode'} = $slot->{'barcode'}
@@ -1285,10 +1311,14 @@ sub move_unlocked {
                message => "slot $from_slot is empty");
     }
 
-    if (defined $state->{'slots'}->{$from_slot}->{'loaded_in'}) {
-       return $self->make_error("failed", $params{'finished_cb'},
-               reason => "invalid",
-               message => "slot $from_slot is currently loaded");
+    my $in_drive = $state->{'slots'}->{$from_slot}->{'loaded_in'};
+    if (defined $in_drive) {
+       my $info = $state->{'drives'}->{$in_drive};
+       if ($info->{'res_info'} and $self->_res_info_verify($info->{'res_info'})) {
+           return $self->make_error("failed", $params{'finished_cb'},
+                   reason => "invalid",
+                   message => "slot $from_slot is currently loaded and reserved");
+       }
     }
 
     if ($state->{'slots'}->{$to_slot}->{'state'} == Amanda::Changer::SLOT_FULL) {
@@ -1305,17 +1335,49 @@ sub move_unlocked {
        return $params{'finished_cb'}->($err) if $err;
 
        # update metadata
-       $state->{'slots'}->{$to_slot} = { %{ $state->{'slots'}->{$from_slot} } };
-       $state->{'slots'}->{$from_slot}->{'state'} =
-                                               Amanda::Changer::SLOT_EMPTY;
-       $state->{'slots'}->{$from_slot}->{'device_status'} = undef;
-       $state->{'slots'}->{$from_slot}->{'f_type'} = undef;
-       $state->{'slots'}->{$from_slot}->{'label'} = undef;
-       $state->{'slots'}->{$from_slot}->{'barcode'} = undef;
+       if ($from_slot ne $to_slot) {
+           my $f = $state->{'slots'}->{$from_slot};
+           my $t = $state->{'slots'}->{$to_slot};
+
+           $t->{'device_status'} = $f->{'device_status'};
+           $f->{'device_status'} = undef;
+
+           $t->{'state'} = $f->{'state'};
+           $f->{'state'} = Amanda::Changer::SLOT_EMPTY;
+
+           $t->{'f_type'} = $f->{'f_type'};
+           $f->{'f_type'} = undef;
+
+           $t->{'label'} = $f->{'label'};
+           $f->{'label'} = undef;
+
+           $t->{'barcode'} = $f->{'barcode'};
+           $f->{'barcode'} = undef;
+       }
+
+       # properly represent the unload operation, if it was performed
+       if (defined $in_drive) {
+           $state->{'slots'}->{$from_slot}->{'loaded_in'} = undef;
+           $state->{'slots'}->{$to_slot}->{'loaded_in'} = undef;
+
+           $state->{'drives'}->{$in_drive}->{'state'} =
+                                                   Amanda::Changer::SLOT_EMPTY;
+           $state->{'drives'}->{$in_drive}->{'label'} = undef;
+           $state->{'drives'}->{$in_drive}->{'barcode'} = undef;
+           $state->{'drives'}->{$in_drive}->{'orig_slot'} = undef;
+       }
 
        $params{'finished_cb'}->();
     });
-    $self->{'interface'}->transfer($from_slot, $to_slot, $transfer_complete);
+
+    # if the source slot is loaded, then this is just a directed unload operation;
+    # otherwise, it's a transfer.
+    if (defined $in_drive) {
+       Amanda::Debug::debug("move(): unloading drive $in_drive to slot $to_slot");
+       $self->{'interface'}->unload($in_drive, $to_slot, $transfer_complete);
+    } else {
+       $self->{'interface'}->transfer($from_slot, $to_slot, $transfer_complete);
+    }
 }
 
 ##
@@ -1489,6 +1551,7 @@ sub _with_updated_state {
                 $new_slots->{$slot} = {
                     state => Amanda::Changer::SLOT_EMPTY,
                    device_status => undef,
+                   device_error => undef,
                    f_type => undef,
                     label => undef,
                     barcode => undef,
@@ -1505,6 +1568,7 @@ sub _with_updated_state {
                $new_slots->{$slot} = {
                     state => Amanda::Changer::SLOT_FULL,
                    device_status => $state->{'slots'}->{$slot}->{device_status},
+                   device_error => $state->{'slots'}->{$slot}->{device_error},
                    f_type => $state->{'slots'}->{$slot}->{f_type},
                    label => $label,
                    barcode => $info->{'barcode'},
@@ -1521,6 +1585,7 @@ sub _with_updated_state {
                    $new_slots->{$slot} = {
                        state => Amanda::Changer::SLOT_FULL,
                        device_status => undef,
+                       device_error => undef,
                        f_type => undef,
                        label => undef,
                        barcode => undef,
@@ -1616,6 +1681,7 @@ sub _with_updated_state {
                $state->{'slots'}->{$info->{'orig_slot'}} = {
                     state => $info->{'state'},
                     device_status => $old_state->{'device_status'},
+                    device_error => $old_state->{'device_error'},
                     f_type => $old_state->{'f_type'},
                    label => $info->{'label'},
                     barcode => $info->{'barcode'},
@@ -1839,18 +1905,33 @@ sub status {
 
     synchronized($self->{'lock'}, $status_cb, sub {
        my ($status_cb) = @_;
+       my ($counter) = 120;
+
+       my $sys_cb;
+       my $run_mtx = make_cb(run_mtx => sub {
+           my @nobarcode = ('nobarcode') if $self->{'ignore_barcodes'};
+           $self->_run_system_command($sys_cb,
+                   $self->{'mtx'}, "-f", $self->{'device_name'}, @nobarcode,
+                   'status');
+       });
 
-       my $sys_cb = make_cb(sys_cb => sub {
+       $sys_cb = make_cb(sys_cb => sub {
            my ($exitstatus, $output) = @_;
            if ($exitstatus != 0) {
                my $err = $output;
                # if it's a regular SCSI error, just show the sense key
                my ($sensekey) = ($err =~ /mtx: Request Sense: Sense Key=(.*)\n/);
                $err = "SCSI error; Sense Key=$sensekey" if $sensekey;
+               $counter--;
+               if ($sensekey eq "Not Ready" and $counter > 0) {
+                   debug("$output");
+                   return Amanda::MainLoop::call_after(1000, $run_mtx);
+               }
                return $status_cb->("error from mtx: " . $err, {});
            } else {
                my %status;
                for my $line (split '\n', $output) {
+                   debug("mtx: $line");
                    my ($slot, $ie, $slinfo);
 
                    # drives (data transfer elements)
@@ -1895,9 +1976,7 @@ sub status {
            }
 
        });
-       my @nobarcode = ('nobarcode') if $self->{'ignore_barcodes'};
-       $self->_run_system_command($sys_cb,
-           $self->{'mtx'}, "-f", $self->{'device_name'}, @nobarcode, 'status');
+       $run_mtx->();
     });
 }
 
@@ -1964,12 +2043,12 @@ sub _run_system_command {
 
     my ($readfd, $writefd) = POSIX::pipe();
     if (!defined($writefd)) {
-       die("Error creating pipe: $!");
+       confess("Error creating pipe: $!");
     }
 
     my $pid = fork();
     if (!defined($pid) or $pid < 0) {
-        die("Can't fork to run changer script: $!");
+        confess("Can't fork to run changer script: $!");
     }
 
     if (!$pid) {