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