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