1#! @PERL@
2# Copyright (c) 2009-2013 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
21use lib '@amperldir@';
22use strict;
23use warnings;
24
25use File::Basename;
26use Getopt::Long;
27use Text::Wrap;
28
29use Amanda::Device qw( :constants );
30use Amanda::Debug qw( :logging );
31use Amanda::Config qw( :init :getconf config_dir_relative );
32use Amanda::Util qw( :constants );
33use Amanda::Changer;
34use Amanda::Header qw( :constants );
35use Amanda::MainLoop;
36use Amanda::Tapelist;
37
38my $exit_status = 0;
39
40##
41# Subcommand handling
42
43my %subcommands;
44
45sub 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
51Amanda::Util::setup_application("amlabel", "server", $CONTEXT_CMDLINE);
52
53my $config_overrides = new_config_overrides($#ARGV+1);
54my ($opt_force, $opt_config, $opt_slot, $opt_label);
55my ($opt_barcode, $opt_meta, $opt_assign);
56
57$opt_force = 0;
58$opt_barcode = undef;
59$opt_meta = undef;
60$opt_assign = undef;
61
62debug("Arguments: " . join(' ', @ARGV));
63Getopt::Long::Configure(qw(bundling));
64GetOptions(
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
75if ($opt_assign && (!$opt_meta and !$opt_barcode)) {
76    print STDERR "--assign require --barcode or --meta\n";
77    usage();
78}
79
80usage() if @ARGV == 0;
81$opt_config = $ARGV[0];
82if (@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
98set_config_overrides($config_overrides);
99config_init($CONFIG_INIT_EXPLICIT_NAME, $opt_config);
100my ($cfgerr_level, @cfgerr_errors) = config_errors();
101if ($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
109Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
110
111my ($tlf, $tl, $res);
112
113sub 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
127sub 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("Device meta '$meta' is not the same as the --meta argument '$opt_meta'", $finished_cb);
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'}'", $finished_cb);
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'}'", $finished_cb);
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
402main(\&Amanda::MainLoop::quit);
403Amanda::MainLoop::run();
404Amanda::Util::finish_application();
405exit($exit_status);
406