3 # Copyright (c) 2008,2009 Zmanda, Inc. All Rights Reserved.
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License version 2 as published
7 # by the Free Software Foundation.
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
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
18 # Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
19 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
21 use lib '@amperldir@';
25 use Amanda::Config qw( :init :getconf config_print_errors
26 config_dir_relative new_config_overrides add_config_override);
28 use Amanda::Device qw( :constants );
29 use Amanda::Debug qw( :logging );
34 use Amanda::Util qw( :constants );
39 my $amadmin = "$sbindir/amadmin";
40 my $amtrmidx = "$amlibexecdir/amtrmidx";
41 my $amtrmlog = "$amlibexecdir/amtrmlog";
55 if ($log_created == 1) {
60 $SIG{__DIE__} = \&die_handler;
63 if ($log_created == 1) {
69 $SIG{INT} = \&int_handler;
73 $0 [-n] [-v] [-q] [-d] [config-overwrites] <config> <label>
74 \t--changer changer-name
75 \t\tSpecify the name of the changer to use (for --erase).
77 \t\tRemove indexes and logs immediately
79 \t\tDo nothing to original files, leave new ones in database directory.
81 \t\tErase the media, if possible
83 \t\tDisplay this message.
85 \t\tDo not remove label from the tapelist
87 \t\tQuiet, opposite of -v.
89 \t\tVerbose, list backups of hosts and disks that are being discarded.
91 This program allows you to invalidate the contents of an existing
92 backup tape within the Amanda current tape database. This is meant as
93 a recovery mecanism for when a good backup is damaged either by faulty
94 hardware or user error, i.e. the tape is eaten by the tape drive, or
95 the tape has been overwritten.
100 foreach my $msg (@_) {
102 print "$0: $msg\n" if $verbose;
106 Amanda::Util::setup_application("amrmtape", "server", $CONTEXT_CMDLINE);
108 my $config_overrides = new_config_overrides( scalar(@ARGV) + 1 );
110 debug("Arguments: " . join(' ', @ARGV));
111 Getopt::Long::Configure(qw{ bundling });
112 my $opts_ok = GetOptions(
113 'version' => \&Amanda::Util::version_opt,
114 "changer=s" => \$changer_name,
115 "cleanup" => \$cleanup,
116 "dryrun|n" => \$dry_run,
119 "keep-label" => \$keep_label,
120 'o=s' => sub { add_config_override_opt( $config_overrides, $_[1] ); },
121 "quiet|q" => sub { undef $verbose; },
122 "verbose|v" => \$verbose,
125 unless ($opts_ok && scalar(@ARGV) == 2) {
126 unless (scalar(@ARGV) == 2) {
127 print STDERR "Specify a configuration and label.\n";
138 my ($config_name, $label) = @ARGV;
140 set_config_overrides($config_overrides);
141 my $cfg_ok = config_init( $CONFIG_INIT_EXPLICIT_NAME, $config_name );
143 my ($cfgerr_level, @cfgerr_errors) = config_errors();
144 if ($cfgerr_level >= $CFGERR_WARNINGS) {
145 config_print_errors();
146 if ($cfgerr_level >= $CFGERR_ERRORS) {
147 die "Errors processing config file";
151 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
152 $logdir = config_dir_relative(getconf($CNF_LOGDIR));
153 $log_file = "$logdir/log";
156 # Check for log file existance
158 `amcleanup -p $config_name`;
163 open(LOG, $log_file);
164 my $info_line = <LOG>;
166 $info_line =~ /^INFO (.*) .* pid .*$/;
167 my $process_name = $1;
168 print "$process_name is running, or you must run amcleanup\n";
173 # amadmin may later try to load this and will die if it has errors
174 # load it now to catch the problem sooner (before we might erase data)
175 my $diskfile = config_dir_relative(getconf($CNF_DISKFILE));
176 $cfgerr_level = Amanda::Disklist::read_disklist('filename' => $diskfile);
177 if ($cfgerr_level >= $CFGERR_ERRORS) {
178 die "Errors processing disklist";
181 my $tapelist_file = config_dir_relative(getconf($CNF_TAPELIST));
182 my $tapelist = Amanda::Tapelist->new($tapelist_file, !$dry_run);
184 die "Could not read the tapelist";
189 my $t = $tapelist->lookup_tapelabel($label);
191 $t->{'datestamp'} = 0 if $t;
192 } elsif (!defined $t) {
193 print "label '$label' not found in $tapelist_file\n";
196 $tapelist->remove_tapelabel($label);
199 #take a copy in case we roolback
200 my $backup_tapelist_file = dirname($tapelist_file) . "-backup-amrmtape-" . time();
201 if (-x $tapelist_file) {
202 unless (copy($tapelist_file, $backup_tapelist_file)) {
203 die "Failed to copy/backup $tapelist_file to $backup_tapelist_file";
211 my $tmp_curinfo_file = "$AMANDA_TMPDIR/curinfo-amrmtape-" . time();
212 unless (open(AMADMIN, "$amadmin $config_name export |")) {
213 die "Failed to execute $amadmin: $! $?";
215 open(CURINFO, ">$tmp_curinfo_file");
218 print CURINFO "$_[0]";
224 while(my $line = <AMADMIN>) {
225 my @parts = split(/\s+/, $line);
226 if ($parts[0] =~ /^CURINFO|#|(?:command|last_level|consecutive_runs|(?:full|incr)-(?:rate|comp)):$/) {
228 } elsif ($parts[0] eq 'host:') {
231 } elsif ($parts[0] eq 'disk:') {
234 } elsif ($parts[0] eq 'history:') {
236 } elsif ($line eq "//\n") {
239 } elsif ($parts[0] eq 'stats:') {
240 if (scalar(@parts) < 6 || scalar(@parts) > 8) {
241 die "unexpected number of fields in \"stats\" entry for $host:$disk\n\t$line";
243 my $level = $parts[1];
244 my $cur_label = $parts[7];
245 if (defined $cur_label and $cur_label eq $label) {
246 $dead_level = $level;
247 vlog "Discarding Host: $host, Disk: $disk, Level: $level\n";
248 } elsif ( $level > $dead_level ) {
249 vlog "Discarding Host: $host, Disk: $disk, Level: $level\n";
254 die "Error: unrecognized line of input:\n\t$line";
258 my $rollback_from_curinfo = sub {
259 unlink $tmp_curinfo_file;
260 return if $keep_label;
261 unless (move($backup_tapelist_file, $tapelist_file)) {
262 printf STDERR "Failed to rollback new tapelist.\n";
268 unless (close AMADMIN) {
269 $rollback_from_curinfo->();
270 die "$amadmin exited with non-zero while exporting: $! $?";
274 if (system("$amadmin $config_name import < $tmp_curinfo_file")) {
275 $rollback_from_curinfo->();
276 die "$amadmin exited with non-zero while importing: $! $?";
280 unlink $tmp_curinfo_file;
281 unlink $backup_tapelist_file;
283 if ($cleanup && !$dry_run) {
284 if (system($amtrmlog, $config_name)) {
285 die "$amtrmlog exited with non-zero while scrubbing logs: $! $?";
287 if (system($amtrmidx, $config_name)) {
288 die "$amtrmidx exited with non-zero while scrubbing indexes: $! $?";
292 Amanda::MainLoop::quit();
295 my $erase_volume = make_cb('erase_volume' => sub {
299 open(LOG, ">$log_file");
300 print LOG "INFO amrmtape amrmtape pid $$\n";
302 my $chg = Amanda::Changer->new($changer_name, tapelist => $tapelist);
306 my ($err, $resv) = @_;
309 my $dev = $resv->{'device'};
310 die "Can not erase $label because the device doesn't support this feature"
311 unless $dev->property_get('full_deletion');
313 my $rel_cb = make_cb('rel_cb' => sub {
314 $resv->release(finished_cb => sub {
326 or die "Failed to erase volume";
327 $resv->set_label(finished_cb => sub {
330 # label the tape with the same label it had
332 $dev->start($ACCESS_WRITE, $label, undef)
333 or die "Failed to write tape label";
334 return $resv->set_label(label => $label, finished_cb => $rel_cb);
350 Amanda::MainLoop::run();
352 if ($log_created == 1) {
357 Amanda::Util::finish_application();