1 # Copyright (c) 2009-2012 Zmanda, Inc. All Rights Reserved.
3 # This library is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU Lesser General Public
5 #* License as published by the Free Software Foundation; either
6 # version 2.1 of the License, or (at your option) any later version.
8 # This library is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
11 # License for more details.
13 # You should have received a copy of the GNU Lesser General Public License
14 # along with this library; if not, write to the Free Software Foundation,
15 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
17 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20 package Amanda::Taper::Scan::traditional;
24 Amanda::Taper::Scan::traditional
28 This package implements the "traditional" taperscan algorithm. See
29 C<amanda-taperscan(7)>.
35 use base qw( Amanda::Taper::Scan );
37 use Amanda::Config qw( :getconf );
38 use Amanda::Device qw( :constants );
40 use Amanda::Debug qw( :logging );
47 # parent will set all of the $params{..} keys for us
62 die "Can only run one scan at a time" if $self->{'scanning'};
63 $self->{'scanning'} = 1;
64 $self->{'user_msg_fn'} = $params{'user_msg_fn'} || sub {};
66 # refresh the tapelist at every scan
67 $self->read_tapelist();
69 # count the number of scans we do, so we can only load 'current' on the
71 $self->{'scan_num'}++;
73 $self->stage_1($params{'result_cb'});
80 $self->{'user_msg_fn'}->(%params);
87 my @result = ($params{'error'}, $params{'res'}, $params{'label'},
88 $params{'mode'}, $params{'is_new'});
90 if ($params{'error'}) {
91 debug("Amanda::Taper::Scan::traditional result: error=$params{'error'}");
93 # if we already had a reservation when the error occurred, then we'll need
94 # to release that reservation before signalling the error
96 my $finished_cb = make_cb(finished_cb => sub {
98 # if there was an error releasing, log it and ignore it
99 Amanda::Debug::warn("while releasing reservation: $err") if $err;
101 $self->{'scanning'} = 0;
102 $params{'result_cb'}->(@result);
104 return $params{'res'}->release(finished_cb => $finished_cb);
106 } elsif ($params{'res'}) {
107 my $devname = $params{'res'}->{'device'}->device_name;
108 my $slot = $params{'res'}->{'this_slot'};
109 debug("Amanda::Taper::Scan::traditional result: '$params{label}' " .
110 "on $devname slot $slot, mode $params{mode}");
112 debug("Amanda::Taper::Scan::traditional result: scan failed");
114 # we may not ever have looked for this, the oldest reusable volume, if
115 # the changer is not fast-searchable. But we'll tell the user about it
117 my $oldest_reusable = $self->oldest_reusable_volume(new_label_ok => 0);
118 $self->_user_msg(scan_failed => 1,
119 expected_label => $oldest_reusable,
121 @result = ("No acceptable volumes found");
124 $self->{'scanning'} = 0;
125 $params{'result_cb'}->(@result);
129 # stage 1: search for the oldest reusable volume
133 my ($result_cb) = @_;
136 my $steps = define_steps
137 cb_ref => \$result_cb;
140 debug("Amanda::Taper::Scan::traditional stage 1: search for oldest reusable volume");
141 $oldest_reusable = $self->oldest_reusable_volume(
142 new_label_ok => 0, # stage 1 never selects new volumes
145 if (!defined $oldest_reusable) {
146 debug("Amanda::Taper::Scan::traditional no oldest reusable volume");
147 return $self->stage_2($result_cb);
149 debug("Amanda::Taper::Scan::traditional oldest reusable volume is '$oldest_reusable'");
151 # try loading that oldest volume, but only if the changer is fast-search capable
152 $steps->{'get_info'}->();
155 step get_info => sub {
156 $self->{'changer'}->info(
157 info => [ "fast_search" ],
158 info_cb => $steps->{'got_info'},
162 step got_info => sub {
163 my ($error, %results) = @_;
165 return $self->scan_result(error => $error, result_cb => $result_cb);
168 if ($results{'fast_search'}) {
169 debug("Amanda::Taper::Scan::traditional stage 1: searching oldest reusable " .
170 "volume '$oldest_reusable'");
171 $self->_user_msg(search_label => 1,
172 label => $oldest_reusable);
174 $steps->{'do_load'}->();
176 # no fast search, so skip to stage 2
177 debug("Amanda::Taper::Scan::traditional changer is not fast-searchable; skipping to stage 2");
178 $self->stage_2($result_cb);
182 step do_load => sub {
183 $self->{'changer'}->load(
184 label => $oldest_reusable,
186 res_cb => $steps->{'load_done'});
189 step load_done => sub {
190 my ($err, $res) = @_;
192 $self->_user_msg(search_result => 1, res => $res, err => $err);
194 if ($err->failed and $err->notfound) {
195 debug("Amanda::Taper::Scan::traditional oldest reusable volume not found");
196 return $self->stage_2($result_cb);
198 return $self->scan_result(error => $err,
199 res => $res, result_cb => $result_cb);
203 $self->{'seen'}->{$res->{'this_slot'}} = 1;
205 my $status = $res->{'device'}->status;
206 if ($status != $DEVICE_STATUS_SUCCESS) {
207 warning "Error reading label after searching for '$oldest_reusable'";
208 return $self->release_and_stage_2($res, $result_cb);
211 # go on to stage 2 if we didn't get the expected volume
212 my $label = $res->{'device'}->volume_label;
213 my $labelstr = $self->{'labelstr'};
214 if ($label !~ /$labelstr/) {
215 warning "Searched for label '$oldest_reusable' but found a volume labeled '$label'";
216 return $self->release_and_stage_2($res, $result_cb);
219 # great! -- volume found
220 return $self->scan_result(res => $res, label => $oldest_reusable,
221 mode => $ACCESS_WRITE, is_new => 0, result_cb => $result_cb);
226 # stage 2: scan for any usable volume
228 sub release_and_stage_2 {
230 my ($res, $result_cb) = @_;
232 $res->release(finished_cb => sub {
235 $self->scan_result(error => $error, result_cb => $result_cb);
237 $self->stage_2($result_cb);
244 my ($result_cb) = @_;
247 my $load_current = ($self->{'scan_num'} == 1);
248 my $steps = define_steps
249 cb_ref => \$result_cb;
255 debug("Amanda::Taper::Scan::traditional stage 2: scan for any reusable volume");
257 # bail on an error releasing a reservation
259 return $self->scan_result(error => $err, result_cb => $result_cb);
262 # load the current or next slot
265 # load 'current' the first time through
267 relative_slot => 'current',
271 relative_slot => 'next',
272 (defined $last_slot)? (slot => $last_slot) : (),
276 $self->{'changer'}->load(
279 res_cb => $steps->{'loaded'},
280 except_slots => $self->{'seen'},
286 (my $err, $res) = @_;
287 my $loaded_current = $load_current;
288 $load_current = 0; # don't load current a second time
290 # bail out immediately if the scan is complete
291 if ($err and $err->failed and $err->notfound) {
292 $self->_user_msg(search_result => 1, res => $res, err => $err);
293 # no error, no reservation -> end of the scan
294 return $self->scan_result(result_cb => $result_cb);
297 # tell user_msg which slot we're looking at..
299 $self->_user_msg(scan_slot => 1, slot => $res->{'this_slot'});
300 } elsif (defined $err->{'slot'}) {
301 $self->_user_msg(scan_slot => 1, slot => $err->{'slot'});
303 $self->_user_msg(scan_slot => 1, slot => "?");
306 # and then tell it the result if already known (error) or try
307 # loading the volume.
309 my $ignore_error = 0;
310 # there are two "acceptable" errors: if the slot exists but the volume
312 $ignore_error = 1 if ($err->volinuse && $err->{slot});
313 # or if we loaded the 'current' slot and it was invalid (this happens if
314 # the user changes 'use-slots', for example
315 $ignore_error = 1 if ($loaded_current && $err->invalid);
316 $ignore_error = 1 if ($err->empty);
319 $self->_user_msg(slot_result => 1, err => $err);
320 if ($err->{'slot'}) {
321 $last_slot = $err->{slot};
322 $self->{'seen'}->{$last_slot} = 1;
324 return $steps->{'load'}->(undef);
326 # if we have a fatal error or something other than "notfound"
327 # or "volinuse", bail out.
328 $self->_user_msg(slot_result => 1, err => $err);
329 return $self->scan_result(error => $err, res => $res,
330 result_cb => $result_cb);
334 $self->{'seen'}->{$res->{'this_slot'}} = 1;
336 $steps->{'try_volume'}->();
339 step try_volume => sub {
340 my $slot = $res->{'this_slot'};
341 my $dev = $res->{'device'};
342 my $status = $dev->status;
343 my $labelstr = $res->{'chg'}->{'labelstr'};
345 my $autolabel = $res->{'chg'}->{'autolabel'};
347 if ($status == $DEVICE_STATUS_SUCCESS) {
348 $label = $dev->volume_label;
350 if ($label !~ /$labelstr/) {
351 if (!$autolabel->{'other_config'}) {
352 $self->_user_msg(slot_result => 1,
353 does_not_match_labelstr => 1,
354 labelstr => $labelstr,
358 return $steps->{'try_continue'}->();
361 # verify that the label is in the tapelist
362 my $tle = $self->{'tapelist'}->lookup_tapelabel($label);
364 $self->_user_msg(slot_result => 1,
365 not_in_tapelist => 1,
369 return $steps->{'try_continue'}->();
372 # see if it's reusable
373 if (!$self->is_reusable_volume(label => $label, new_label_ok => 1)) {
374 $self->_user_msg(slot_result => 1,
379 return $steps->{'try_continue'}->();
381 $self->_user_msg(slot_result => 1,
385 $self->scan_result(res => $res, label => $label,
386 mode => $ACCESS_WRITE, is_new => 0,
387 result_cb => $result_cb);
392 if (!defined $autolabel->{'template'} ||
393 $autolabel->{'template'} eq "") {
394 if ($status & $DEVICE_STATUS_VOLUME_UNLABELED and
395 $dev->volume_header and
396 $dev->volume_header->{'type'} == $Amanda::Header::F_EMPTY) {
397 $self->_user_msg(slot_result => 1,
402 } elsif ($status & $DEVICE_STATUS_VOLUME_UNLABELED and
403 $dev->volume_header and
404 $dev->volume_header->{'type'} == $Amanda::Header::F_WEIRD) {
405 $self->_user_msg(slot_result => 1,
410 } elsif ($status & $DEVICE_STATUS_VOLUME_ERROR) {
411 $self->_user_msg(slot_result => 1,
414 err => $dev->error_or_status(),
417 } elsif ($status != $DEVICE_STATUS_SUCCESS) {
418 $self->_user_msg(slot_result => 1,
421 err => $dev->error_or_status(),
425 $self->_user_msg(slot_result => 1,
430 return $steps->{'try_continue'}->();
433 if ($status & $DEVICE_STATUS_VOLUME_UNLABELED and
434 $dev->volume_header and
435 $dev->volume_header->{'type'} == $Amanda::Header::F_EMPTY) {
436 if (!$autolabel->{'empty'}) {
437 $self->_user_msg(slot_result => 1,
441 return $steps->{'try_continue'}->();
443 } elsif ($status & $DEVICE_STATUS_VOLUME_UNLABELED and
444 $dev->volume_header and
445 $dev->volume_header->{'type'} == $Amanda::Header::F_WEIRD) {
446 if (!$autolabel->{'non_amanda'}) {
447 $self->_user_msg(slot_result => 1,
451 return $steps->{'try_continue'}->();
453 } elsif ($status & $DEVICE_STATUS_VOLUME_ERROR) {
454 if (!$autolabel->{'volume_error'}) {
455 $self->_user_msg(slot_result => 1,
457 err => $dev->error_or_status(),
460 return $steps->{'try_continue'}->();
462 } elsif ($status != $DEVICE_STATUS_SUCCESS) {
463 $self->_user_msg(slot_result => 1,
465 err => $dev->error_or_status(),
468 return $steps->{'try_continue'}->();
471 $self->_user_msg(slot_result => 1, slot => $slot, res => $res);
472 $res->get_meta_label(finished_cb => $steps->{'got_meta_label'});
476 step got_meta_label => sub {
477 my ($err, $meta) = @_;
480 $self->scan_result(error => $err, res => $res,
481 result_cb => $result_cb);
485 ($meta, $err) = $res->make_new_meta_label() if !defined $meta;
487 $self->scan_result(error => $err, res => $res,
488 result_cb => $result_cb);
492 (my $label, $err) = $res->make_new_tape_label(meta => $meta);
495 if (!defined $label) {
496 # make this fatal, rather than silently skipping new tapes
497 $self->scan_result(error => $err, res => $res, result_cb => $result_cb);
501 $self->scan_result(res => $res, label => $label, mode => $ACCESS_WRITE,
502 is_new => 1, result_cb => $result_cb);
506 step try_continue => sub {
507 # no luck -- release this reservation and get the next
508 $last_slot = $res->{'this_slot'};
510 $res->release(finished_cb => $steps->{'load'});