Imported Upstream version 3.2.0
[debian/amanda] / perl / Amanda / Recovery / Scan.pm
1 # Copyright (c) 2010 Zmanda, Inc.  All Rights Reserved.
2 #
3 # This program is free software; you can redistribute it and/or modify it
4 # under the terms of the GNU General Public License version 2 as published
5 # by the Free Software Foundation.
6 #
7 # This program 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 General Public License
10 # for more details.
11 #
12 # You should have received a copy of the GNU General Public License along
13 # with this program; if not, write to the Free Software Foundation, Inc.,
14 # 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
15 #
16 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
17 # Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
18
19 package Amanda::Recovery::Scan;
20
21 use strict;
22 use warnings;
23 use Carp;
24 use POSIX ();
25 use Data::Dumper;
26 use vars qw( @ISA );
27 use base qw(Exporter);
28 our @EXPORT_OK = qw($DEFAULT_CHANGER);
29
30 use Amanda::Paths;
31 use Amanda::Util;
32 use Amanda::Device qw( :constants );
33 use Amanda::Debug qw( debug );
34 use Amanda::Changer;
35 use Amanda::MainLoop;
36 use Amanda::Interactive;
37
38 use constant SCAN_ASK  => 1;     # call Amanda::Interactive module
39 use constant SCAN_POLL => 2;     # wait 'poll_delay' and retry the scan.
40 use constant SCAN_FAIL => 3;     # abort
41 use constant SCAN_CONTINUE => 4; # continue to the next step
42 use constant SCAN_ASK_POLL => 5; # call Amanda::Interactive module and
43                                  # poll at the same time.
44
45 =head1 NAME
46
47 Amanda::Recovery::Scan -- interface to scan algorithm
48
49 =head1 SYNOPSIS
50
51     use Amanda::Recovey::Scan;
52
53     # scan the default changer with no interactivity
54     my $scan = Amanda::Recovery::Scan->new();
55     # ..or scan the changer $chg, using $interactive for interactivity
56     $scan = Amanda::Recovery::Scan->new(chg => $chg,
57                                         interactive => $interactive);
58
59     $scan->find_volume(
60         label => "TAPE-012",
61         res_cb => sub {
62             my ($err, $reservation) = @_;
63             if ($err) {
64                 die "$err";
65             }
66             $dev = $reservation->{device};
67             # use device..
68         });
69
70     # later..
71     $reservation->release(finished_cb => $start_next_volume);
72
73     
74 =head1 OVERVIEW
75
76 This package provides a way for programs that need to read data from volumes
77 (loosely called "recovery" programs) to find the volumes they need in a
78 configurable way.  It takes care of prompting for volumes when they are not
79 available, juggling multiple changers, and any other unpredictabilities.
80
81 =head1 INTERFACE
82
83 Like L<Amanda::Changer>, this package operates asynchronously, and thus
84 requires that the caller use L<Amanda::MainLoop> to poll for events.
85
86 A new Scan object is created with the C<new> function as follows:
87
88   my $scan = Amanda::Recovery::Scan->new(scan_conf   => $scan_conf,
89                                          chg         => $chg,
90                                          interactive => $interactive);
91
92 C<scan_conf> is the configuration for the scan, which at this point should be
93 omitted, as configuration is not yet supported.  The C<chg> parameter specifies
94 the changer to start the scan with. The default changer is used if C<chg> is
95 omitted. The C<interactive> parameter gives an C<Amanda::Interactive> object.
96
97 =head2 CALLBACKS
98
99 Many of the callbacks used by this package are identical to the callbacks of
100 the same name in L<Amanda::Changer>.
101
102 When a callback is called with an error, it is an object of type
103 C<Amanda::Changer::Error>.  The C<volinuse> reason has a different meaning: it
104 means that the volume with that label is present in the changer, but is in use
105 by another program.
106
107 =head2 Scan object
108
109 =head3 find_volume
110
111   $scan->find_volume(label       => $label,
112                      res_cb      => $res_cb,
113                      user_msg_fn => $user_msg_fn,
114                      set_current => 0)
115
116 Find the volume labelled C<$label> and call C<$res_cb>.  C<$user_msg_fn> is
117 used to send progress information, The argumnet it takes are describe in
118 the next section.  As with the C<load> method
119 of the changer API, C<set_current> should be set to 1 if you want the scan to
120 set the current slot.
121
122 =head3 user_msg_fn
123
124 The user_msg_fn take various arguments
125
126 Initiate the scan of the slot $slot:
127   $self->user_msg_fn(scan_slot => 1,
128                      slot      => $slot);
129
130 Initiate the scan of the slot $slot which should have the label $label:
131   $self->user_msg_fn(scan_slot => 1,
132                      slot      => $slot,
133                      label     => $label);   
134
135 The result of scanning slot $slot:
136   $self->user_msg_fn(slot_result => 1,
137                      slot        => $slot,
138                      err         => $err,
139                      res         => $res);
140
141 Other options can be added at any time.  The function can ignore them.
142
143 =cut
144
145 our $DEFAULT_CHANGER = {};
146
147 sub new {
148     my $class = shift;
149     my %params = @_;
150     my $scan_conf = $params{'scan_conf'};
151     my $chg = $params{'chg'};
152     my $interactive = $params{'interactive'};
153
154     #until we have a config for it.
155     $scan_conf = Amanda::Recovery::Scan::Config->new();
156     $chg = Amanda::Changer->new() if !defined $chg;
157
158     my $self = {
159         initial_chg => $chg,
160         chg         => $chg,
161         scan_conf   => $scan_conf,
162         interactive => $interactive,
163     };
164     return bless ($self, $class);
165 }
166
167 sub find_volume {
168     my $self = shift;
169     my %params = @_;
170
171     my $label = $params{'label'};
172     my $user_msg_fn = $params{'user_msg_fn'} || \&_user_msg_fn;
173     my $res;
174     my %seen = ();
175     my $inventory;
176     my $current;
177     my $new_slot;
178     my $poll_src;
179     my $scan_running = 0;
180     my $interactive_running = 0;
181     my $restart_scan = 0;
182     my $abort_scan = undef;
183     my $last_err = undef; # keep the last meaningful error, the one reported
184                           # to the user, most scan end with the notfound error,
185                           # it's more interesting to report an error from the
186                           # device or ...
187     my $slot_scanned;
188     my $remove_undef_state = 0;
189     my $load_for_label = 0; # 1 = Try to load the slot with the correct label
190                             # 0 = Load a slot with an unknown label
191
192     my $steps = define_steps
193         cb_ref => \$params{'res_cb'};
194
195     step get_first_inventory => sub {
196         Amanda::Debug::debug("find_volume labeled '$label'");
197
198         $scan_running = 1;
199         $self->{'chg'}->inventory(inventory_cb => $steps->{'got_first_inventory'});
200     };
201
202     step got_first_inventory => sub {
203         (my $err, $inventory) = @_;
204
205         if ($err && $err->notimpl) {
206             #inventory not implemented
207             return $self->_find_volume_no_inventory(%params);
208         } elsif ($err) {
209             #inventory fail
210             return $steps->{'call_res_cb'}->($err, undef);
211         }
212
213         # find current slot and keep a private copy of the value
214         for my $i (0..(scalar(@$inventory)-1)) {
215             if ($inventory->[$i]->{current}) {
216                 $current = $inventory->[$i]->{slot};
217                 last;
218             }
219         }
220
221         if (!defined $current) {
222             if (scalar(@$inventory) == 0) {
223                 $current = 0;
224             } else {
225                 $current = $inventory->[0]->{slot};
226             }
227         }
228
229         # continue parsing the inventory
230         $steps->{'parse_inventory'}->($err, $inventory);
231     };
232
233     step restart_scan => sub {
234         $restart_scan = 0;
235         return $steps->{'get_inventory'}->();
236     };
237
238     step get_inventory => sub {
239         $self->{'chg'}->inventory(inventory_cb => $steps->{'parse_inventory'});
240     };
241
242     step parse_inventory => sub {
243         (my $err, $inventory) = @_;
244
245         if ($err && $err->notimpl) {
246             #inventory not implemented
247             return $self->_find_volume_no_inventory(%params);
248         }
249         return $steps->{'handle_error'}->($err, undef) if $err;
250
251         # throw out the inventory result and move on if the situation has
252         # changed while we were waiting
253         return $steps->{'abort_scan'}->() if $abort_scan;
254         return $steps->{'restart_scan'}->() if $restart_scan;
255
256         # check if label is in the inventory
257         for my $i (0..(scalar(@$inventory)-1)) {
258             my $sl = $inventory->[$i];
259             if (defined $sl->{'label'} &&
260                 $sl->{'label'} eq $label) {
261                 $slot_scanned = $sl->{'slot'};
262                 if ($sl->{'reserved'}) {
263                     return $steps->{'handle_error'}->(
264                             Amanda::Changer::Error->new('failed',
265                                 reason => 'volinuse',
266                                 message => "Volume '$label' in slot $slot_scanned is reserved"),
267                             undef);
268                 }
269                 Amanda::Debug::debug("parse_inventory: load slot $slot_scanned with label '$label'");
270                 $user_msg_fn->(scan_slot => 1,
271                                slot      => $slot_scanned,
272                                label     => $label);
273                 $seen{$slot_scanned} = { device_status => $sl->{'device_status'},
274                                          f_type        => $sl->{'f_type'},
275                                          label         => $sl->{'label'} };
276                 $load_for_label = 1;
277                 return $self->{'chg'}->load(slot => $slot_scanned,
278                                   res_cb => $steps->{'slot_loaded'},
279                                   set_current => $params{'set_current'});
280             }
281         }
282
283         # Remove from seen all slot that have state == SLOT_UNKNOWN
284         # It is done when as scan is restarted from interactive object.
285         if ($remove_undef_state) {
286             for my $i (0..(scalar(@$inventory)-1)) {
287                 my $slot = $inventory->[$i]->{slot};
288                 if (exists($seen{$slot}) &&
289                     !defined($inventory->[$i]->{state})) {
290                     delete $seen{$slot}
291                 }
292             }
293             $remove_undef_state = 0;
294         }
295
296         # remove any slots where the state has changed from the list of seen slots
297         for my $i (0..(scalar(@$inventory)-1)) {
298             my $sl = $inventory->[$i];
299             my $slot = $sl->{slot};
300             if ($seen{$slot} &&
301                 defined($sl->{'state'}) &&
302                 (($seen{$slot}->{'device_status'} != $sl->{'device_status'}) ||
303                  (defined $seen{$slot}->{'device_status'} &&
304                   $seen{$slot}->{'device_status'} == $DEVICE_STATUS_SUCCESS &&
305                   $seen{$slot}->{'f_type'} != $sl->{'f_type'}) ||
306                  (defined $seen{$slot}->{'device_status'} &&
307                   $seen{$slot}->{'device_status'} == $DEVICE_STATUS_SUCCESS &&
308                   defined $seen{$slot}->{'f_type'} &&
309                   $seen{$slot}->{'f_type'} == $Amanda::Header::F_TAPESTART &&
310                   $seen{$slot}->{'label'} ne $sl->{'label'}))) {
311                 delete $seen{$slot};
312             }
313         }
314
315         # scan any unseen slot already in a drive, if configured to do so
316         if ($self->{'scan_conf'}->{'scan_drive'}) {
317             for my $sl (@$inventory) {
318                 my $slot = $sl->{'slot'};
319                 if (defined $sl->{'loaded_in'} &&
320                     !$sl->{'reserved'} &&
321                     !$seen{$slot}) {
322                     $slot_scanned = $slot;
323                     $user_msg_fn->(scan_slot => 1, slot => $slot_scanned);
324                     $seen{$slot_scanned} = { device_status => $sl->{'device_status'},
325                                              f_type        => $sl->{'f_type'},
326                                              label         => $sl->{'label'} };
327                     $load_for_label = 0;
328                     return $self->{'chg'}->load(slot => $slot_scanned,
329                                       res_cb => $steps->{'slot_loaded'},
330                                       set_current => $params{'set_current'});
331                 }
332             }
333         }
334
335         # scan slot
336         if ($self->{'scan_conf'}->{'scan_unknown_slot'}) {
337             #find index for current slot
338             my $current_index = undef;
339             for my $i (0..(scalar(@$inventory)-1)) {
340                 my $slot = $inventory->[$i]->{slot};
341                 if ($slot eq $current) {
342                     $current_index = $i;
343                 }
344             }
345
346             #scan next slot to scan
347             $current_index = 0 if !defined $current_index;
348             for my $i ($current_index..(scalar(@$inventory)-1), 0..($current_index-1)) {
349                 my $sl = $inventory->[$i];
350                 my $slot = $sl->{slot};
351                 # skip slots we've seen
352                 next if defined($seen{$slot});
353                 # skip slots that are empty
354                 next if defined $sl->{'state'} &&
355                         $sl->{'state'} == Amanda::Changer::SLOT_EMPTY;
356                 # skip slots for which we have a known label, since it's not the
357                 # one we want
358                 next if defined $sl->{'f_type'} &&
359                         $sl->{'f_type'} == $Amanda::Header::F_TAPESTART;
360                 next if defined $sl->{'label'};
361
362                 # found a slot to check - reset our current slot
363                 $current = $slot;
364                 $slot_scanned = $current;
365                 Amanda::Debug::debug("parse_inventory: load slot $current");
366                 $user_msg_fn->(scan_slot => 1, slot => $slot_scanned);
367                 $seen{$slot_scanned} = { device_status => $sl->{'device_status'},
368                                          f_type        => $sl->{'f_type'},
369                                          label         => $sl->{'label'} };
370                 $load_for_label = 0;
371                 return $self->{'chg'}->load(slot => $slot_scanned,
372                                 res_cb => $steps->{'slot_loaded'},
373                                 set_current => $params{'set_current'});
374             }
375         }
376
377         #All slots are seen or empty.
378         if ($last_err) {
379             return $steps->{'handle_error'}->($last_err, undef);
380         } else {
381             return $steps->{'handle_error'}->(
382                     Amanda::Changer::Error->new('failed',
383                             reason => 'notfound',
384                             message => "Volume '$label' not found"),
385                     undef);
386         }
387     };
388
389     step slot_loaded => sub {
390         (my $err, $res) = @_;
391
392         # we don't responsd to abort_scan or restart_scan here, since we
393         # have an open reservation that we should deal with.
394
395         $user_msg_fn->(slot_result => 1,
396                        slot => $slot_scanned,
397                        err  => $err,
398                        res  => $res);
399         if ($res) {
400             if ($res->{device}->status == $DEVICE_STATUS_SUCCESS &&
401                 $res->{device}->volume_label &&
402                 $res->{device}->volume_label eq $label) {
403                 my $volume_label = $res->{device}->volume_label;
404                 return $steps->{'call_res_cb'}->(undef, $res);
405             }
406             my $f_type;
407             if (defined $res->{device}->volume_header) {
408                 $f_type = $res->{device}->volume_header->{type};
409             } else {
410                 $f_type = undef;
411             }
412
413             # The slot did not contain the volume we wanted, so mark it
414             # as seen and try again.
415             $seen{$slot_scanned} = {
416                         device_status => $res->{device}->status,
417                         f_type => $f_type,
418                         label  => $res->{device}->volume_label
419             };
420
421             # notify the user
422             if ($res->{device}->status == $DEVICE_STATUS_SUCCESS) {
423                 $last_err = undef;
424             } else {
425                 $last_err = Amanda::Changer::Error->new('fatal',
426                                 message => $res->{device}->error_or_status());
427             }
428             return $res->release(finished_cb => $steps->{'load_released'});
429         } else {
430             if ($load_for_label == 0 && $err->volinuse) {
431                 # Scan semantics for volinuse is different than changer.
432                 # If a slot with unknown label is loaded then we map
433                 # volinuse to driveinuse.
434                 $err->{reason} = "driveinuse";
435             }
436             $last_err = $err if $err->fatal || !$err->notfound;
437             if ($load_for_label == 1 && $err->failed && $err->volinuse) {
438                 # volinuse is an error
439                 return $steps->{'handle_error'}->($err, $steps->{'load_released'});
440             }
441             return $steps->{'load_released'}->();
442         }
443     };
444
445     step load_released => sub {
446         my ($err) = @_;
447
448         # TODO: handle error
449
450         $res = undef;
451
452         # throw out the inventory result and move on if the situation has
453         # changed while we were loading a volume
454         return $steps->{'abort_scan'}->() if $abort_scan;
455         return $steps->{'restart_scan'}->() if $restart_scan;
456
457         $new_slot = $current;
458         $steps->{'get_inventory'}->();
459     };
460
461     step handle_error => sub {
462         my ($err, $continue_cb) = @_;
463
464         my $scan_method = undef;
465         $scan_running = 0;
466         my $message;
467
468
469         $poll_src->remove() if defined $poll_src;
470         $poll_src = undef;
471
472         # prefer to use scan method for $last_err, if present
473         if ($last_err && $err->failed && $err->notfound) {
474             $message = "$last_err";
475         
476             if ($last_err->isa("Amanda::Changer::Error")) {
477                 if ($last_err->fatal) {
478                     $scan_method = $self->{'scan_conf'}->{'fatal'};
479                 } else {
480                     $scan_method = $self->{'scan_conf'}->{$last_err->{'reason'}};
481                 }
482             } elsif ($continue_cb) {
483                 $scan_method = SCAN_CONTINUE;
484             }
485         }
486
487         #use scan method for $err
488         if (!defined $scan_method) {
489             if ($err) {
490                 $message = "$err" if !defined $message;
491                 if ($err->fatal) {
492                     $scan_method = $self->{'scan_conf'}->{'fatal'};
493                 } else {
494                     $scan_method = $self->{'scan_conf'}->{$err->{'reason'}};
495                 }
496             } else {
497                 die("error not defined");
498                 $scan_method = SCAN_ASK_POLL;
499             }
500         }
501
502         ## implement the desired scan method
503
504         if ($scan_method == SCAN_CONTINUE && !defined $continue_cb) {
505             $scan_method = $self->{'scan_conf'}->{'notfound'};
506             if ($scan_method == SCAN_CONTINUE) {
507                 $scan_method = SCAN_FAIL;
508             }
509         }
510
511         if ($scan_method == SCAN_ASK && !defined $self->{'interactive'}) {
512             $scan_method = SCAN_FAIL;
513         }
514
515         if ($scan_method == SCAN_ASK_POLL && !defined $self->{'interactive'}) {
516             $scan_method = SCAN_FAIL;
517         }
518
519         if ($scan_method == SCAN_ASK) {
520             return $steps->{'scan_interactive'}->("$message");
521         } elsif ($scan_method == SCAN_POLL) {
522             $poll_src = Amanda::MainLoop::call_after(
523                                 $self->{'scan_conf'}->{'poll_delay'},
524                                 $steps->{'after_poll'});
525             return;
526         } elsif ($scan_method == SCAN_ASK_POLL) {
527             $steps->{'scan_interactive'}->("$message\n");
528             $poll_src = Amanda::MainLoop::call_after(
529                                 $self->{'scan_conf'}->{'poll_delay'},
530                                 $steps->{'after_poll'});
531             return;
532         } elsif ($scan_method == SCAN_FAIL) {
533             return $steps->{'call_res_cb'}->($err, undef);
534         } elsif ($scan_method == SCAN_CONTINUE) {
535             return $continue_cb->($err, undef);
536         } else {
537             die("Invalid SCAN_* value:$err:$err->{'reason'}:$scan_method");
538         }
539     };
540
541     step after_poll => sub {
542         $poll_src->remove() if defined $poll_src;
543         $poll_src = undef;
544         return $steps->{'restart_scan'}->();
545     };
546
547     step scan_interactive => sub {
548         my ($err_message) = @_;
549         if (!$interactive_running) {
550             $interactive_running = 1;
551             my $message = "$err_message\nInsert volume labeled '$label' in changer and type <enter>\nor type \"^D\" to abort\n";
552             $self->{'interactive'}->user_request(
553                                 message     => $message,
554                                 label       => $label,
555                                 err         => "$err_message",
556                                 chg_name    => $self->{'chg'}->{'chg_name'},
557                                 finished_cb => $steps->{'scan_interactive_cb'});
558         }
559         return;
560     };
561
562     step scan_interactive_cb => sub {
563         my ($err, $message) = @_;
564         $interactive_running = 0;
565         $poll_src->remove() if defined $poll_src;
566         $poll_src = undef;
567         $last_err = undef;
568
569         if ($err) {
570             if ($scan_running) {
571                 $abort_scan = $err;
572                 return;
573             } else {
574                 return $steps->{'call_res_cb'}->($err, undef);
575             }
576         }
577
578         if ($message ne '') {
579             # use a new changer
580             my $new_chg;
581             if (ref($message) eq 'HASH' and $message == $DEFAULT_CHANGER) {
582                 $new_chg = Amanda::Changer->new();
583             } else {
584                 $new_chg = Amanda::Changer->new($message);
585             }
586             if ($new_chg->isa("Amanda::Changer::Error")) {
587                 return $steps->{'scan_interactive'}->("$new_chg");
588             }
589             $self->{'chg'} = $new_chg;
590             %seen = ();
591         } else {
592             $remove_undef_state = 1;
593         }
594
595         if ($scan_running) {
596             $restart_scan = 1;
597             return;
598         } else {
599             return $steps->{'restart_scan'}->();
600         }
601     };
602
603     step abort_scan => sub {
604         $steps->{'call_res_cb'}->($abort_scan, undef);
605     };
606
607     step call_res_cb => sub {
608         (my $err, $res) = @_;
609
610         # TODO: what happens if the search was aborted or
611         # restarted in the interim?
612
613         $abort_scan = undef;
614         $poll_src->remove() if defined $poll_src;
615         $poll_src = undef;
616         $interactive_running = 0;
617         $self->{'interactive'}->abort() if defined $self->{'interactive'};
618         $params{'res_cb'}->($err, $res);
619     };
620 }
621
622 #
623 sub _find_volume_no_inventory {
624     my $self = shift;
625     my %params = @_;
626
627     my $label = $params{'label'};
628     my $res;
629     my %seen_slots = ();
630     my $inventory;
631     my $current;
632     my $new_slot;
633     my $last_slot;
634
635     my $steps = define_steps
636         cb_ref => \$params{'res_cb'};
637
638     step load_label => sub {
639         return $self->{'chg'}->load(relative_slot => "current",
640                                     res_cb => $steps->{'load_label_cb'});
641     };
642
643     step load_label_cb => sub {
644         (my $err, $res) = @_;
645
646         if ($err) {
647             if ($err->failed && $err->notfound) {
648                 if ($err->{'message'} eq "all slots have been loaded") {
649                     $err->{'message'} = "label '$label' not found";
650                 }
651                 return $params{'res_cb'}->($err, undef);
652             } elsif ($err->failed && $err->volinuse and defined $err->{'slot'}) {
653                 $last_slot = $err->{'slot'};
654             } else {
655                 #no interactivity yet.
656                 return $params{'res_cb'}->($err, undef);
657             }
658         } else {
659             $last_slot = $res->{'this_slot'}
660         }
661
662         $seen_slots{$last_slot} = 1 if defined $last_slot;
663         if ($res) {
664             my $dev = $res->{'device'};
665             if (defined $dev->volume_label && $dev->volume_label eq $label) {
666                 return $params{'res_cb'}->(undef, $res);
667             }
668             return $res->release(finished_cb => $steps->{'released'});
669         } else {
670             return $steps->{'released'}->()
671         }
672     };
673
674     step released => sub {
675         $self->{'chg'}->load(relative_slot => "next",
676                    except_slots => \%seen_slots,
677                    res_cb => $steps->{'load_label_cb'},
678                    set_current => 1);
679     };
680 }
681
682 sub _user_msg_fn {
683     my %params = @_;
684 }
685
686 package Amanda::Recovery::Scan::Config;
687
688 sub new {
689     my $class = shift;
690     my ($cc) = @_;
691
692     my $self = bless {}, $class;
693
694     $self->{'scan_drive'} = 0;
695     $self->{'scan_unknown_slot'} = 1;
696     $self->{'scan_interactivity'} = undef;
697     $self->{'poll'} = 0;
698     $self->{'wait'} = 1;
699     $self->{'poll_unknown_slot'} = 0;
700     $self->{'poll_drive'} = 0;
701     $self->{'poll_delay'} = 10000; #10 seconds
702     $self->{'user_unknown_slot'} = 1;
703     $self->{'user_drive'} = 0;
704     $self->{'fatal'} = Amanda::Recovery::Scan::SCAN_CONTINUE;
705     $self->{'driveinuse'} = Amanda::Recovery::Scan::SCAN_ASK_POLL;
706     $self->{'volinuse'} = Amanda::Recovery::Scan::SCAN_ASK_POLL;
707     $self->{'notfound'} = Amanda::Recovery::Scan::SCAN_ASK_POLL;
708     $self->{'unknown'} = Amanda::Recovery::Scan::SCAN_FAIL;
709     $self->{'notimpl'} = Amanda::Recovery::Scan::SCAN_FAIL;
710     $self->{'invalid'} = Amanda::Recovery::Scan::SCAN_CONTINUE;
711
712     return $self;
713 }
714
715 1;