2 # Copyright (c) 2009, 2010 Zmanda, Inc. All Rights Reserved.
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.
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
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
17 # Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
18 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
20 use lib '@amperldir@';
27 use Amanda::Device qw( :constants );
28 use Amanda::Debug qw( :logging );
29 use Amanda::Config qw( :init :getconf config_dir_relative );
30 use Amanda::Util qw( :constants );
32 use Amanda::Constants;
34 use Amanda::Taper::Scan;
35 use Amanda::Recovery::Scan;
36 use Amanda::Interactive;
46 my ($finished_cb) = @_;
47 $finished_cb ||= sub { exit(1); };
50 Usage: amtape <conf> <command> {<args>} [-o configoption]*
53 local $Text::Wrap::columns = 80 - 20;
54 for my $subcmd (sort keys %subcommands) {
55 my ($syntax, $descr, $code) = @{$subcommands{$subcmd}};
56 $descr = wrap('', ' ' x 20, $descr);
57 printf(" %-15s %s\n", $syntax, $descr);
63 sub subcommand($$$&) {
64 my ($subcmd, $syntax, $descr, $code) = @_;
66 $subcommands{$subcmd} = [ $syntax, $descr, make_cb($subcmd => $code) ];
69 sub invoke_subcommand {
70 my ($subcmd, $finished_cb, @args) = @_;
71 die "invalid subcommand $subcmd" unless exists $subcommands{$subcmd};
73 $subcommands{$subcmd}->[2]->($finished_cb, @args);
79 subcommand("usage", "usage", "this message",
81 my ($finished_cb, @args) = @_;
86 subcommand("reset", "reset", "reset changer to known state",
88 my ($finished_cb, @args) = @_;
90 my $chg = load_changer($finished_cb) or return;
92 $chg->reset(finished_cb => sub {
94 return failure($err, $finished_cb) if $err;
96 print STDERR "changer is reset\n";
101 subcommand("eject", "eject [<drive>]", "eject the volume in the specified drive",
103 my ($finished_cb, @args) = @_;
106 my $chg = load_changer($finished_cb) or return;
109 @drive_args = (drive => shift @args);
111 $chg->eject(@drive_args,
114 return failure($err, $finished_cb) if $err;
116 print STDERR "drive ejected\n";
121 subcommand("clean", "clean [<drive>]", "clean a drive in the changer",
123 my ($finished_cb, @args) = @_;
126 my $chg = load_changer($finished_cb) or return;
129 @drive_args = (drive => shift @args);
130 } elsif (@args != 0) {
131 return usage($finished_cb);
134 $chg->clean(@drive_args,
137 return failure($err, $finished_cb) if $err;
139 print STDERR "drive cleaned\n";
144 subcommand("show", "show", "scan all slots in the changer, starting with the current slot",
146 my ($finished_cb, @args) = @_;
151 my $steps = define_steps
152 cb_ref => \$finished_cb;
155 return usage($finished_cb);
158 my $chg = load_changer($finished_cb) or return;
161 $chg->info(info => [ 'num_slots' ], info_cb => $steps->{'info_cb'});
164 step info_cb => sub {
165 my ($err, %info) = @_;
166 return failure($err, $finished_cb) if $err;
168 print STDERR "amtape: scanning all $info{num_slots} slots in changer:\n";
170 $steps->{'load_current'}->();
173 step load_current => sub {
174 $chg->load(relative_slot => 'current', mode => "read", res_cb => $steps->{'loaded'});
178 my ($err, $res) = @_;
180 if ($err->notfound) {
181 # no more interesting slots
184 } elsif ($err->volinuse and defined $err->{'slot'}) {
185 $last_slot = $err->{'slot'};
187 return failure($err, $finished_cb) if $err;
190 $last_slot = $res->{'this_slot'};
193 $seen_slots{$last_slot} = 1;
196 my $dev = $res->{'device'};
197 my $st = $dev->read_label();
198 if ($st == $DEVICE_STATUS_SUCCESS) {
199 print STDERR sprintf("slot %3s: date %-14s label %s\n",
200 $last_slot, $dev->volume_time(),
201 $dev->volume_label());
203 return $res->set_label(label => $dev->volume_label(),
204 finished_cb => $steps->{'set_labeled'});
205 } elsif ($st == $DEVICE_STATUS_VOLUME_UNLABELED) {
206 print STDERR sprintf("slot %3s: unlabeled volume\n", $last_slot);
208 print STDERR sprintf("slot %3s: %s\n", $last_slot, $dev->error_or_status());
211 print STDERR sprintf("slot %3s: in use\n", $last_slot);
215 $res->release(finished_cb => $steps->{'released'});
217 $steps->{'released'}->();
221 step set_labeled => sub {
222 $gres->release(finished_cb => $steps->{'released'});
225 step released => sub {
226 $chg->load(relative_slot => 'next', slot => $last_slot,
227 except_slots => { %seen_slots }, res_cb => $steps->{'loaded'});
231 subcommand("inventory", "inventory", "show inventory of changer slots",
233 my ($finished_cb, @args) = @_;
235 my $chg = load_changer($finished_cb) or return;
238 return usage($finished_cb);
241 # TODO -- support an --xml option
243 my $inventory_cb = make_cb(inventory_cb => sub {
244 my ($err, $inv) = @_;
247 print STDERR "inventory not supported by this changer\n";
249 print STDERR "$err\n";
252 return $finished_cb->();
256 my $line = "slot $sl->{slot}:";
257 if (!defined($sl->{device_status}) && !defined($sl->{label})) {
258 $line .= " unknown state";
259 } elsif ($sl->{'status'} == Amanda::Changer::SLOT_EMPTY) {
262 if (defined $sl->{label}) {
263 $line .= " label $sl->{label}";
264 } elsif ($sl->{'device_status'} != $DEVICE_STATUS_SUCCESS) {
265 $line .= "device error";
266 } elsif ($sl->{'f_type'} != $Amanda::Header::F_TAPESTART) {
272 if ($sl->{'barcode'}) {
273 $line .= " barcode $sl->{barcode}";
275 if ($sl->{'reserved'}) {
276 $line .= " reserved";
278 if (defined $sl->{'loaded_in'}) {
279 $line .= " (in drive $sl->{'loaded_in'})";
281 if ($sl->{'import_export'}) {
282 $line .= " (import/export slot)";
285 # note that inventory goes to stdout
291 $chg->inventory(inventory_cb => $inventory_cb);
294 subcommand("current", "current", "load and show the contents of the current slot",
296 my ($finished_cb, @args) = @_;
298 return usage($finished_cb) if @args;
300 # alias for 'slot current'
301 return invoke_subcommand("slot", $finished_cb, "current");
304 subcommand("slot", "slot <slot>",
305 "load the volume in slot <slot>; <slot> can also be 'current', 'next', 'first', or 'last'",
307 my ($finished_cb, @args) = @_;
311 my $steps = define_steps
312 cb_ref => \$finished_cb;
314 # NOTE: the syntax of this subcommand precludes actual slots named
315 # 'current' or 'next' .. when we have a changer using such slot names,
316 # this subcommand will need to support a --literal flag
318 usage($finished_cb) unless (@args == 1);
319 my $slot = shift @args;
321 my $chg = load_changer($finished_cb) or return;
323 step get_slot => sub {
324 if ($slot eq 'current' or $slot eq 'next') {
325 @slotarg = (relative_slot => $slot);
326 } elsif ($slot eq 'first' or $slot eq 'last') {
327 return $chg->inventory(inventory_cb => $steps->{'inventory_cb'});
329 @slotarg = (slot => $slot);
332 $steps->{'do_load'}->();
335 step inventory_cb => sub {
336 my ($err, $inv) = @_;
338 if ($err->failed and $err->notimpl) {
339 return failed("This changer does not support special slot '$slot'");
345 if ($slot eq 'first') {
346 @slotarg = (slot => $inv->[0]->{'slot'});
348 @slotarg = (slot => $inv->[-1]->{'slot'});
351 $steps->{'do_load'}->();
354 step do_load => sub {
355 $chg->load(@slotarg, set_current => 1,
356 res_cb => $steps->{'done_load'});
359 step done_load => sub {
360 my ($err, $res) = @_;
361 return failure($err, $finished_cb) if ($err);
364 my $gotslot = $res->{'this_slot'};
365 print STDERR "changed to slot $gotslot\n";
367 if ($res->{device}->volume_label) {
369 $res->set_label(label => $res->{device}->volume_label(),
370 finished_cb => $steps->{'set_labeled'});
372 $res->release(finished_cb => $steps->{'released'});
376 step set_labeled => sub {
377 $gres->release(finished_cb => $steps->{'released'});
380 step released => sub {
382 return failure($err, $finished_cb) if ($err);
388 subcommand("label", "label <label>", "load the volume with label <label>",
390 my ($finished_cb, @args) = @_;
395 usage($finished_cb) unless (@args == 1);
396 my $label = shift @args;
398 my $steps = define_steps
399 cb_ref => \$finished_cb;
402 my $_user_msg_fn = sub {
405 if (exists($params{'scan_slot'})) {
406 print "slot $params{'slot'}:";
407 } elsif (exists($params{'slot_result'})) {
408 if (defined($params{'err'})) {
409 print " $params{'err'}\n";
410 } else { # res must be defined
411 my $res = $params{'res'};
412 my $dev = $res->{'device'};
413 if ($dev->status == $DEVICE_STATUS_SUCCESS) {
414 my $volume_label = $res->{device}->volume_label;
415 print " $volume_label\n";
417 my $errmsg = $res->{device}->error_or_status();
424 $inter = Amanda::Interactive->new(name => 'stdin');
425 $scan = Amanda::Recovery::Scan->new(interactive => $inter);
426 return failure("$scan", $finished_cb)
427 if ($scan->isa("Amanda::Changer::Error"));
429 $scan->find_volume(label => $label,
430 res_cb => $steps->{'done_load'},
431 user_msg_fn => $_user_msg_fn,
435 step done_load => sub {
436 my ($err, $res) = @_;
437 return failure($err, $finished_cb) if ($err);
439 my $gotslot = $res->{'this_slot'};
440 my $devname = $res->{'device'}->device_name;
442 print STDERR "label $label is now loaded from slot $gotslot\n";
444 if ($res->{device}->volume_label) {
446 $res->set_label(label => $res->{device}->volume_label(),
447 finished_cb => $steps->{'set_labeled'});
449 $res->release(finished_cb => $steps->{'released'});
453 step set_labeled => sub {
454 $gres->release(finished_cb => $steps->{'released'});
457 step released => sub {
459 return failure($err, $finished_cb) if ($err);
465 subcommand("taper", "taper", "perform the taperscan algorithm and display the result",
467 my ($finished_cb, @args) = @_;
469 my $taper_user_msg_fn = sub {
471 if (exists($params{'text'})) {
472 print STDERR "$params{'text'}\n";
473 } elsif (exists($params{'scan_slot'})) {
474 print STDERR "slot $params{'slot'}:";
475 } elsif (exists($params{'search_label'})) {
476 print STDERR "Searching for label '$params{'label'}':";
477 } elsif (exists($params{'slot_result'}) ||
478 exists($params{'search_result'})) {
479 if (defined($params{'err'})) {
480 if (exists($params{'search_result'}) &&
481 defined($params{'err'}->{'slot'})) {
482 print STDERR "slot $params{'err'}->{'slot'}:";
484 print STDERR " $params{'err'}\n";
485 } else { # res must be defined
486 my $res = $params{'res'};
487 my $dev = $res->{'device'};
488 if (exists($params{'search_result'})) {
489 print STDERR " found in slot $res->{'this_slot'}:";
491 if ($dev->status == $DEVICE_STATUS_SUCCESS) {
492 my $volume_label = $res->{device}->volume_label;
493 if ($params{'active'}) {
494 print STDERR " volume '$volume_label' is still active and cannot be overwritten\n";
495 } elsif ($params{'does_not_match_labelstr'}) {
496 print STDERR " volume '$volume_label' does not match labelstr '$params{'labelstr'}'\n";
497 } elsif ($params{'not_in_tapelist'}) {
498 print STDERR " volume '$volume_label' is not in the tapelist\n"
500 print STDERR " volume '$volume_label'\n";
502 } elsif ($dev->status & $DEVICE_STATUS_VOLUME_UNLABELED and
503 $dev->volume_header and
504 $dev->volume_header->{'type'} == $Amanda::Header::F_EMPTY) {
505 print STDERR " contains an empty volume\n";
506 } elsif ($dev->status & $DEVICE_STATUS_VOLUME_UNLABELED and
507 $dev->volume_header and
508 $dev->volume_header->{'type'} == $Amanda::Header::F_WEIRD) {
509 print STDERR " contains a non-Amanda volume; check and relabel it with 'amlabel -f'\n";
510 } elsif ($dev->status & $DEVICE_STATUS_VOLUME_ERROR) {
511 my $message = $dev->error_or_status();
512 print STDERR " can't read label: $message\n";
514 my $errmsg = $res->{device}->error_or_status();
515 print STDERR " $errmsg\n";
519 print STDERR "UNKNOWN\n";
523 usage($finished_cb) unless (@args == 0);
524 my $label = shift @args;
526 my $chg = load_changer($finished_cb) or return;
528 my $result_cb = make_cb(result_cb => sub {
529 my ($err, $res, $label, $mode) = @_;
530 return failure($err, $finished_cb) if $err;
532 my $modestr = ($mode == $ACCESS_APPEND)? "append" : "write";
533 my $slot = $res->{'this_slot'};
534 print STDERR "Will $modestr to volume $label in slot $slot.\n";
535 $res->release(finished_cb => sub {
543 my $taperscan = Amanda::Taper::Scan->new(changer => $chg);
545 result_cb => $result_cb,
546 user_msg_fn => $taper_user_msg_fn,
550 subcommand("update", "update [WHAT]", "update the changer's state; see changer docs for syntax of WHAT",
552 my ($finished_cb, @args) = @_;
555 my $chg = load_changer($finished_cb) or return;
558 @changed_args = (changed => shift @args);
560 $chg->update(@changed_args,
562 print STDERR "$_[0]\n";
566 return failure($err, $finished_cb) if $err;
568 print STDERR "update complete\n";
577 my ($finished_cb) = @_;
579 my $chg = Amanda::Changer->new();
580 return failure($chg, $finished_cb) if ($chg->isa("Amanda::Changer::Error"));
585 my ($msg, $finished_cb) = @_;
586 print STDERR "ERROR: $msg\n";
591 # show the slot contents in the old-fashioned format
595 printf STDERR "slot %3s: ", $res->{'this_slot'};
596 my $dev = $res->{'device'};
597 if ($dev->status != $DEVICE_STATUS_SUCCESS) {
598 print STDERR "Could not open device: "
599 . $dev->error_or_status() . "\n";
603 printf STDERR "time %-14s label %s\n", $dev->volume_time, $dev->volume_label;
609 Amanda::Util::setup_application("amtape", "server", $CONTEXT_CMDLINE);
611 my $config_overrides = new_config_overrides($#ARGV+1);
613 Getopt::Long::Configure(qw(bundling));
615 'help|usage|?' => \&usage,
616 'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); },
619 usage() if (@ARGV < 1);
621 my $config_name = shift @ARGV;
622 set_config_overrides($config_overrides);
623 config_init($CONFIG_INIT_EXPLICIT_NAME, $config_name);
624 my ($cfgerr_level, @cfgerr_errors) = config_errors();
625 if ($cfgerr_level >= $CFGERR_WARNINGS) {
626 config_print_errors();
627 if ($cfgerr_level >= $CFGERR_ERRORS) {
628 die("errors processing config file");
632 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
634 #make STDOUT not line buffered
635 my $previous_fh = select(STDOUT);
637 select($previous_fh);
640 my ($finished_cb) = @_;
642 my $subcmd = shift @ARGV;
643 usage($finished_cb) unless defined($subcmd) and exists ($subcommands{$subcmd});
644 invoke_subcommand($subcmd, $finished_cb, @ARGV);
646 main(\&Amanda::MainLoop::quit);
647 Amanda::MainLoop::run();
648 Amanda::Util::finish_application();