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