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::Constants; 35use Amanda::MainLoop; 36use Amanda::Taper::Scan; 37use Amanda::Recovery::Scan; 38use Amanda::Interactivity; 39use Amanda::Tapelist; 40 41my $exit_status = 0; 42my $tl; 43 44## 45# Subcommand handling 46 47my %subcommands; 48 49sub usage { 50 my ($finished_cb) = @_; 51 52 $finished_cb = sub { exit(1); } if (!$finished_cb or !(ref($finished_cb) eq "CODE")); 53 54 print STDERR <<EOF; 55Usage: amtape [-o configoption]* <conf> <command> {<args>} 56 Valid commands are: 57EOF 58 local $Text::Wrap::columns = 80 - 20; 59 for my $subcmd (sort keys %subcommands) { 60 my ($syntax, $descr, $code) = @{$subcommands{$subcmd}}; 61 $descr = wrap('', ' ' x 20, $descr); 62 printf(" %-15s %s\n", $syntax, $descr); 63 } 64 $exit_status = 1; 65 $finished_cb->(); 66} 67 68sub subcommand($$$&) { 69 my ($subcmd, $syntax, $descr, $code) = @_; 70 71 $subcommands{$subcmd} = [ $syntax, $descr, make_cb($subcmd => $code) ]; 72} 73 74sub invoke_subcommand { 75 my ($subcmd, $finished_cb, @args) = @_; 76 die "invalid subcommand $subcmd" unless exists $subcommands{$subcmd}; 77 78 $subcommands{$subcmd}->[2]->($finished_cb, @args); 79} 80 81## 82# subcommands 83 84subcommand("usage", "usage", "this message", 85sub { 86 my ($finished_cb, @args) = @_; 87 88 return usage($finished_cb); 89}); 90 91subcommand("reset", "reset", "reset changer to known state", 92sub { 93 my ($finished_cb, @args) = @_; 94 95 my $chg = load_changer($finished_cb) or return; 96 97 $chg->reset(finished_cb => sub { 98 my ($err) = @_; 99 $chg->quit(); 100 return failure($err, $finished_cb) if $err; 101 102 print STDERR "changer is reset\n"; 103 $finished_cb->(); 104 }); 105}); 106 107subcommand("eject", "eject [<drive>]", "eject the volume in the specified drive", 108sub { 109 my ($finished_cb, @args) = @_; 110 my @drive_args; 111 112 my $chg = load_changer($finished_cb) or return; 113 114 if (@args) { 115 @drive_args = (drive => shift @args); 116 } 117 $chg->eject(@drive_args, 118 finished_cb => sub { 119 my ($err) = @_; 120 $chg->quit(); 121 return failure($err, $finished_cb) if $err; 122 123 print STDERR "drive ejected\n"; 124 $finished_cb->(); 125 }); 126}); 127 128subcommand("clean", "clean [<drive>]", "clean a drive in the changer", 129sub { 130 my ($finished_cb, @args) = @_; 131 my @drive_args; 132 133 my $chg = load_changer($finished_cb) or return; 134 135 if (@args == 1) { 136 @drive_args = (drive => shift @args); 137 } elsif (@args != 0) { 138 return usage($finished_cb); 139 } 140 141 $chg->clean(@drive_args, 142 finished_cb => sub { 143 my ($err) = @_; 144 $chg->quit(); 145 return failure($err, $finished_cb) if $err; 146 147 print STDERR "drive cleaned\n"; 148 $finished_cb->(); 149 }); 150}); 151 152subcommand("show", "show [<slots>]", "scan all slots (or listed slots) in the changer, starting with the current slot", 153sub { 154 my ($finished_cb, @args) = @_; 155 my $last_slot; 156 my %seen_slots; 157 my $chg; 158 159 if (@args > 1) { 160 return usage($finished_cb); 161 } 162 163 my $what = $args[0]; 164 my @slots; 165 166 if (defined $what) { 167 my @what1 = split /,/, $what; 168 foreach my $what1 (@what1) { 169 if ($what1 =~ /^(\d*)-(\d*)$/) { 170 my $begin = $1; 171 my $end = $2; 172 $end = $begin if $begin > $end; 173 while ($begin <= $end) { 174 push @slots, $begin; 175 $begin++; 176 } 177 } else { 178 push @slots, $what1; 179 } 180 } 181 } 182 183 my $use_slots = @slots > 0; 184 185 $chg = load_changer($finished_cb) or return; 186 187 my $steps = define_steps 188 cb_ref => \$finished_cb, 189 finalize => sub { $chg->quit() if defined $chg }; 190 191 step start => sub { 192 $chg->info(info => [ 'num_slots' ], info_cb => $steps->{'info_cb'}); 193 }; 194 195 step info_cb => sub { 196 my ($err, %info) = @_; 197 return failure($err, $finished_cb) if $err; 198 199 if ($use_slots) { 200 my $slot = shift @slots; 201 $chg->load(slot => $slot, 202 mode => "read", 203 res_cb => $steps->{'loaded'}); 204 205 } else { 206 print STDERR "amtape: scanning all $info{num_slots} slots in changer:\n"; 207 208 $chg->load(relative_slot => 'current', 209 mode => "read", 210 res_cb => $steps->{'loaded'}); 211 } 212 }; 213 214 step loaded => sub { 215 my ($err, $res) = @_; 216 if ($err) { 217 if ($err->notfound) { 218 # no more interesting slots 219 $finished_cb->(); 220 return; 221 } elsif ($err->volinuse and defined $err->{'slot'}) { 222 $last_slot = $err->{'slot'}; 223 print STDERR sprintf("slot %3s: in use\n", $last_slot); 224 } elsif ($err->empty and defined $err->{'slot'}) { 225 $last_slot = $err->{'slot'}; 226 print STDERR sprintf("slot %3s: empty\n", $last_slot); 227 } elsif ($err->invalid and defined $err->{'slot'}) { 228 $last_slot = $err->{'slot'}; 229 print STDERR sprintf("slot %3s: %s\n", $last_slot, "$err"); 230 } else { 231 return failure($err, $finished_cb) if $err; 232 } 233 } else { 234 $last_slot = $res->{'this_slot'}; 235 } 236 237 $seen_slots{$last_slot} = 1; 238 239 if ($res) { 240 my $dev = $res->{'device'}; 241 my $st = $dev->read_label(); 242 if ($st == $DEVICE_STATUS_SUCCESS) { 243 print STDERR sprintf("slot %3s: date %-14s label %s\n", 244 $last_slot, $dev->volume_time(), 245 $dev->volume_label()); 246 } elsif ($st == $DEVICE_STATUS_VOLUME_UNLABELED) { 247 print STDERR sprintf("slot %3s: unlabeled volume\n", $last_slot); 248 } else { 249 print STDERR sprintf("slot %3s: %s\n", $last_slot, $dev->error_or_status()); 250 } 251 } 252 253 if ($res) { 254 $res->release(finished_cb => $steps->{'released'}); 255 } else { 256 $steps->{'released'}->(); 257 } 258 }; 259 260 step released => sub { 261 if ($use_slots) { 262 return $finished_cb->() if @slots == 0; 263 my $slot = shift @slots; 264 $chg->load(slot => $slot, 265 mode => "read", 266 res_cb => $steps->{'loaded'}); 267 268 } else { 269 $chg->load(relative_slot => 'next', 270 slot => $last_slot, 271 except_slots => { %seen_slots }, 272 res_cb => $steps->{'loaded'}); 273 } 274 }; 275}); 276 277subcommand("inventory", "inventory", "show inventory of changer slots", 278sub { 279 my ($finished_cb, @args) = @_; 280 281 my $chg = load_changer($finished_cb) or return; 282 283 if (@args != 0) { 284 return usage($finished_cb); 285 } 286 287 # TODO -- support an --xml option 288 289 my $inventory_cb = make_cb(inventory_cb => sub { 290 my ($err, $inv) = @_; 291 if ($err) { 292 if ($err->notimpl) { 293 if ($err->{'message'}) { 294 print STDERR "inventory not supported by this changer: $err->{'message'}\n"; 295 } else { 296 print STDERR "inventory not supported by this changer\n"; 297 } 298 } else { 299 print STDERR "$err\n"; 300 } 301 302 $chg->quit(); 303 return $finished_cb->(); 304 } 305 306 for my $sl (@$inv) { 307 my $line = "slot $sl->{slot}:"; 308 my $tle; 309 if ($sl->{'state'} == Amanda::Changer::SLOT_EMPTY) { 310 $line .= " empty"; 311 } elsif (!defined($sl->{device_status}) && !defined($sl->{label})) { 312 $line .= " unknown state"; 313 } else { 314 if (defined $sl->{label}) { 315 $line .= " label $sl->{label}"; 316 $tle = $tl->lookup_tapelabel($sl->{label}); 317 if (defined $tle) { 318 if ($tle->{'meta'}) { 319 $line .= " ($tle->{'meta'})"; 320 } 321 } 322 } elsif ($sl->{'device_status'} == $DEVICE_STATUS_VOLUME_UNLABELED) { 323 $line .= " blank"; 324 } elsif ($sl->{'device_status'} != $DEVICE_STATUS_SUCCESS) { 325 if (defined $sl->{'device_error'}) { 326 $line .= " " . $sl->{'device_error'}; 327 } else { 328 $line .= "device error"; 329 } 330 } elsif ($sl->{'f_type'} != $Amanda::Header::F_TAPESTART) { 331 $line .= " blank"; 332 } else { 333 $line .= " unknown"; 334 } 335 } 336 if ($sl->{'barcode'}) { 337 $line .= " barcode $sl->{barcode}"; 338 } 339 if ($sl->{'reserved'}) { 340 $line .= " reserved"; 341 } 342 if (defined $sl->{'loaded_in'}) { 343 $line .= " (in drive $sl->{'loaded_in'})"; 344 } 345 if ($sl->{'import_export'}) { 346 $line .= " (import/export slot)"; 347 } 348 if ($sl->{'current'}) { 349 $line .= " (current)"; 350 } 351 if (defined $tle) { 352 if (defined $sl->{'barcode'} and 353 defined $tle->{'barcode'} and 354 $sl->{'barcode'} ne $tle->{'barcode'}) { 355 $line .= " MISTMATCH barcode in tapelist: $tle->{'barcode'}"; 356 } 357 } 358 359 # note that inventory goes to stdout 360 print "$line\n"; 361 } 362 363 $chg->quit(); 364 $finished_cb->(); 365 }); 366 $chg->inventory(inventory_cb => $inventory_cb); 367}); 368 369subcommand("verify", "verify", "verify the changer is correctly configured", 370sub { 371 my ($finished_cb, @args) = @_; 372 373 my $chg = load_changer($finished_cb) or return; 374 375 if (@args != 0) { 376 return usage($finished_cb); 377 } 378 379 # TODO -- support an --xml option 380 381 my $verify_cb = make_cb(verify => sub { 382 my ($err, @results) = @_; 383 if ($err) { 384 if ($err->notimpl) { 385 if ($err->{'message'}) { 386 print STDERR "verify not supported by this changer: $err->{'message'}\n"; 387 } else { 388 print STDERR "verify not supported by this changer\n"; 389 } 390 } else { 391 print STDERR "$err\n"; 392 } 393 } else { 394 print STDERR join("\n", @results); 395 print STDERR "\n"; 396 } 397 $chg->quit(); 398 return $finished_cb->(); 399 }); 400 $chg->verify(finished_cb => $verify_cb); 401}); 402 403subcommand("current", "current", "load and show the contents of the current slot", 404sub { 405 my ($finished_cb, @args) = @_; 406 407 return usage($finished_cb) if @args; 408 409 # alias for 'slot current' 410 return invoke_subcommand("slot", $finished_cb, "current"); 411}); 412 413subcommand("slot", "slot <slot>", 414 "load the volume in slot <slot>; <slot> can also be 'current', 'next', 'first', or 'last'", 415sub { 416 my ($finished_cb, @args) = @_; 417 my @slotarg; 418 my $chg; 419 420 my $steps = define_steps 421 cb_ref => \$finished_cb, 422 finalize => sub { $chg->quit() if defined $chg }; 423 424 # NOTE: the syntax of this subcommand precludes actual slots named 425 # 'current' or 'next' .. when we have a changer using such slot names, 426 # this subcommand will need to support a --literal flag 427 428 return usage($finished_cb) unless (@args == 1); 429 my $slot = shift @args; 430 431 $chg = load_changer($finished_cb) or return; 432 433 step get_slot => sub { 434 if ($slot eq 'current' or $slot eq 'next') { 435 @slotarg = (relative_slot => $slot); 436 } elsif ($slot eq 'first' or $slot eq 'last') { 437 return $chg->inventory(inventory_cb => $steps->{'inventory_cb'}); 438 } else { 439 @slotarg = (slot => $slot); 440 } 441 442 $steps->{'do_load'}->(); 443 }; 444 445 step inventory_cb => sub { 446 my ($err, $inv) = @_; 447 if ($err) { 448 if ($err->failed and $err->notimpl) { 449 return failed("This changer does not support special slot '$slot'"); 450 } else { 451 return failed($err); 452 } 453 } 454 455 if ($slot eq 'first') { 456 @slotarg = (slot => $inv->[0]->{'slot'}); 457 } else { 458 @slotarg = (slot => $inv->[-1]->{'slot'}); 459 } 460 461 $steps->{'do_load'}->(); 462 }; 463 464 step do_load => sub { 465 $chg->load(@slotarg, set_current => 1, 466 res_cb => $steps->{'done_load'}); 467 }; 468 469 step done_load => sub { 470 my ($err, $res) = @_; 471 return failure($err, $finished_cb) if ($err); 472 473 show_slot($res); 474 my $gotslot = $res->{'this_slot'}; 475 print STDERR "changed to slot $gotslot\n"; 476 477 $res->release(finished_cb => $steps->{'released'}); 478 }; 479 480 step released => sub { 481 my ($err) = @_; 482 return failure($err, $finished_cb) if ($err); 483 484 $finished_cb->(); 485 }; 486}); 487 488subcommand("label", "label <label>", "load the volume with label <label>", 489sub { 490 my ($finished_cb, @args) = @_; 491 my $interactivity; 492 my $scan; 493 my $chg; 494 495 return usage($finished_cb) unless (@args == 1); 496 my $label = shift @args; 497 498 my $steps = define_steps 499 cb_ref => \$finished_cb, 500 finalize => sub { $scan->quit() if defined $scan; 501 $chg->quit() if defined $chg }; 502 503 step start => sub { 504 my $_user_msg_fn = sub { 505 my %params = @_; 506 507 if (exists($params{'scan_slot'})) { 508 print "slot $params{'slot'}:"; 509 } elsif (exists($params{'slot_result'})) { 510 if (defined($params{'err'})) { 511 print " $params{'err'}\n"; 512 } else { # res must be defined 513 my $res = $params{'res'}; 514 my $dev = $res->{'device'}; 515 if ($dev->status == $DEVICE_STATUS_SUCCESS) { 516 my $volume_label = $res->{device}->volume_label; 517 print " $volume_label\n"; 518 } else { 519 my $errmsg = $res->{device}->error_or_status(); 520 print " $errmsg\n"; 521 } 522 } 523 } 524 }; 525 526 $interactivity = Amanda::Interactivity->new(name => 'stdin'); 527 $chg = load_changer($finished_cb) or return; 528 $scan = Amanda::Recovery::Scan->new(chg => $chg, 529 interactivity => $interactivity); 530 return failure("$scan", $finished_cb) 531 if ($scan->isa("Amanda::Changer::Error")); 532 533 $scan->find_volume(label => $label, 534 res_cb => $steps->{'done_load'}, 535 user_msg_fn => $_user_msg_fn, 536 set_current => 1); 537 }; 538 539 step done_load => sub { 540 my ($err, $res) = @_; 541 return failure($err, $finished_cb) if ($err); 542 543 my $gotslot = $res->{'this_slot'}; 544 my $devname = $res->{'device'}->device_name; 545 show_slot($res); 546 print STDERR "label $label is now loaded from slot $gotslot\n"; 547 548 $res->release(finished_cb => $steps->{'released'}); 549 }; 550 551 step released => sub { 552 my ($err) = @_; 553 return failure($err, $finished_cb) if ($err); 554 555 $finished_cb->(); 556 }; 557}); 558 559subcommand("taper", "taper", "perform the taperscan algorithm and display the result", 560sub { 561 my ($finished_cb, @args) = @_; 562 563 my $taper_user_msg_fn = sub { 564 my %params = @_; 565 if (exists($params{'text'})) { 566 print STDERR "$params{'text'}\n"; 567 } elsif (exists($params{'scan_slot'})) { 568 print STDERR "slot $params{'slot'}:"; 569 } elsif (exists($params{'search_label'})) { 570 print STDERR "Searching for label '$params{'label'}':"; 571 } elsif (exists($params{'slot_result'}) || 572 exists($params{'search_result'})) { 573 if (defined($params{'err'})) { 574 if (exists($params{'search_result'}) && 575 defined($params{'err'}->{'slot'})) { 576 print STDERR "slot $params{'err'}->{'slot'}:"; 577 } 578 print STDERR " $params{'err'}\n"; 579 } elsif ($params{'res'}) { 580 my $res = $params{'res'}; 581 my $dev = $res->{'device'}; 582 if (exists($params{'search_result'})) { 583 print STDERR " found in slot $res->{'this_slot'}:"; 584 } 585 if ($dev->status == $DEVICE_STATUS_SUCCESS) { 586 my $volume_label = $res->{device}->volume_label; 587 if ($params{'active'}) { 588 print STDERR " volume '$volume_label' is still active and cannot be overwritten\n"; 589 } elsif ($params{'does_not_match_labelstr'}) { 590 print STDERR " volume '$volume_label' does not match labelstr '$params{'labelstr'}'\n"; 591 } elsif ($params{'not_in_tapelist'}) { 592 print STDERR " volume '$volume_label' is not in the tapelist\n"; 593 } elsif ($params{'relabeled'}) { 594 print STDERR " volume '$volume_label' from another config will be relabeled\n"; 595 } else { 596 print STDERR " volume '$volume_label'\n"; 597 } 598 } elsif ($dev->status & $DEVICE_STATUS_VOLUME_UNLABELED and 599 $dev->volume_header and 600 $dev->volume_header->{'type'} == $Amanda::Header::F_EMPTY) { 601 print STDERR " contains an empty volume\n"; 602 } elsif ($dev->status & $DEVICE_STATUS_VOLUME_UNLABELED and 603 $dev->volume_header and 604 $dev->volume_header->{'type'} == $Amanda::Header::F_WEIRD) { 605 my $autolabel = getconf($CNF_AUTOLABEL); 606 if ($autolabel->{'non_amanda'}) { 607 print STDERR " contains a non-Amanda volume\n"; 608 } else { 609 print STDERR " contains a non-Amanda volume; check and relabel it with 'amlabel -f'\n"; 610 } 611 } elsif ($dev->status & $DEVICE_STATUS_VOLUME_ERROR) { 612 my $message = $dev->error_or_status(); 613 print STDERR " can't read label: $message\n"; 614 } else { 615 my $errmsg = $res->{device}->error_or_status(); 616 print STDERR " $errmsg\n"; 617 } 618 } 619 } else { 620 print STDERR "UNKNOWN\n"; 621 } 622 }; 623 624 return usage($finished_cb) unless (@args == 0); 625 my $label = shift @args; 626 627 my $chg = load_changer($finished_cb) or return; 628 my $interactivity = Amanda::Interactivity->new(name => 'tty'); 629 my $scan_name = getconf($CNF_TAPERSCAN); 630 my $taperscan = Amanda::Taper::Scan->new(algorithm => $scan_name, 631 changer => $chg, 632 tapelist => $tl); 633 634 my $result_cb = make_cb(result_cb => sub { 635 my ($err, $res, $label, $mode) = @_; 636 if ($err) { 637 if ($res) { 638 $res->release(finished_cb => sub { 639 $taperscan->quit() if defined $taperscan; 640 return failure($err, $finished_cb); 641 }); 642 return; 643 } else { 644 $taperscan->quit() if defined $taperscan; 645 return failure($err, $finished_cb); 646 } 647 } 648 649 my $modestr = ($mode == $ACCESS_APPEND)? "append" : "write"; 650 my $slot = $res->{'this_slot'}; 651 if (defined $res->{'device'} and defined $res->{'device'}->volume_label() and $res->{'device'}->volume_label() eq $label) { 652 print STDERR "Will $modestr to volume '$label' in slot $slot.\n"; 653 } elsif (defined $res->{'device'} and defined $res->{'device'}->volume_label()) { 654 print STDERR "Will $modestr label '$label' to '" . $res->{'device'}->volume_label() . "' labelled volume in slot $slot.\n"; 655 } else { 656 my $header = $res->{'device'}->volume_header(); 657 if ($header->{'type'} == $Amanda::Header::F_WEIRD) { 658 print STDERR "Will $modestr label '$label' to non-Amanda volume in slot $slot.\n"; 659 } else { 660 print STDERR "Will $modestr label '$label' to new volume in slot $slot.\n"; 661 } 662 } 663 $res->release(finished_cb => sub { 664 my ($err) = @_; 665 die "$err" if $err; 666 667 $taperscan->quit() if defined $taperscan; 668 $finished_cb->(); 669 }); 670 }); 671 672 $taperscan->scan( 673 result_cb => $result_cb, 674 user_msg_fn => $taper_user_msg_fn, 675 ); 676}); 677 678subcommand("update", "update [WHAT]", "update the changer's state; see changer docs for syntax of WHAT", 679sub { 680 my ($finished_cb, @args) = @_; 681 my @changed_args; 682 683 my $chg = load_changer($finished_cb) or return; 684 685 if (@args) { 686 @changed_args = (changed => shift @args); 687 } 688 $chg->update(@changed_args, 689 user_msg_fn => sub { 690 print STDERR "$_[0]\n"; 691 }, 692 finished_cb => sub { 693 my ($err) = @_; 694 $chg->quit(); 695 return failure($err, $finished_cb) if $err; 696 697 print STDERR "update complete\n"; 698 $finished_cb->(); 699 }); 700}); 701 702## 703# Utilities 704 705sub load_changer { 706 my ($finished_cb) = @_; 707 708 my $chg = Amanda::Changer->new(undef, tapelist => $tl); 709 return failure($chg, $finished_cb) if ($chg->isa("Amanda::Changer::Error")); 710 return $chg; 711} 712 713sub failure { 714 my ($msg, $finished_cb) = @_; 715 if ($msg->isa("Amanda::Changer::Error") and defined $msg->{'slot'}) { 716 print STDERR "ERROR: Slot: $msg->{'slot'}: $msg\n"; 717 } else { 718 print STDERR "ERROR: $msg\n"; 719 } 720 $exit_status = 1; 721 $finished_cb->(); 722} 723 724# show the slot contents in the old-fashioned format 725sub show_slot { 726 my ($res) = @_; 727 728 printf STDERR "slot %3s: ", $res->{'this_slot'}; 729 my $dev = $res->{'device'}; 730 if ($dev->status != $DEVICE_STATUS_SUCCESS) { 731 print STDERR "Could not open device: " 732 . $dev->error_or_status() . "\n"; 733 return; 734 } 735 736 printf STDERR "time %-14s label %s\n", $dev->volume_time, $dev->volume_label; 737} 738 739## 740# main 741 742Amanda::Util::setup_application("amtape", "server", $CONTEXT_CMDLINE); 743 744my $config_overrides = new_config_overrides($#ARGV+1); 745 746debug("Arguments: " . join(' ', @ARGV)); 747Getopt::Long::Configure(qw(bundling)); 748GetOptions( 749 'version' => \&Amanda::Util::version_opt, 750 'help|usage|?' => \&usage, 751 'o=s' => sub { add_config_override_opt($config_overrides, $_[1]); }, 752) or usage(); 753 754usage() if (@ARGV < 1); 755 756my $config_name = shift @ARGV; 757set_config_overrides($config_overrides); 758config_init($CONFIG_INIT_EXPLICIT_NAME, $config_name); 759my ($cfgerr_level, @cfgerr_errors) = config_errors(); 760if ($cfgerr_level >= $CFGERR_WARNINGS) { 761 config_print_errors(); 762 if ($cfgerr_level >= $CFGERR_ERRORS) { 763 die("errors processing config file"); 764 } 765} 766 767Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER); 768 769my $tlf = Amanda::Config::config_dir_relative(getconf($CNF_TAPELIST)); 770$tl = Amanda::Tapelist->new($tlf); 771 772#make STDOUT not line buffered 773my $previous_fh = select(STDOUT); 774$| = 1; 775select($previous_fh); 776 777sub main { 778 my ($finished_cb) = @_; 779 780 my $steps = define_steps 781 cb_ref => \$finished_cb; 782 783 step start => sub { 784 my $subcmd = shift @ARGV; 785 return usage($finished_cb) unless defined($subcmd) and exists ($subcommands{$subcmd}); 786 invoke_subcommand($subcmd, $finished_cb, @ARGV); 787 } 788} 789 790main(\&Amanda::MainLoop::quit); 791Amanda::MainLoop::run(); 792Amanda::Util::finish_application(); 793exit($exit_status); 794