1 # Copyright (c) 2009, 2010 Zmanda, Inc. All Rights Reserved.
3 # This library is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU Lesser General Public License version 2.1 as
5 # published by the Free Software Foundation.
7 # This library is distributed in the hope that it will be useful, but
8 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
10 # License for more details.
12 # You should have received a copy of the GNU Lesser General Public License
13 # along with this library; if not, write to the Free Software Foundation,
14 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
16 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
19 package Amanda::Taper::Scan::traditional;
23 Amanda::Taper::Scan::traditional
27 This package implements the "traditional" taperscan algorithm. See
28 C<amanda-taperscan(7)>.
34 use base qw( Amanda::Taper::Scan );
36 use Amanda::Config qw( :getconf );
37 use Amanda::Device qw( :constants );
39 use Amanda::Debug qw( :logging );
46 # parent will set all of the $params{..} keys for us
61 die "Can only run one scan at a time" if $self->{'scanning'};
62 $self->{'scanning'} = 1;
63 $self->{'user_msg_fn'} = $params{'user_msg_fn'} || sub {};
65 # refresh the tapelist at every scan
66 $self->read_tapelist();
68 # count the number of scans we do, so we can only load 'current' on the
70 $self->{'scan_num'}++;
72 $self->stage_1($params{'result_cb'});
79 $self->{'user_msg_fn'}->(%params);
86 my @result = ($params{'error'}, $params{'res'}, $params{'label'},
87 $params{'mode'}, $params{'is_new'});
89 if ($params{'error'}) {
90 debug("Amanda::Taper::Scan::traditional result: error=$params{'error'}");
92 # if we already had a reservation when the error occurred, then we'll need
93 # to release that reservation before signalling the error
95 my $finished_cb = make_cb(finished_cb => sub {
97 # if there was an error releasing, log it and ignore it
98 Amanda::Debug::warn("while releasing reservation: $err") if $err;
100 $self->{'scanning'} = 0;
101 $params{'result_cb'}->(@result);
103 return $params{'res'}->release(finished_cb => $finished_cb);
105 } elsif ($params{'res'}) {
106 my $devname = $params{'res'}->{'device'}->device_name;
107 my $slot = $params{'res'}->{'this_slot'};
108 debug("Amanda::Taper::Scan::traditional result: '$params{label}' " .
109 "on $devname slot $slot, mode $params{mode}");
111 debug("Amanda::Taper::Scan::traditional result: scan failed");
113 # we may not ever have looked for this, the oldest reusable volume, if
114 # the changer is not fast-searchable. But we'll tell the user about it
116 my $oldest_reusable = $self->oldest_reusable_volume(new_label_ok => 0);
117 $self->_user_msg(scan_failed => 1,
118 expected_label => $oldest_reusable,
120 @result = ("No acceptable volumes found");
123 $self->{'scanning'} = 0;
124 $params{'result_cb'}->(@result);
128 # stage 1: search for the oldest reusable volume
132 my ($result_cb) = @_;
135 my $steps = define_steps
136 cb_ref => \$result_cb;
139 debug("Amanda::Taper::Scan::traditional stage 1: search for oldest reusable volume");
140 $oldest_reusable = $self->oldest_reusable_volume(
141 new_label_ok => 0, # stage 1 never selects new volumes
144 if (!defined $oldest_reusable) {
145 debug("Amanda::Taper::Scan::traditional no oldest reusable volume");
146 return $self->stage_2($result_cb);
148 debug("Amanda::Taper::Scan::traditional oldest reusable volume is '$oldest_reusable'");
150 # try loading that oldest volume, but only if the changer is fast-search capable
151 $steps->{'get_info'}->();
154 step get_info => sub {
155 $self->{'changer'}->info(
156 info => [ "fast_search" ],
157 info_cb => $steps->{'got_info'},
161 step got_info => sub {
162 my ($error, %results) = @_;
164 return $self->scan_result(error => $error, result_cb => $result_cb);
167 if ($results{'fast_search'}) {
168 debug("Amanda::Taper::Scan::traditional stage 1: searching oldest reusable " .
169 "volume '$oldest_reusable'");
170 $self->_user_msg(search_label => 1,
171 label => $oldest_reusable);
173 $steps->{'do_load'}->();
175 # no fast search, so skip to stage 2
176 debug("Amanda::Taper::Scan::traditional changer is not fast-searchable; skipping to stage 2");
177 $self->stage_2($result_cb);
181 step do_load => sub {
182 $self->{'changer'}->load(
183 label => $oldest_reusable,
185 res_cb => $steps->{'load_done'});
188 step load_done => sub {
189 my ($err, $res) = @_;
191 $self->_user_msg(search_result => 1, res => $res, err => $err);
193 if ($err->failed and $err->notfound) {
194 debug("Amanda::Taper::Scan::traditional oldest reusable volume not found");
195 return $self->stage_2($result_cb);
197 return $self->scan_result(error => $err,
198 res => $res, result_cb => $result_cb);
202 $self->{'seen'}->{$res->{'this_slot'}} = 1;
204 my $status = $res->{'device'}->status;
205 if ($status != $DEVICE_STATUS_SUCCESS) {
206 warning "Error reading label after searching for '$oldest_reusable'";
207 return $self->release_and_stage_2($res, $result_cb);
210 # go on to stage 2 if we didn't get the expected volume
211 my $label = $res->{'device'}->volume_label;
212 my $labelstr = $self->{'labelstr'};
213 if ($label !~ /$labelstr/) {
214 warning "Searched for label '$oldest_reusable' but found a volume labeled '$label'";
215 return $self->release_and_stage_2($res, $result_cb);
218 # great! -- volume found
219 return $self->scan_result(res => $res, label => $oldest_reusable,
220 mode => $ACCESS_WRITE, is_new => 0, result_cb => $result_cb);
226 my ($res, $result_cb) = @_;
228 my $slot = $res->{'this_slot'};
229 my $dev = $res->{'device'};
230 my $status = $dev->status;
231 my $labelstr = $res->{'chg'}->{'labelstr'};
233 my $autolabel = $res->{'chg'}->{'autolabel'};
235 if ($status == $DEVICE_STATUS_SUCCESS) {
236 $label = $dev->volume_label;
238 if ($label !~ /$labelstr/) {
239 if (!$autolabel->{'other_config'}) {
240 $self->_user_msg(slot_result => 1,
241 does_not_match_labelstr => 1,
242 labelstr => $labelstr,
249 # verify that the label is in the tapelist
250 my $tle = $self->{'tapelist'}->lookup_tapelabel($label);
252 $self->_user_msg(slot_result => 1,
253 not_in_tapelist => 1,
260 # see if it's reusable
261 if (!$self->is_reusable_volume(label => $label, new_label_ok => 1)) {
262 $self->_user_msg(slot_result => 1,
269 $self->_user_msg(slot_result => 1,
273 $self->scan_result(res => $res, label => $label,
274 mode => $ACCESS_WRITE, is_new => 0, result_cb => $result_cb);
279 if (!defined $autolabel->{'template'} ||
280 $autolabel->{'template'} eq "") {
281 $self->_user_msg(slot_result => 1,
288 $self->_user_msg(slot_result => 1, slot => $slot, res => $res);
290 if ($status & $DEVICE_STATUS_VOLUME_UNLABELED and
291 $dev->volume_header and
292 $dev->volume_header->{'type'} == $Amanda::Header::F_EMPTY) {
293 if (!$autolabel->{'empty'}) {
294 $self->_user_msg(slot_result => 1,
300 } elsif ($status & $DEVICE_STATUS_VOLUME_UNLABELED and
301 $dev->volume_header and
302 $dev->volume_header->{'type'} == $Amanda::Header::F_WEIRD) {
303 if (!$autolabel->{'non_amanda'}) {
304 $self->_user_msg(slot_result => 1,
310 } elsif ($status & $DEVICE_STATUS_VOLUME_ERROR) {
311 if (!$autolabel->{'volume_error'}) {
312 $self->_user_msg(slot_result => 1,
314 err => $dev->error_or_status(),
319 } elsif ($status != $DEVICE_STATUS_SUCCESS) {
320 $self->_user_msg(slot_result => 1,
322 err => $dev->error_or_status(),
328 ($label, my $err) = $res->make_new_tape_label();
329 if (!defined $label) {
330 # make this fatal, rather than silently skipping new tapes
331 $self->scan_result(error => $err, res => $res, result_cb => $result_cb);
335 $self->scan_result(res => $res, label => $label, mode => $ACCESS_WRITE,
336 is_new => 1, result_cb => $result_cb);
341 # stage 2: scan for any usable volume
343 sub release_and_stage_2 {
345 my ($res, $result_cb) = @_;
347 $res->release(finished_cb => sub {
350 $self->scan_result(error => $error, result_cb => $result_cb);
352 $self->stage_2($result_cb);
359 my ($result_cb) = @_;
362 my $load_current = ($self->{'scan_num'} == 1);
363 my $steps = define_steps
364 cb_ref => \$result_cb;
369 debug("Amanda::Taper::Scan::traditional stage 2: scan for any reusable volume");
371 # bail on an error releasing a reservation
373 return $self->scan_result(error => $err, result_cb => $result_cb);
376 # load the current or next slot
379 # load 'current' the first time through
381 relative_slot => 'current',
385 relative_slot => 'next',
386 (defined $last_slot)? (slot => $last_slot) : (),
390 $self->{'changer'}->load(
393 res_cb => $steps->{'loaded'},
394 except_slots => $self->{'seen'},
400 my ($err, $res) = @_;
401 my $loaded_current = $load_current;
402 $load_current = 0; # don't load current a second time
404 $self->_user_msg(search_result => 1, res => $res, err => $err);
405 # bail out immediately if the scan is complete
406 if ($err and $err->failed and $err->notfound) {
407 # no error, no reservation -> end of the scan
408 return $self->scan_result(result_cb => $result_cb);
411 # tell user_msg which slot we're looking at..
413 $self->_user_msg(scan_slot => 1, slot => $res->{'this_slot'});
414 } elsif (defined $err->{'slot'}) {
415 $self->_user_msg(scan_slot => 1, slot => $err->{'slot'});
417 $self->_user_msg(scan_slot => 1, slot => "?");
420 # and then tell it the result if already known (error) or try
421 # loading the volume.
423 my $ignore_error = 0;
424 # there are two "acceptable" errors: if the slot exists but the volume
426 $ignore_error = 1 if ($err->volinuse && $err->{slot});
427 # or if we loaded the 'current' slot and it was invalid (this happens if
428 # the user changes 'use-slots', for example
429 $ignore_error = 1 if ($loaded_current && $err->invalid);
430 $ignore_error = 1 if ($err->empty);
433 $self->_user_msg(slot_result => 1, err => $err);
434 if ($err->{'slot'}) {
435 $last_slot = $err->{slot};
436 $self->{'seen'}->{$last_slot} = 1;
438 return $steps->{'load'}->(undef);
440 # if we have a fatal error or something other than "notfound"
441 # or "volinuse", bail out.
442 $self->_user_msg(slot_result => 1, err => $err);
443 return $self->scan_result(error => $err, res => $res,
444 result_cb => $result_cb);
448 $self->{'seen'}->{$res->{'this_slot'}} = 1;
450 # we're done if try_volume calls result_cb (with success or an error)
451 return if ($self->try_volume($res, $result_cb));
453 # no luck -- release this reservation and get the next
454 $last_slot = $res->{'this_slot'};
456 $res->release(finished_cb => $steps->{'load'});