Imported Upstream version 3.2.1
[debian/amanda] / server-src / amlabel.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 File::Basename;
25 use Getopt::Long;
26 use Text::Wrap;
27
28 use Amanda::Device qw( :constants );
29 use Amanda::Debug qw( :logging );
30 use Amanda::Config qw( :init :getconf config_dir_relative );
31 use Amanda::Util qw( :constants );
32 use Amanda::Changer;
33 use Amanda::Header qw( :constants );
34 use Amanda::MainLoop;
35 use Amanda::Tapelist;
36
37 my $exit_status = 0;
38
39 ##
40 # Subcommand handling
41
42 my %subcommands;
43
44 sub usage {
45     print STDERR "Usage: amlabel [-f] [-o configoption]* <conf> <label> [slot <slot-number>]\n";
46     exit(1);
47 }
48
49 Amanda::Util::setup_application("amlabel", "server", $CONTEXT_CMDLINE);
50
51 my $config_overrides = new_config_overrides($#ARGV+1);
52 my ($opt_force, $opt_config, $opt_slot, $opt_label);
53
54 $opt_force = 0;
55 Getopt::Long::Configure(qw(bundling));
56 GetOptions(
57     'help|usage|?' => \&usage,
58     'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
59     'f' => \$opt_force,
60     'version' => \&Amanda::Util::version_opt,
61 ) or usage();
62
63 if (@ARGV == 2) {
64     $opt_slot = undef;
65 } elsif (@ARGV == 4 and $ARGV[2] eq 'slot') {
66     $opt_slot = $ARGV[3];
67 } else {
68     usage();
69 }
70
71 $opt_config = $ARGV[0];
72 $opt_label = $ARGV[1];
73
74 set_config_overrides($config_overrides);
75 config_init($CONFIG_INIT_EXPLICIT_NAME, $opt_config);
76 my ($cfgerr_level, @cfgerr_errors) = config_errors();
77 if ($cfgerr_level >= $CFGERR_WARNINGS) {
78     config_print_errors();
79     if ($cfgerr_level >= $CFGERR_ERRORS) {
80         print STDERR "errors processing config file";
81         exit 1;
82     }
83 }
84
85 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
86
87 my ($tlf, $tl, $res);
88
89 sub failure {
90     my ($msg, $finished_cb) = @_;
91     print STDERR "$msg\n";
92     $exit_status = 1;
93     if ($res) {
94         $res->release(finished_cb => sub {
95             # ignore error
96             $finished_cb->()
97         });
98     } else {
99         $finished_cb->();
100     }
101 }
102
103 sub main {
104     my ($finished_cb) = @_;
105
106     my $steps = define_steps
107         cb_ref => \$finished_cb;
108
109     step start => sub {
110         my $labelstr = getconf($CNF_LABELSTR);
111         if ($opt_label !~ /$labelstr/) {
112             return failure("Label '$opt_label' doesn't match labelstr '$labelstr'.", $finished_cb);
113         }
114
115         $tlf = Amanda::Config::config_dir_relative(getconf($CNF_TAPELIST));
116         $tl = Amanda::Tapelist->new($tlf);
117         if (!defined $tl) {
118             return failure("Can't load tapelist file ($tlf)", $finished_cb);
119         }
120         if (!$opt_force) {
121             if ($tl->lookup_tapelabel($opt_label)) {
122                 return failure("Label '$opt_label' already on a volume", $finished_cb);
123             }
124         }
125
126         $steps->{'load'}->();
127     };
128
129     step load => sub {
130         my $chg = Amanda::Changer->new();
131
132         return failure($chg, $finished_cb)
133             if $chg->isa("Amanda::Changer::Error");
134
135         print "Reading label...\n";
136         if ($opt_slot) {
137             $chg->load(slot => $opt_slot, mode => "write",
138                     res_cb => $steps->{'loaded'});
139         } else {
140             $chg->load(relative_slot => "current", mode => "write",
141                     res_cb => $steps->{'loaded'});
142         }
143     };
144
145     step loaded => sub {
146         (my $err, $res) = @_;
147
148         return failure($err, $finished_cb) if $err;
149
150         my $dev = $res->{'device'};
151         my $dev_ok = 1;
152         if ($dev->status & $DEVICE_STATUS_VOLUME_UNLABELED) {
153             if (!$dev->volume_header or $dev->volume_header->{'type'} == $F_EMPTY) {
154                 print "Found an empty tape.\n";
155             } else {
156                 # force is required for non-Amanda tapes
157                 print "Found a non-Amanda tape.\n";
158                 $dev_ok = 0 unless ($opt_force);
159             }
160         } elsif ($dev->status & $DEVICE_STATUS_VOLUME_ERROR) {
161             # it's OK to force through VOLUME_ERROR
162             print "Error reading volume label: " . $dev->error_or_status(), "\n";
163             $dev_ok = 0 unless ($opt_force);
164         } elsif ($dev->status != $DEVICE_STATUS_SUCCESS) {
165             # but anything else is fatal
166             print "Error reading volume label: " . $dev->error_or_status(), "\n";
167             $dev_ok = 0;
168         } else {
169             # this is a labeled Amanda tape
170             my $label = $dev->volume_label;
171             my $labelstr = getconf($CNF_LABELSTR);
172
173             if ($label !~ /$labelstr/) {
174                 print "Found label '$label', but it is not from configuration " .
175                     "'" . Amanda::Config::get_config_name() . "'.\n";
176                 $dev_ok = 0 unless ($opt_force);
177             } elsif ($tl->lookup_tapelabel($label)) {
178                 print "Volume with label '$label' is active and contains data from this configuration.\n";
179                 if ($opt_force) {
180                     # if -f, then the user should clean things up..
181                     print "Consider using 'amrmtape' to remove volume '$label' from the catalog.\n";
182                     # note that we don't run amrmtape automatically, as it could result in data loss when
183                     # multiple volumes have (perhaps accidentally) the same label
184                 } else {
185                     $dev_ok = 0
186                 }
187             } else {
188                 print "Found Amanda volume '$label'.\n";
189             }
190         }
191
192         if ($dev_ok) {
193             print "Writing label '$opt_label'...\n";
194
195             if (!$dev->start($ACCESS_WRITE, $opt_label, "X")) {
196                 return failure("Error writing label: " . $dev->error_or_status(), $finished_cb);
197             } elsif (!$dev->finish()) {
198                 return failure("Error finishing device: " . $dev->error_or_status(), $finished_cb);
199             }
200
201             print "Checking label...\n";
202             my $status = $dev->read_label();
203             if ($status != $DEVICE_STATUS_SUCCESS) {
204                 return failure("Checking the tape label failed: " . $dev->error_or_status(),
205                         $finished_cb);
206             } elsif (!$dev->volume_label) {
207                 return failure("No label found.", $finished_cb);
208             } elsif ($dev->volume_label ne $opt_label) {
209                 my $got = $dev->volume_label;
210                 return failure("Read back a different label: got '$got', but expected '$opt_label'",
211                         $finished_cb);
212             } elsif ($dev->volume_time ne "X") {
213                 my $got = $dev->volume_time;
214                 return failure("Read back a different timetstamp: got '$got', but expected 'X'",
215                         $finished_cb);
216             }
217
218             # update the tapelist
219             $tl->reload(1);
220             $tl->remove_tapelabel($opt_label);
221             $tl->add_tapelabel("0", $opt_label, undef, 1, undef, $res->{'barcode'});
222             $tl->write();
223
224             print "Success!\n";
225
226             # notify the changer
227             $res->set_label(label => $opt_label, finished_cb => $steps->{'labeled'});
228         } else {
229             return failure("Not writing label.", $finished_cb);
230         }
231     };
232
233     step labeled => sub {
234         my ($err) = @_;
235         return failure($err, $finished_cb) if $err;
236
237         $res->release(finished_cb => $steps->{'released'});
238     };
239
240     step released => sub {
241         my ($err) = @_;
242         return failure($err, $finished_cb) if $err;
243
244         $finished_cb->();
245     };
246 }
247 main(\&Amanda::MainLoop::quit);
248 Amanda::MainLoop::run();
249 Amanda::Util::finish_application();
250 exit($exit_status);