Imported Upstream version 3.3.0
[debian/amanda] / server-src / amcheck-device.pl
1 #! @PERL@
2 # Copyright (c) 2009, 2010 Zmanda Inc.  All Rights Reserved.
3 #
4 # This program is free software; you can redistribute it and/or modify it
5 # under the terms of the GNU General Public License version 2 as published
6 # by the Free Software Foundation.
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 94086, USA, or: http://www.zmanda.com
19
20 use lib '@amperldir@';
21 use strict;
22 use warnings;
23
24 use Amanda::Util qw( :constants );
25 use Amanda::Config qw( :init :getconf );
26 use Amanda::Logfile qw( :logtype_t log_add $amanda_log_trace_log );
27 use Amanda::Debug;
28 use Amanda::Device qw( :constants );
29 use Amanda::MainLoop;
30 use Amanda::Changer;
31 use Amanda::Taper::Scan;
32 use Amanda::Interactivity;
33 use Getopt::Long;
34
35 Amanda::Util::setup_application("amcheck-device", "server", $CONTEXT_CMDLINE);
36
37 my $config_overrides = new_config_overrides($#ARGV+1);
38 my $overwrite = 0;
39 Getopt::Long::Configure(qw{bundling});
40 GetOptions(
41     'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
42     'w' => \$overwrite,
43 ) or usage();
44
45 sub usage {
46     print STDERR "USAGE: amcheck-device <config> [-w] <config-overwrites>";
47     exit 1;
48 }
49
50 if (@ARGV != 1) {
51     usage();
52 }
53
54 set_config_overrides($config_overrides);
55 config_init($CONFIG_INIT_EXPLICIT_NAME, $ARGV[0]);
56 my ($cfgerr_level, @cfgerr_errors) = config_errors();
57 if ($cfgerr_level >= $CFGERR_WARNINGS) {
58     config_print_errors();
59     if ($cfgerr_level >= $CFGERR_ERRORS) {
60         print STDERR "Errors processing config file";
61         exit 1;
62     }
63 }
64
65 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
66 my $exit_status = 0;
67
68 sub _user_msg_fn {
69         my %params = @_;
70         if (exists $params{'scan_failed'}) {
71             print STDERR "Taper scan algorithm did not find an acceptable volume.\n";
72             if ($params{'expected_label'} or $params{'expected_new'}) {
73                 my @exp;
74                 if ($params{'expected_label'}) {
75                     push @exp, "volume '$params{expected_label}'";
76                 }
77                 if ($params{'expected_new'}) {
78                     push @exp, "a new volume";
79                 }
80                 my $exp = join(" or ", @exp);
81                 print STDERR "    (expecting $exp)\n";
82             }
83         } elsif (exists($params{'text'})) {
84             print STDERR "$params{'text'}\n";
85         } elsif (exists($params{'scan_slot'})) {
86             print STDERR "slot $params{'slot'}:";
87         } elsif (exists($params{'search_label'})) {
88             print STDERR "Searching for label '$params{'label'}':";
89         } elsif (exists($params{'slot_result'}) ||
90                  exists($params{'search_result'})) {
91             if (defined($params{'err'})) {
92                 if (exists($params{'search_result'}) &&
93                     defined($params{'err'}->{'this_slot'})) {
94                     print STDERR "slot $params{'err'}->{'this_slot'}:";
95                 }
96                 print STDERR " $params{'err'}\n";
97             } else { # res must be defined
98                 my $res = $params{'res'};
99                 my $dev = $res->{'device'};
100                 if (exists($params{'search_result'})) {
101                     print STDERR "found in slot $res->{'this_slot'}:";
102                 }
103                 if ($dev->status == $DEVICE_STATUS_SUCCESS) {
104                     my $volume_label = $res->{device}->volume_label;
105                     if ($params{'active'}) {
106                         print STDERR " volume '$volume_label' is still active and cannot be overwritten\n";
107                     } elsif ($params{'does_not_match_labelstr'}) {
108                         print STDERR " volume '$volume_label' does not match labelstr '$params{'labelstr'}'\n";
109                     } elsif ($params{'not_in_tapelist'}) {
110                         print STDERR " volume '$volume_label' is not in the tapelist\n"
111                     } else {
112                         print STDERR " volume '$volume_label'\n";
113                     }
114                 } elsif ($dev->status & $DEVICE_STATUS_VOLUME_UNLABELED and
115                          $dev->volume_header and
116                          $dev->volume_header->{'type'} == $Amanda::Header::F_EMPTY) {
117                     print STDERR " contains an empty volume\n";
118                 } elsif ($dev->status & $DEVICE_STATUS_VOLUME_UNLABELED and
119                          $dev->volume_header and
120                          $dev->volume_header->{'type'} == $Amanda::Header::F_WEIRD) {
121                     print STDERR " contains a non-Amanda volume; check and relabel it with 'amlabel -f'\n";
122                 } elsif ($dev->status & $DEVICE_STATUS_VOLUME_ERROR) {
123                     my $message = $dev->error_or_status();
124                     print STDERR " Can't read label: $message\n";
125                 } else {
126                     my $errmsg = $res->{device}->error_or_status();
127                     print STDERR " $errmsg\n";
128                 }
129             }
130         } else {
131             print STDERR "UNKNOWN\n";
132         }
133 }
134
135 sub failure {
136     my ($msg, $finished_cb) = @_;
137     print STDERR "ERROR: $msg\n";
138     $exit_status = 1;
139     $finished_cb->();
140 }
141
142 sub do_check {
143     my ($finished_cb) = @_;
144     my ($res, $label, $mode);
145     my $tlf = Amanda::Config::config_dir_relative(getconf($CNF_TAPELIST));
146     my $tl = Amanda::Tapelist->new($tlf);
147     my $chg = Amanda::Changer->new(undef, tapelist => $tl);
148     return failure($chg, $finished_cb) if ($chg->isa("Amanda::Changer::Error"));
149     my $interactivity = Amanda::Interactivity->new(
150                                         name => getconf($CNF_INTERACTIVITY));
151     my $scan_name = getconf($CNF_TAPERSCAN);
152     my $taperscan = Amanda::Taper::Scan->new(algorithm => $scan_name,
153                                              changer => $chg,
154                                              interactivity => $interactivity,
155                                              tapelist => $tl);
156
157     my $steps = define_steps
158         cb_ref => \$finished_cb,
159         finalize => sub { $taperscan->quit(); };
160
161     step start => sub {
162         $taperscan->scan(
163             result_cb => $steps->{'result_cb'},
164             user_msg_fn => \&_user_msg_fn
165         );
166     };
167
168     step result_cb => sub {
169         (my $err, $res, $label, $mode) = @_;
170         return failure($err, $finished_cb) if $err;
171
172         my $modestr = ($mode == $ACCESS_APPEND)? "append" : "write";
173         my $slot = $res->{'this_slot'};
174         if (defined $res->{'device'}->volume_label()) {
175             print "Will $modestr to volume '$label' in slot $slot.\n";
176         } else {
177             print "Will $modestr label '$label' to new volume in slot $slot.\n";
178         }
179
180         $steps->{'check_access_type'}->();
181     };
182
183     step check_access_type => sub {
184         my $mat = $res->{'device'}->property_get('medium_access_type');
185         if (defined $mat and $mat == $MEDIA_ACCESS_MODE_WRITE_ONLY) {
186             print "WARNING: Media access mode is WRITE_ONLY; dumps may not be recoverable\n";
187         }
188
189         if (getconf_seen($CNF_DEVICE_OUTPUT_BUFFER_SIZE)) {
190             my $dobs = getconf($CNF_DEVICE_OUTPUT_BUFFER_SIZE);
191             my $block_size = $res->{'device'}->property_get("BLOCK_SIZE");
192             if ($block_size * 2 > $dobs) {
193                 print "WARNING: DEVICE-OUTPUT-BUFFER-SIZE is not at least twice the block size of the device, it should be increased for better throughput\n";
194             }
195         }
196         $steps->{'check_overwrite'}->();
197     };
198
199     step check_overwrite => sub {
200         # if we're not overwriting, just release the reservation
201         if (!$overwrite) {
202             print "NOTE: skipping tape-writable test\n";
203             return $steps->{'release'}->();
204         }
205
206         if ($mode != $ACCESS_WRITE) {
207             my $modestr = Amanda::Device::DeviceAccessMode_to_string($mode);
208             print "NOTE: taperscan specified access mode $modestr; skipping volume-writeable test\n";
209             return $steps->{'release'}->();
210         }
211
212         print "Writing label '$label' to check writablility\n";
213         if (!$res->{'device'}->start($ACCESS_WRITE, $label, "X")) {
214             print "ERROR: writing to volume: " . $res->{'device'}->error_or_status(), "\n";
215             $exit_status = 1;
216         } else {
217             print "Volume '$label' is writeable.\n";
218         }
219
220         $steps->{'release'}->();
221     };
222
223     step release => sub {
224         $res->release(finished_cb => $steps->{'released'});
225     };
226
227     step released => sub {
228         my ($err) = @_;
229         return failure($err, $finished_cb) if $err;
230
231         $finished_cb->();
232     };
233 }
234
235 Amanda::MainLoop::call_later(\&do_check, \&Amanda::MainLoop::quit);
236 Amanda::MainLoop::run();
237 Amanda::Util::finish_application();
238 exit($exit_status);