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