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