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