Blob Blame History Raw
# Copyright (c) 2010-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 94085, or: http://www.zmanda.com

package Amanda::ScanInventory;

=head1 NAME

Amanda::ScanInventory

=head1 SYNOPSIS

This package implements a base class for all scan that use the inventory.
see C<amanda-taperscan(7)>.

=cut

use strict;
use warnings;
use Amanda::Tapelist;
use Carp;
use POSIX ();
use Data::Dumper;
use vars qw( @ISA );
use base qw(Exporter);
our @EXPORT_OK = qw($DEFAULT_CHANGER);

use Amanda::Paths;
use Amanda::Tapelist;
use Amanda::Config qw( :getconf );
use Amanda::Util qw( match_labelstr );
use Amanda::Device qw( :constants );
use Amanda::Debug qw( debug );
use Amanda::Changer;
use Amanda::MainLoop;
use Amanda::Interactivity;

use constant SCAN_ASK      => 1; # call Amanda::Interactivity module
use constant SCAN_POLL     => 2; # wait 'poll_delay' and retry the scan.
use constant SCAN_FAIL     => 3; # abort
use constant SCAN_CONTINUE => 4; # continue to the next step
use constant SCAN_ASK_POLL => 5; # call Amanda::Interactivity module and
				 # poll at the same time.
use constant SCAN_LOAD     => 6; # load a slot
use constant SCAN_DONE     => 7; # successful scan

our $DEFAULT_CHANGER = {};

sub new {
    my $class = shift;
    my %params = @_;
    my $scan_conf = $params{'scan_conf'};
    my $tapelist = $params{'tapelist'};
    my $chg = $params{'changer'};
    my $interactivity = $params{'interactivity'};

    #until we have a config for it.
    $scan_conf = Amanda::ScanInventory::Config->new();
    $chg = Amanda::Changer->new(undef, tapelist => $tapelist) if !defined $chg;

    my $self = {
	initial_chg => $chg,
	chg         => $chg,
	scanning    => 0,
	scan_conf   => $scan_conf,
	tapelist    => $tapelist,
        interactivity => $interactivity,
	seen        => {},
	scan_num    => 0,
    };
    return bless ($self, $class);
}

sub scan {
    my $self = shift;
    my %params = @_;

    die "Can only run one scan at a time" if $self->{'scanning'};
    $self->{'scanning'} = 1;
    $self->{'user_msg_fn'} = $params{'user_msg_fn'} || sub {};

    # refresh the tapelist at every scan
    $self->read_tapelist();

    # count the number of scans we do, so we can only load 'current' on the
    # first scan
    $self->{'scan_num'}++;

    $self->_scan(%params);
}

sub _user_msg {
    my $self = shift;
    my %params = @_;
    $self->{'user_msg_fn'}->(%params);
}

