Imported Upstream version 3.3.0
[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 [--barcode <barcode>] [--meta <meta>] [--assign] [--version]\n"
46                . "               [-f] [-o configoption]* <conf> [<label>] [slot <slot-number>]\n";
47     exit(1);
48 }
49
50 Amanda::Util::setup_application("amlabel", "server", $CONTEXT_CMDLINE);
51
52 my $config_overrides = new_config_overrides($#ARGV+1);
53 my ($opt_force, $opt_config, $opt_slot, $opt_label);
54 my ($opt_barcode, $opt_meta, $opt_assign);
55
56 $opt_force = 0;
57 $opt_barcode = undef;
58 $opt_meta = undef;
59 $opt_assign = undef;
60 Getopt::Long::Configure(qw(bundling));
61 GetOptions(
62     'help|usage|?' => \&usage,
63     'o=s'        => sub { add_config_override_opt($config_overrides, $_[1]); },
64     'f'          => \$opt_force,
65     'barcode=s'  => \$opt_barcode,
66     'meta=s'     => \$opt_meta,
67     'assign'     => \$opt_assign,
68     'version'    => \&Amanda::Util::version_opt,
69 ) or usage();
70
71 if ($opt_assign && (!$opt_meta || !$opt_barcode)) {
72     print STDERR "--assign require --barcode or --meta\n";
73     usage();
74 }
75
76 usage() if @ARGV == 0;
77 $opt_config = $ARGV[0];
78 if (@ARGV == 1) {
79     $opt_slot = undef;
80     $opt_label = undef;
81 } elsif (@ARGV == 2) {
82     $opt_slot = undef;
83     $opt_label = $ARGV[1];
84 } elsif (@ARGV == 3 and $ARGV[1] eq 'slot') {
85     $opt_slot = $ARGV[2];
86     $opt_label = undef;
87 } elsif (@ARGV == 4 and $ARGV[2] eq 'slot') {
88     $opt_slot = $ARGV[3];
89     $opt_label = $ARGV[1];
90 } else {
91     usage();
92 }
93
94 set_config_overrides($config_overrides);
95 config_init($CONFIG_INIT_EXPLICIT_NAME, $opt_config);
96 my ($cfgerr_level, @cfgerr_errors) = config_errors();
97 if ($cfgerr_level >= $CFGERR_WARNINGS) {
98     config_print_errors();
99     if ($cfgerr_level >= $CFGERR_ERRORS) {
100         print STDERR "errors processing config file";
101         exit 1;
102     }
103 }
104
105 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
106
107 my ($tlf, $tl, $res);
108
109 sub failure {
110     my ($msg, $finished_cb) = @_;
111     print STDERR "$msg\n";
112     $exit_status = 1;
113     if ($res) {
114         $res->release(finished_cb => sub {
115             # ignore error
116             $finished_cb->()
117         });
118     } else {
119         $finished_cb->();
120     }
121 }
122
123 sub main {
124     my ($finished_cb) = @_;
125     my $gerr;
126     my $chg;
127     my $dev;
128     my $dev_ok;
129
130     my $steps = define_steps
131         cb_ref => \$finished_cb,
132         finalize => sub { $chg->quit() if defined $chg };
133
134     step start => sub {
135         my $labelstr = getconf($CNF_LABELSTR);
136         if (defined ($opt_label) && $opt_label !~ /$labelstr/) {
137             return failure("Label '$opt_label' doesn't match labelstr '$labelstr'.", $finished_cb);
138         }
139
140         $tlf = Amanda::Config::config_dir_relative(getconf($CNF_TAPELIST));
141         $tl = Amanda::Tapelist->new($tlf);
142         if (!defined $tl) {
143             return failure("Can't load tapelist file ($tlf)", $finished_cb);
144         }
145
146         $chg = Amanda::Changer->new(undef, tapelist => $tl);
147
148         return failure($chg, $finished_cb)
149             if $chg->isa("Amanda::Changer::Error");
150
151         if ($opt_assign) {
152             return $steps->{'assign'}->();
153         }
154
155         if (defined($opt_label) && !$opt_force) {
156             if ($tl->lookup_tapelabel($opt_label)) {
157                 return failure("Label '$opt_label' already on a volume", $finished_cb);
158             }
159         }
160
161         $steps->{'load'}->();
162     };
163
164     step load => sub {
165         print "Reading label...\n";
166         if ($opt_slot) {
167             $chg->load(slot => $opt_slot, mode => "write",
168                        res_cb => $steps->{'loaded'});
169         } elsif ($opt_barcode) {
170             $chg->inventory(inventory_cb => $steps->{'inventory'});
171         } else {
172             $chg->load(relative_slot => "current", mode => "write",
173                        res_cb => $steps->{'loaded'});
174         }
175     };
176
177     step inventory => sub {
178         my ($err, $inv) = @_;
179
180         return failure($err, $finished_cb) if $err;
181
182         for my $sl (@$inv) {
183             if ($sl->{'barcode'} eq $opt_barcode) {
184                 return $chg->load(slot => $sl->{'slot'}, mode => "write",
185                                   res_cb => $steps->{'loaded'});
186             }
187         }
188
189         return failure("No volume with barcode '$opt_barcode' available", $finished_cb);
190     };
191
192     step loaded => sub {
193         (my $err, $res) = @_;
194
195         return failure($err, $finished_cb) if $err;
196
197         if (defined $opt_slot && defined $opt_barcode &&
198             $opt_barcode ne $res->{'barcode'}) {
199             if (defined $res->{'barcode'}) {
200                 return failure("Volume in slot $opt_slot have barcode '$res->{'barcode'}, it is not '$opt_barcode'", $finished_cb);
201             } else {
202                 return failure("Volume in slot $opt_slot have no barcode", $finished_cb);
203             }
204         }
205         $dev = $res->{'device'};
206         $dev_ok = 1;
207         if ($dev->status & $DEVICE_STATUS_VOLUME_UNLABELED) {
208             if (!$dev->volume_header or $dev->volume_header->{'type'} == $F_EMPTY) {
209                 print "Found an empty tape.\n";
210             } else {
211                 # force is required for non-Amanda tapes
212                 print "Found a non-Amanda tape.\n";
213                 $dev_ok = 0 unless ($opt_force);
214             }
215         } elsif ($dev->status & $DEVICE_STATUS_VOLUME_ERROR) {
216             # it's OK to force through VOLUME_ERROR
217             print "Error reading volume label: " . $dev->error_or_status(), "\n";
218             $dev_ok = 0 unless ($opt_force);
219         } elsif ($dev->status != $DEVICE_STATUS_SUCCESS) {
220             # but anything else is fatal
221             print "Error reading volume label: " . $dev->error_or_status(), "\n";
222             $dev_ok = 0;
223         } else {
224             # this is a labeled Amanda tape
225             my $label = $dev->volume_label;
226             my $labelstr = getconf($CNF_LABELSTR);
227
228             if ($label !~ /$labelstr/) {
229                 print "Found label '$label', but it is not from configuration " .
230                     "'" . Amanda::Config::get_config_name() . "'.\n";
231                 $dev_ok = 0 unless ($opt_force);
232             } elsif ($tl->lookup_tapelabel($label)) {
233                 print "Volume with label '$label' is active and contains data from this configuration.\n";
234                 if ($opt_force) {
235                     # if -f, then the user should clean things up..
236                     print "Consider using 'amrmtape' to remove volume '$label' from the catalog.\n";
237                     # note that we don't run amrmtape automatically, as it could result in data loss when
238                     # multiple volumes have (perhaps accidentally) the same label
239                 } else {
240                     $dev_ok = 0
241                 }
242             } else {
243                 print "Found Amanda volume '$label'.\n";
244             }
245         }
246
247         $res->get_meta_label(finished_cb => $steps->{'got_meta'});
248     };
249
250     step got_meta => sub {
251         my ($err, $meta) = @_;
252
253         if (defined $meta && defined $opt_meta && $meta ne $opt_meta) {
254             return failure();
255         }
256         $meta = $opt_meta if !defined $meta;
257         ($meta, my $merr) = $res->make_new_meta_label() if !defined $meta;
258         if (defined $merr) {
259             return failure($merr, $finished_cb);
260         }
261         $opt_meta = $meta;
262
263         my $label = $opt_label;
264         if (!defined($label)) {
265             ($label, my $lerr) = $res->make_new_tape_label(meta => $meta);
266             if (defined $lerr) {
267                 return failure($lerr, $finished_cb);
268             }
269         }
270
271         if ($dev_ok) {
272             print "Writing label '$label'...\n";
273
274             if (!$dev->start($ACCESS_WRITE, $label, "X")) {
275                 return failure("Error writing label: " . $dev->error_or_status(), $finished_cb);
276             } elsif (!$dev->finish()) {
277                 return failure("Error finishing device: " . $dev->error_or_status(), $finished_cb);
278             }
279
280             print "Checking label...\n";
281             my $status = $dev->read_label();
282             if ($status != $DEVICE_STATUS_SUCCESS) {
283                 return failure("Checking the tape label failed: " . $dev->error_or_status(),
284                         $finished_cb);
285             } elsif (!$dev->volume_label) {
286                 return failure("No label found.", $finished_cb);
287             } elsif ($dev->volume_label ne $label) {
288                 my $got = $dev->volume_label;
289                 return failure("Read back a different label: got '$got', but expected '$label'",
290                         $finished_cb);
291             } elsif ($dev->volume_time ne "X") {
292                 my $got = $dev->volume_time;
293                 return failure("Read back a different timetstamp: got '$got', but expected 'X'",
294                         $finished_cb);
295             }
296
297             # update the tapelist
298             $tl->reload(1);
299             $tl->remove_tapelabel($label);
300             $tl->add_tapelabel("0", $label, undef, 1, $meta, $res->{'barcode'});
301             $tl->write();
302
303             print "Success!\n";
304
305             # notify the changer
306             $res->set_label(label => $label, finished_cb => $steps->{'set_meta_label'});
307         } else {
308             return failure("Not writing label.", $finished_cb);
309         }
310     };
311
312     step set_meta_label => sub {
313         my ($gerr) = @_;
314
315         if ($opt_meta) {
316             return $res->set_meta_label(meta => $opt_meta,
317                                         finished_cb => $steps->{'labeled'});
318         } else {
319             return $steps->{'labeled'}->();
320         }
321     };
322
323     step labeled => sub {
324         my ($err) = @_;
325         $gerr = $err if !$gerr;
326
327         $res->release(finished_cb => $steps->{'released'});
328     };
329
330     step released => sub {
331         my ($err) = @_;
332         return failure($gerr, $finished_cb) if $gerr;
333         return failure($err, $finished_cb) if $err;
334
335         $finished_cb->();
336     };
337
338     step assign => sub {
339         my $tle;
340         $tle = $tl->lookup_tapelabel($opt_label);
341         if (defined $tle) {
342             my $meta = $opt_meta;
343             if (defined $meta) {
344                 if (defined($tle->{'meta'}) && $meta ne $tle->{'meta'} &&
345                     !$opt_force) {
346                     return failure("Can't change meta-label with --force, old meta-label is '$tle->{'meta'}'");
347                 }
348             } else {
349                 $meta = $tle->{'meta'};
350             }
351             my $barcode = $opt_barcode;
352             if (defined $barcode) {
353                 if (defined($tle->{'barcode'}) &&
354                     $barcode ne $tle->{'barcode'} &&
355                     !$opt_force) {
356                     return failure("Can't change barcode with --force, old barcode is '$tle->{'barcode'}'");
357                 }
358             } else {
359                 $barcode = $tle->{'barcode'};
360             }
361
362             $tl->reload(1);
363             $tl->remove_tapelabel($opt_label);
364             $tl->add_tapelabel($tle->{'datestamp'}, $tle->{'label'},
365                                $tle->{'comment'}, $tle->{'reuse'}, $meta,
366                                $barcode);
367             $tl->write();
368         } else {
369             return failure("Label '$opt_label' is not in the tapelist file", $finished_cb);
370         }
371
372         $chg->inventory(inventory_cb => $steps->{'assign_inventory'});
373     };
374
375     step assign_inventory => sub {
376         my ($err, $inv) = @_;
377
378         if ($err) {
379             return $finished_cb->() if $err->notimpl;
380             return failure($err, $finished_cb);
381         }
382
383         for my $sl (@$inv) {
384             if (defined $sl->{'label'} && $sl->{'label'} eq $opt_label) {
385                 return $chg->set_meta_label(meta => $opt_meta,
386                                             slot => $sl->{'slot'},
387                                             finished_cb => $steps->{'done'});
388             }
389         }
390         $finished_cb->();
391     };
392
393     step done => sub {
394         $finished_cb->();
395     }
396 }
397
398 main(\&Amanda::MainLoop::quit);
399 Amanda::MainLoop::run();
400 Amanda::Util::finish_application();
401 exit($exit_status);