3 # Copyright (c) 2008-2012 Zmanda, Inc. All Rights Reserved.
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 # Contact information: Zmanda Inc, 465 S. Mathilda Ave., Suite 300
20 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
22 use lib '@amperldir@';
26 use Amanda::Config qw( :init :getconf config_print_errors
27 config_dir_relative new_config_overrides add_config_override);
29 use Amanda::Device qw( :constants );
30 use Amanda::Debug qw( :logging );
35 use Amanda::Util qw( :constants );
40 my $amadmin = "$sbindir/amadmin";
41 my $amtrmidx = "$amlibexecdir/amtrmidx";
42 my $amtrmlog = "$amlibexecdir/amtrmlog";
54 $0 [-n] [-v] [-q] [-d] [config-overwrites] <config> <label>
55 \t--changer changer-name
56 \t\tSpecify the name of the changer to use (for --erase).
58 \t\tRemove indexes and logs immediately
60 \t\tDo nothing to original files, leave new ones in database directory.
62 \t\tErase the media, if possible
64 \t\tDisplay this message.
66 \t\tDo not remove label from the tapelist
68 \t\tQuiet, opposite of -v.
70 \t\tVerbose, list backups of hosts and disks that are being discarded.
72 This program allows you to invalidate the contents of an existing
73 backup tape within the Amanda current tape database. This is meant as
74 a recovery mecanism for when a good backup is damaged either by faulty
75 hardware or user error, i.e. the tape is eaten by the tape drive, or
76 the tape has been overwritten.
81 foreach my $msg (@_) {
83 print "$0: $msg\n" if $verbose;
87 Amanda::Util::setup_application("amrmtape", "server", $CONTEXT_CMDLINE);
89 my $config_overrides = new_config_overrides( scalar(@ARGV) + 1 );
91 debug("Arguments: " . join(' ', @ARGV));
92 Getopt::Long::Configure(qw{ bundling });
93 my $opts_ok = GetOptions(
94 'version' => \&Amanda::Util::version_opt,
95 "changer=s" => \$changer_name,
96 "cleanup" => \$cleanup,
97 "dryrun|n" => \$dry_run,
100 "keep-label" => \$keep_label,
101 'o=s' => sub { add_config_override_opt( $config_overrides, $_[1] ); },
102 "quiet|q" => sub { undef $verbose; },
103 "verbose|v" => \$verbose,
106 unless ($opts_ok && scalar(@ARGV) == 2) {
107 unless (scalar(@ARGV) == 2) {
108 print STDERR "Specify a configuration and label.\n";
119 my ($config_name, $label) = @ARGV;
121 set_config_overrides($config_overrides);
122 my $cfg_ok = config_init( $CONFIG_INIT_EXPLICIT_NAME, $config_name );
124 my ($cfgerr_level, @cfgerr_errors) = config_errors();
125 if ($cfgerr_level >= $CFGERR_WARNINGS) {
126 config_print_errors();
127 if ($cfgerr_level >= $CFGERR_ERRORS) {
128 die "Errors processing config file";
132 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
134 # amadmin may later try to load this and will die if it has errors
135 # load it now to catch the problem sooner (before we might erase data)
136 my $diskfile = config_dir_relative(getconf($CNF_DISKFILE));
137 $cfgerr_level = Amanda::Disklist::read_disklist('filename' => $diskfile);
138 if ($cfgerr_level >= $CFGERR_ERRORS) {
139 die "Errors processing disklist";
142 my $tapelist_file = config_dir_relative(getconf($CNF_TAPELIST));
143 my $tapelist = Amanda::Tapelist->new($tapelist_file, !$dry_run);
145 die "Could not read the tapelist";
150 my $t = $tapelist->lookup_tapelabel($label);
152 $t->{'datestamp'} = 0 if $t;
153 } elsif (!defined $t) {
154 print "label '$label' not found in $tapelist_file\n";
157 $tapelist->remove_tapelabel($label);
160 #take a copy in case we roolback
161 my $backup_tapelist_file = dirname($tapelist_file) . "-backup-amrmtape-" . time();
162 if (-x $tapelist_file) {
163 unless (copy($tapelist_file, $backup_tapelist_file)) {
164 die "Failed to copy/backup $tapelist_file to $backup_tapelist_file";
172 my $tmp_curinfo_file = "$AMANDA_TMPDIR/curinfo-amrmtape-" . time() . "-" . $$;
173 unless (open(AMADMIN, "$amadmin $config_name export |")) {
174 die "Failed to execute $amadmin: $! $?";
176 open(CURINFO, ">$tmp_curinfo_file") or
177 die "Failed to open $tmp_curinfo_file for writing: $! $?";
180 print CURINFO "$_[0]";
186 while(my $line = <AMADMIN>) {
187 my @parts = split(/\s+/, $line);
188 if ($parts[0] =~ /^CURINFO|#|(?:command|last_level|consecutive_runs|(?:full|incr)-(?:rate|comp)):$/) {
190 } elsif ($parts[0] eq 'host:') {
193 } elsif ($parts[0] eq 'disk:') {
196 } elsif ($parts[0] eq 'history:') {
198 } elsif ($line eq "//\n") {
201 } elsif ($parts[0] eq 'stats:') {
202 if (scalar(@parts) < 6 || scalar(@parts) > 8) {
203 die "unexpected number of fields in \"stats\" entry for $host:$disk\n\t$line";
205 my $level = $parts[1];
206 my $cur_label = $parts[7];
207 if (defined $cur_label and $cur_label eq $label) {
208 $dead_level = $level;
209 vlog "Discarding Host: $host, Disk: $disk, Level: $level\n";
210 } elsif ( $level > $dead_level ) {
211 vlog "Discarding Host: $host, Disk: $disk, Level: $level\n";
216 die "Error: unrecognized line of input:\n\t$line";
220 my $rollback_from_curinfo = sub {
221 unlink $tmp_curinfo_file;
222 return if $keep_label;
223 unless (move($backup_tapelist_file, $tapelist_file)) {
224 printf STDERR "Failed to rollback new tapelist.\n";
230 unless (close AMADMIN) {
231 $rollback_from_curinfo->();
232 die "$amadmin exited with non-zero while exporting: $! $?";
236 if (system("$amadmin $config_name import < $tmp_curinfo_file")) {
237 $rollback_from_curinfo->();
238 die "$amadmin exited with non-zero while importing: $! $?";
242 unlink $tmp_curinfo_file;
243 unlink $backup_tapelist_file;
245 if ($cleanup && !$dry_run) {
246 if (system($amtrmlog, $config_name)) {
247 die "$amtrmlog exited with non-zero while scrubbing logs: $! $?";
249 if (system($amtrmidx, $config_name)) {
250 die "$amtrmidx exited with non-zero while scrubbing indexes: $! $?";
254 Amanda::MainLoop::quit();
257 my $erase_volume = make_cb('erase_volume' => sub {
259 my $chg = Amanda::Changer->new($changer_name, tapelist => $tapelist);
263 my ($err, $resv) = @_;
266 my $dev = $resv->{'device'};
267 die "Can not erase $label because the device doesn't support this feature"
268 unless $dev->property_get('full_deletion');
270 my $rel_cb = make_cb('rel_cb' => sub {
271 $resv->release(finished_cb => sub {
283 or die "Failed to erase volume";
284 $resv->set_label(finished_cb => sub {
287 # label the tape with the same label it had
289 $dev->start($ACCESS_WRITE, $label, undef)
290 or die "Failed to write tape label";
291 return $resv->set_label(label => $label, finished_cb => $rel_cb);
307 Amanda::MainLoop::run();
309 Amanda::Util::finish_application();