sub _scan {
    my $self = shift;
    my %params = @_;

    my $user_msg_fn = $params{'user_msg_fn'} || \&_user_msg_fn;
    my $action;
    my $action_slot;
    my $res;
    my $label;
    my $inventory;
    my $current;
    my $new_slot;
    my $poll_src;
    my $scan_running = 0;
    my $interactivity_running = 0;
    my $restart_scan = 0;		# if a scan must be restarted
    my $restart_scan_changer = undef;
    my $abort_scan = undef;
    my $last_err = undef; # keep the last meaningful error, the one reported
			  # to the user, most scan end with the notfound error,
			  # it's more interesting to report an error from the
			  # device or ...
    my $slot_scanned;
    my $remove_undef_state = 0;
    my $result_cb = $params{'result_cb'};
    my $new_inventory = 0;
    my $restart_scan_running = 0;	# if restart_scan is running

    my $steps = define_steps
	cb_ref => \$result_cb;

    step init => sub {
	$scan_running = 1;
	$steps->{'should_get_inventory'}->();
    };

    step should_get_inventory => sub {
	$new_inventory = 0;
	if ($res || ($self->{'slots'} && @{$self->{'slots'}} && defined $self->{'slots'}->[0])) {
	    return $steps->{'action'}->();
	}
	return $steps->{'get_inventory'}->();
    };

    step restart_scan => sub {
	$restart_scan = 0;

	return if $restart_scan_running;
	$restart_scan_running = 1;

	# Reload the tapelist at every scan.
	$self->{'tapelist'}->reload(0);

	if ($restart_scan_changer) {
	    $self->{'chg'}->quit() if $self->{'chg'} != $self->{'initial_chg'};
	    $self->{'chg'} = $restart_scan_changer;
	    $restart_scan_changer = undef;
	}
	return $steps->{'get_inventory'}->();
    };

    step get_inventory => sub {
	if ($remove_undef_state and $self->{'chg'}->{'scan-require-update'}) {
	    $self->{'chg'}->update();
	}
	$self->{'chg'}->inventory(inventory_cb => $steps->{'parse_inventory'});
    };

    step parse_inventory => sub {
	(my $err, $inventory) = @_;

	if ($err && $err->notimpl) {
	    #inventory not implemented
	    die("no inventory");
	} elsif ($err and $err->fatal) {
	    #inventory fail
	    return $steps->{'call_result_cb'}->($err, undef);
	}
	return $steps->{'handle_error'}->($err, undef) if $err;

	# throw out the inventory result and move on if the situation has
	# changed while we were waiting
	return $steps->{'abort_scan'}->() if $abort_scan;
	return $steps->{'restart_scan'}->() if $restart_scan;

	# Remove from seen all slot that have state == SLOT_UNKNOWN
	# It is done when a scan is restarted from interactivity object.
	if ($remove_undef_state) {
	    for my $i (0..(scalar(@$inventory)-1)) {
		my $slot = $inventory->[$i]->{slot};
		if (exists($self->{seen}->{$slot}) &&
		    !defined($inventory->[$i]->{state})) {
		    delete $self->{seen}->{$slot};
		}
	    }
	    $remove_undef_state = 0;
	}

	# remove any slots where the state has changed from the list of seen slots
	for my $i (0..(scalar(@$inventory)-1)) {
	    my $sl = $inventory->[$i];
	    my $slot = $sl->{slot};
	    if ($self->{seen}->{$slot} &&
		!defined ($self->{seen}->{$slot}->{'failed'}) &&
		defined($sl->{'state'}) &&
		!($self->{seen}->{$slot}->{'device_status'} == $DEVICE_STATUS_SUCCESS &&
		  ($sl->{'device_status'} & $DEVICE_STATUS_DEVICE_ERROR ||
		   $sl->{'device_status'} & $DEVICE_STATUS_VOLUME_ERROR)) &&
		(($self->{seen}->{$slot}->{'device_status'} != $sl->{'device_status'}) ||
		 (defined $self->{seen}->{$slot}->{'device_status'} &&
		  $self->{seen}->{$slot}->{'device_status'} == $DEVICE_STATUS_SUCCESS &&
		  $self->{seen}->{$slot}->{'f_type'} != $sl->{'f_type'}) ||
		 (defined $self->{seen}->{$slot}->{'device_status'} &&
		  $self->{seen}->{$slot}->{'device_status'} == $DEVICE_STATUS_SUCCESS &&
		  defined $self->{seen}->{$slot}->{'f_type'} &&
		  $self->{seen}->{$slot}->{'f_type'} == $Amanda::Header::F_TAPESTART &&
		  $self->{seen}->{$slot}->{'label'} ne $sl->{'label'}))) {
		delete $self->{seen}->{$slot};
	    }
	}
	$new_inventory = 1;
	$steps->{'action'}->();
    };

    step action => sub {
	$self->{'slot-error-message'} = undef;

	if ($res) {
	    my $dev = $res->{'device'};
	    if ($dev) {
		my $volume_header = $dev->volume_header;
		if ($dev->status == $DEVICE_STATUS_SUCCESS) {
		    my $label = $volume_header->{'name'};
		    if ($self->is_reusable_volume(label => $label, new_label_ok => 1)) {
			    $action = Amanda::ScanInventory::SCAN_DONE;
			    return $steps->{'call_result_cb'}->(undef, $res);
		    } else {
			my $vol_tle = $self->{'tapelist'}->lookup_tapelabel($label);
			if ($vol_tle) {
			    if ($self->volume_is_new_labelled($vol_tle, {label => $label, barcode => $res->{barcode}})) {
				$action = Amanda::ScanInventory::SCAN_DONE;
				return $steps->{'call_result_cb'}->(undef, $res);
			    }
			}
		   }
		} else {
		    if ($self->volume_is_labelable({ device_status => $dev->status,
						     f_type  => $volume_header->{'type'},
						     label   => $label,
						     slot    => $res->{'this_slot'},
						     barcode => $res->{'barcode'},
						     meta    => $res->{'meta'} })) {
			$action = Amanda::ScanInventory::SCAN_DONE;
			return $steps->{'call_result_cb'}->(undef, $res);
		    }
		}
	    }
	}
	delete $self->{'use_sl'};
	if (!$self->{'slots'} || !@{$self->{'slots'}} || !defined $self->{'slots'}->[0]) {
	    $self->{'slots'} = $self->analyze($inventory, $self->{seen});
	}
	if (@{$self->{'slots'}}) {
	    $self->{'use_sl'} = shift @{$self->{'slots'}};
	}
	if ($self->{'use_sl'} && defined $self->{'use_sl'}->{'slot'}) {
	    $action = Amanda::ScanInventory::SCAN_LOAD;
	    $action_slot = $self->{'use_sl'}->{'slot'};
	} elsif ($self->{'scan_conf'}->{'ask'}) {
	    $action = Amanda::ScanInventory::SCAN_ASK_POLL;
	} else {
	    $action = Amanda::ScanInventory::SCAN_FAIL;
	}

	if (defined $res) {
	    $res->release(need_another => 1, finished_cb => $steps->{'released'});
	    $res = undef;
	} else {
	    $steps->{'released'}->();
	}
    };

    step released => sub {
	if ($action == Amanda::ScanInventory::SCAN_LOAD) {
	    $slot_scanned = $action_slot;
	    $self->_user_msg(scan_slot => 1,
			     slot => $slot_scanned);
	    $self->{'slot-error-message'} = $self->{seen}->{$slot_scanned}->{'device_error'};

	    return $self->{'chg'}->load(
			slot => $slot_scanned,
			set_current => $params{'set_current'},
			res_cb => $steps->{'slot_loaded'});
	}

	if (!$new_inventory) {
	    delete $self->{'slots'};
	    return $steps->{'get_inventory'}->();
	}

	my $err;
	if ($last_err) {
	    $err = $last_err;
	} else {
	    $err = Amanda::Changer::Error->new('failed',
				reason => 'notfound',
				message => "No acceptable volumes found");
	}

	if ($action == Amanda::ScanInventory::SCAN_FAIL) {
	    return $steps->{'handle_error'}->($err, undef);
	}
	$scan_running = 0;
	$steps->{'scan_next'}->($action, $err);
    };

    step slot_loaded => sub {
	(my $err, $res) = @_;

	$self->{'slot_loaded_err'} = $err;

	# we don't responsd to abort_scan or restart_scan here, since we
	# have an open reservation that we should deal with.

	# change status of slot in error if that one succeeded.
	if (defined $self->{'slot-error-message'} and
	    $res and defined $res->{'device'} and
	    $self->{'slot-error-message'} ne $res->{'device'}->error) {
	    # mark all unseen slots with that error message as unknown state
	    for my $i (0..(scalar(@$inventory)-1)) {
		my $sl = $inventory->[$i];
		next if $self->{seen}->{$sl->{slot}};
		next if !defined $self->{'slot-error-message'} ||
			!defined $sl->{'device_error'} ||
			$self->{'slot-error-message'} ne $sl->{'device_error'};
		# mark the slot as unknown
		$inventory->[$i] = { slot  => $sl->{'slot'},
				     state => $sl->{'state'}};
	    }
	    if ($self->{'chg'}->can("set_error_to_unknown")) {
		$self->{'chg'}->set_error_to_unknown(
			error_message => $self->{'slot-error-message'},
			set_to_unknown_cb => $steps->{'set_to_unknown_cb'});
	    }
	} else {
	    return $steps->{'set_to_unknown_cb'}->();
	}
    };

    step set_to_unknown_cb => sub {
	my $err = $self->{'slot_loaded_err'};
	$self->{'slot_loaded_err'} = undef;

	my $label;
	if ($res && defined $res->{device} &&
	    $res->{device}->status == $DEVICE_STATUS_SUCCESS) {
	    $label = $res->{device}->volume_label;
	}
	my $relabeled = !defined($label) || !match_labelstr($self->{'labelstr'}, $self->{'autolabel'}, $label, $res->{'barcode'}, $res->{'meta'}, $self->{'chg'}->{'storage'}->{'storage_name'});
	$self->_user_msg(slot_result => 1,
			 slot => $slot_scanned,
			 label => $label,
			 err  => $err,
			 relabeled => $relabeled,
			 res  => $res);
	if ($res) {
	    my $f_type;
	    if (defined $res->{device}->volume_header) {
		$f_type = $res->{device}->volume_header->{type};
	    }

	    # The slot did not contain the volume we wanted, so mark it
	    # as seen and try again.
	    $self->{seen}->{$slot_scanned} = {
			device_status => $res->{device}->status,
			device_error => $res->{device}->error_or_status,
			f_type => $f_type,
			label  => $res->{device}->volume_label
	    };

	    # notify the user
	    if ($res->{device}->status == $DEVICE_STATUS_SUCCESS) {
		$last_err = undef;
	    } else {
		$last_err = Amanda::Changer::Error->new('fatal',
				message => $res->{device}->error_or_status());
	    }
	} else {
	    $self->{seen}->{$slot_scanned} = { failed => 1 };
	    if ($err->volinuse) {
		# Scan semantics for volinuse is different than changer.
		# If a slot with unknown label is loaded then we map
		# volinuse to driveinuse.
		$err->{reason} = "driveinuse";
	    }
	    $last_err = $err if $err->fatal || !$err->notfound;
	}
	return $steps->{'load_released'}->();
    };

    step load_released => sub {
	my ($err) = @_;

	# TODO: handle error
	# throw out the inventory result and move on if the situation has
	# changed while we were loading a volume
	return $steps->{'abort_scan'}->() if $abort_scan;
	return $steps->{'restart_scan'}->() if $restart_scan;

	$new_slot = $current;
	$steps->{'should_get_inventory'}->();
    };

    step handle_error => sub {
	my ($err, $continue_cb) = @_;

	my $scan_method = undef;
	$scan_running = 0;
	my $message;

	$poll_src->remove() if defined $poll_src;
	$poll_src = undef;

	# prefer to use scan method for $last_err, if present
	if ($last_err && $err->failed && $err->notfound) {
	    $message = "$last_err";
	
	    if ($last_err->isa("Amanda::Changer::Error")) {
		if ($last_err->fatal) {
		    $scan_method = $self->{'scan_conf'}->{'fatal'};
		} else {
		    $scan_method = $self->{'scan_conf'}->{$last_err->{'reason'}};
		}
	    } elsif ($continue_cb) {
		$scan_method = SCAN_CONTINUE;
	    }
	}

	#use scan method for $err
	if (!defined $scan_method) {
	    if ($err) {
		$message = "$err" if !defined $message;
		if ($err->fatal) {
		    $scan_method = $self->{'scan_conf'}->{'fatal'};
		} else {
		    $scan_method = $self->{'scan_conf'}->{$err->{'reason'}};
		}
	    } else {
		die("error not defined");
		$scan_method = SCAN_ASK_POLL;
	    }
	}

	## implement the desired scan method

	if ($scan_method == SCAN_CONTINUE && !defined $continue_cb) {
	    $scan_method = $self->{'scan_conf'}->{'notfound'};
	    if ($scan_method == SCAN_CONTINUE) {
		$scan_method = SCAN_FAIL;
	    }
	}
	$steps->{'scan_next'}->($scan_method, $err, $continue_cb);
    };

    step scan_next => sub {
	my ($scan_method, $err, $continue_cb) = @_;
	$restart_scan_running = 0;

	if ($scan_method == SCAN_ASK && !defined $self->{'interactivity'}) {
	    $scan_method = SCAN_FAIL;
	}

	if ($scan_method == SCAN_ASK_POLL && !defined $self->{'interactivity'}) {
	    $scan_method = SCAN_FAIL;
	}

	if ($scan_method == SCAN_ASK) {
	    return $steps->{'scan_interactivity'}->("$err");
	} elsif ($scan_method == SCAN_POLL) {
	    $poll_src = Amanda::MainLoop::call_after(
				$self->{'scan_conf'}->{'poll_delay'},
				$steps->{'after_poll'});
	    return;
	} elsif ($scan_method == SCAN_ASK_POLL) {
	    $steps->{'scan_interactivity'}->("$err\n");
	    $poll_src = Amanda::MainLoop::call_after(
				$self->{'scan_conf'}->{'poll_delay'},
				$steps->{'after_poll'});
	    return;
	} elsif ($scan_method == SCAN_FAIL) {
	    return $steps->{'call_result_cb'}->($err, undef);
	} elsif ($scan_method == SCAN_CONTINUE) {
	    return $continue_cb->($err, undef);
	} else {
	    die("Invalid SCAN_* value:$err:$err->{'reason'}:$scan_method");
	}
    };

    step after_poll => sub {
	if ($poll_src) {
	    $poll_src->remove();
	    $poll_src = undef;
	    if ($self->{'chg'}->{'scan-require-update'}) {
		$remove_undef_state = 1;
	    }
	    return $steps->{'restart_scan'}->();
	}
    };

    step scan_interactivity => sub {
	my ($err_message) = @_;
	if (!$interactivity_running) {
	    $interactivity_running = 1;
	    my $message = "$err_message\n";
	    if ($self->{'most_prefered_label'}) {
		$message .= "Insert volume labeled '$self->{'most_prefered_label'}'";
	    } else {
		$message .= "Insert a new volume";
	    }
	    $message .= " in changer and type <enter>\nor type \"^D\" to abort\n";
	    $self->{'interactivity'}->user_request(
				message     => $message,
				label       => $self->{'most_prefered_label'},
				new_volume  => !$self->{'most_prefered_label'},
				err         => "$err_message",
				chg_name    => $self->{'chg'}->{'chg_name'},
				request_cb  => $steps->{'scan_interactivity_cb'});
	}
	return;
    };

    step scan_interactivity_cb => sub {
	my ($err, $message) = @_;
	$interactivity_running = 0;
	$poll_src->remove() if defined $poll_src;
	$poll_src = undef;
	$last_err = undef;

	if ($err) {
	    if ($scan_running) {
		$abort_scan = $err;
		return;
	    } else {
		return $steps->{'call_result_cb'}->($err, undef);
	    }
	}

	# remove leading and trailing space
	$message =~ s/^ +//g;
	$message =~ s/ +$//g;
	if ($message ne '') {
	    # use a new changer
	    my $new_chg;
	    if (ref($message) eq 'HASH' and $message == $DEFAULT_CHANGER) {
		$message = undef;
	    }
	    $new_chg = Amanda::Changer->new($message,
					    tapelist => $self->{'tapelist'});
	    if ($new_chg->isa("Amanda::Changer::Error")) {
		return $steps->{'scan_interactivity'}->("$new_chg");
	    }
	    $restart_scan_changer = $new_chg;
	    $self->{seen} = {};
	} else {
	    $remove_undef_state = 1;
	}

	if ($scan_running) {
	    $restart_scan = 1;
	    return;
	} else {
	    return $steps->{'restart_scan'}->();
	}
    };

    step abort_scan => sub {
	if (defined $res) {
	    $res->released(finished_cb => $steps->{'abort_scan_released'});
	} else {
	    $steps->{'abort_scan_released'}->();
	}
    };

    step abort_scan_released => sub {
	$steps->{'call_result_cb'}->($abort_scan, undef);
    };

    step call_result_cb => sub {
	(my $err, $res) = @_;

	# TODO: what happens if the search was aborted or
	# restarted in the interim?

	$abort_scan = undef;
	$poll_src->remove() if defined $poll_src;
	$poll_src = undef;
	$interactivity_running = 0;
	$self->{'interactivity'}->abort() if defined $self->{'interactivity'};
	$self->{'chg'}->quit() if $self->{'chg'} != $self->{'initial_chg'} and
				  !$res;
	if ($err) {
	    $self->{'scanning'} = 0;
	    return $result_cb->($err, $res);
	}
	$label = $res->{'device'}->volume_label;
	if (!defined($label) ||
	    !match_labelstr($self->{'labelstr'}, $self->{'autolabel'}, $label, $res->{'barcode'}, $res->{'meta'}, $self->{'chg'}->{'storage'}->{'storage_name'})) {
	    $res->get_meta_label(finished_cb => $steps->{'got_meta_label'});
	    return;
	}
	$self->{'scanning'} = 0;
	return $result_cb->(undef, $res, $label, $ACCESS_WRITE);
    };

    step got_meta_label => sub {
	my ($err, $meta) = @_;
	if (defined $err) {
	    return $result_cb->($err, $res);
	}
	($label, my $make_err, my $not_fatal) = $res->make_new_tape_label(meta => $meta);
	if (!defined $label) {
	    if ($not_fatal) {
		# must be logged
		$self->_user_msg(slot_result => 1,
				 slot => $slot_scanned,
				 err => "Can't label slot $slot_scanned: $make_err");
		my $res1 = $res;
		$res = undef;
		return $res1->release(need_another => 1, finished_cb => $steps->{'get_inventory'});
	    } else {
		# make this fatal, rather than silently skipping new tapes
		$self->{'scanning'} = 0;
		return $result_cb->($make_err, $res);
	    }
	}
	$self->{'scanning'} = 0;
	return $result_cb->(undef, $res, $label, $ACCESS_WRITE, 1);
    };
}

