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 my $opts_ok = GetOptions(
111 "changer=s" => \$changer_name,
112 "cleanup" => \$cleanup,
113 "dryrun|n" => \$dry_run,
116 "keep-label" => \$keep_label,
117 'o=s' => sub { add_config_override_opt( $config_overrides, $_[1] ); },
118 "quiet|q" => sub { undef $verbose; },
119 "verbose|v" => \$verbose,
122 unless ($opts_ok && scalar(@ARGV) == 2) {
123 unless (scalar(@ARGV) == 2) {
124 print STDERR "Specify a configuration and label.\n";
135 my ($config_name, $label) = @ARGV;
137 set_config_overrides($config_overrides);
138 my $cfg_ok = config_init( $CONFIG_INIT_EXPLICIT_NAME, $config_name );
140 my ($cfgerr_level, @cfgerr_errors) = config_errors();
141 if ($cfgerr_level >= $CFGERR_WARNINGS) {
142 config_print_errors();
143 if ($cfgerr_level >= $CFGERR_ERRORS) {
144 die "Errors processing config file";
148 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
149 $logdir = config_dir_relative(getconf($CNF_LOGDIR));
150 $log_file = "$logdir/log";
153 # Check for log file existance
155 `amcleanup -p $config_name`;
160 open(LOG, $log_file);
161 my $info_line = <LOG>;
163 $info_line =~ /^INFO (.*) .* pid .*$/;
164 my $process_name = $1;
165 print "$process_name is running, or you must run amcleanup\n";
170 # amadmin may later try to load this and will die if it has errors
171 # load it now to catch the problem sooner (before we might erase data)
172 my $diskfile = config_dir_relative(getconf($CNF_DISKFILE));
173 $cfgerr_level = Amanda::Disklist::read_disklist('filename' => $diskfile);
174 if ($cfgerr_level >= $CFGERR_ERRORS) {
175 die "Errors processing disklist";
179 my $tapelist_file = config_dir_relative(getconf($CNF_TAPELIST));
180 my $tapelist = Amanda::Tapelist->new($tapelist_file, !$dry_run);
182 die "Could not read the tapelist";
185 my $t = $tapelist->lookup_tapelabel($label);
187 $t->{'datestamp'} = 0 if $t;
188 } elsif (!defined $t) {
189 print "label '$label' not found in $tapelist_file\n";
192 $tapelist->remove_tapelabel($label);
195 #take a copy in case we roolback
196 my $backup_tapelist_file = dirname($tapelist_file) . "-backup-amrmtape-" . time();
197 if (-x $tapelist_file) {
198 unless (copy($tapelist_file, $backup_tapelist_file)) {
199 die "Failed to copy/backup $tapelist_file to $backup_tapelist_file";
207 my $tmp_curinfo_file = "$AMANDA_TMPDIR/curinfo-amrmtape-" . time();
208 unless (open(AMADMIN, "$amadmin $config_name export |")) {
209 die "Failed to execute $amadmin: $! $?";
211 open(CURINFO, ">$tmp_curinfo_file");
214 print CURINFO "$_[0]";
220 while(my $line = <AMADMIN>) {
221 my @parts = split(/\s+/, $line);
222 if ($parts[0] =~ /^CURINFO|#|(?:command|last_level|consecutive_runs|(?:full|incr)-(?:rate|comp)):$/) {
224 } elsif ($parts[0] eq 'host:') {
227 } elsif ($parts[0] eq 'disk:') {
230 } elsif ($parts[0] eq 'history:') {
232 } elsif ($line eq "//\n") {
235 } elsif ($parts[0] eq 'stats:') {
236 if (scalar(@parts) < 6 || scalar(@parts) > 8) {
237 die "unexpected number of fields in \"stats\" entry for $host:$disk\n\t$line";
239 my $level = $parts[1];
240 my $cur_label = $parts[7];
241 if ($cur_label eq $label) {
242 $dead_level = $level;
243 vlog "Discarding Host: $host, Disk: $disk, Level: $level\n";
244 } elsif ( $level > $dead_level ) {
245 vlog "Discarding Host: $host, Disk: $disk, Level: $level\n";
250 die "Error: unrecognized line of input:\n\t$line";
254 my $rollback_from_curinfo = sub {
255 unlink $tmp_curinfo_file;
256 return if $keep_label;
257 unless (move($backup_tapelist_file, $tapelist_file)) {
258 printf STDERR "Failed to rollback new tapelist.\n";
264 unless (close AMADMIN) {
265 $rollback_from_curinfo->();
266 die "$amadmin exited with non-zero while exporting: $! $?";
270 if (system("$amadmin $config_name import < $tmp_curinfo_file")) {
271 $rollback_from_curinfo->();
272 die "$amadmin exited with non-zero while importing: $! $?";
276 unlink $tmp_curinfo_file;
277 unlink $backup_tapelist_file;
279 if ($cleanup && !$dry_run) {
280 if (system($amtrmlog, $config_name)) {
281 die "$amtrmlog exited with non-zero while scrubbing logs: $! $?";
283 if (system($amtrmidx, $config_name)) {
284 die "$amtrmidx exited with non-zero while scrubbing indexes: $! $?";
288 Amanda::MainLoop::quit();
291 my $erase_volume = make_cb('erase_volume' => sub {
295 open(LOG, ">$log_file");
296 print LOG "INFO amrmtape amrmtape pid $$\n";
298 my $chg = Amanda::Changer->new($changer_name);
302 my ($err, $resv) = @_;
305 my $dev = $resv->{'device'};
306 die "Can not erase $label because the device doesn't support this feature"
307 unless $dev->property_get('full_deletion');
310 or die "Failed to erase volume";
313 # label the tape with the same label it had
315 $dev->start($ACCESS_WRITE, $label, undef)
316 or die "Failed to write tape label";
320 $resv->release(finished_cb => sub {
334 Amanda::MainLoop::run();
336 if ($log_created == 1) {
341 Amanda::Util::finish_application();