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