sub volume_is_new_labelled {
    my $self = shift;
    my $tle = shift;
    my $sl = shift;

    if ($tle->{'pool'} && $tle->{'pool'} ne $self->{'tapepool'}) {
	return 0;
    }
    if (!$tle->{'pool'} &&
	     !match_labelstr($self->{'labelstr'}, $self->{'autolabel'}, $sl->{'label'}, $sl->{'barcode'}, $sl->{'meta'}, $self->{'chg'}->{'storage'}->{'storage_name'})) {
	return 0;
    }
    if ($tle->{'datestamp'} ne '0') {
	return 0;
    }
    if (!$tle->{'reuse'}) {
	return 0;
    }
    return 1;
}

sub volume_is_labelable {
    my $self = shift;
    my $sl = shift;
    my $dev_status  = $sl->{'device_status'};
    my $f_type = $sl->{'f_type'};
    my $label = $sl->{'label'};
    my $slot = $sl->{'slot'};
    my $barcode = $sl->{'barcode'};
    my $meta = $sl->{'meta'};
    my $chg = $self->{'chg'};
    my $autolabel = $chg->{'autolabel'};

    if (!defined $dev_status) {
	return 0;
    } elsif ($dev_status & $DEVICE_STATUS_VOLUME_UNLABELED and
	     defined $f_type and
	     $f_type == $Amanda::Header::F_EMPTY) {
	if (!$autolabel->{'empty'}) {
#	    $self->_user_msg(slot_result  => 1,
#			     empty        => 1,
#			     slot         => $slot);
	    return 0;
	}
    } elsif ($dev_status & $DEVICE_STATUS_VOLUME_UNLABELED and
	     defined $f_type and
	     $f_type == $Amanda::Header::F_WEIRD) {
	if (!$autolabel->{'non_amanda'}) {
#	    $self->_user_msg(slot_result  => 1,
#			     non_amanda   => 1,
#			     slot         => $slot);
	    return 0;
	}
    } elsif ($dev_status & $DEVICE_STATUS_VOLUME_ERROR) {
	if (!$autolabel->{'volume_error'}) {
#	    $self->_user_msg(slot_result  => 1,
#			     volume_error => 1,
#			     err          => $sl->{'device_error'},
#			     slot         => $slot);
	    return 0;
	}
    } elsif ($dev_status != $DEVICE_STATUS_SUCCESS) {
#	    $self->_user_msg(slot_result  => 1,
#			     not_success  => 1,
#			     err          => $sl->{'device_error'},
#			     slot         => $slot);
	return 0;
    } elsif ($dev_status == $DEVICE_STATUS_SUCCESS and
	     $f_type != $Amanda::Header::F_TAPESTART) {
	return 0;
    } elsif ($dev_status == $DEVICE_STATUS_SUCCESS and
	     $f_type == $Amanda::Header::F_TAPESTART) {
	if (!match_labelstr($self->{'labelstr'}, $autolabel, $label,
			    $barcode, $meta, $self->{'chg'}->{'storage'}->{'storage_name'})) {
	    if (!$autolabel->{'other_config'}) {
#	        $self->_user_msg(slot_result  => 1,
#			         label        => $label,
#			         labelstr     => $self->{'labelstr'}->{'template'},
#			         does_not_match_labelstr => 1,
#			         slot         => $slot);
		return 0;
	    }
	} else {
	    my $vol_tle = $self->{'tapelist'}->lookup_tapelabel($label);
	    if (!$vol_tle) {
#		$self->_user_msg(slot_result     => 1,
#				 label           => $label,
#				 not_in_tapelist => 1,
#				 slot            => $slot);
		return 0;
	    }
	}
    }

    return 1;
}

package Amanda::ScanInventory::Config;

sub new {
    my $class = shift;
    my ($cc) = @_;

    my $self = bless {}, $class;

    $self->{'poll_delay'} = 10000; #10 seconds

    $self->{'fatal'} = Amanda::ScanInventory::SCAN_CONTINUE;
    $self->{'driveinuse'} = Amanda::ScanInventory::SCAN_ASK_POLL;
    $self->{'volinuse'} = Amanda::ScanInventory::SCAN_ASK_POLL;
    $self->{'notfound'} = Amanda::ScanInventory::SCAN_ASK_POLL;
    $self->{'unknown'} = Amanda::ScanInventory::SCAN_FAIL;
    $self->{'invalid'} = Amanda::ScanInventory::SCAN_CONTINUE;

    $self->{'scan'} = 1;
    $self->{'ask'} = 1;
    $self->{'new_labeled'} = 'order';
    $self->{'new_volume'} = 'order';

    return $self;
}

1;