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";
178 my $tapelist_file = config_dir_relative(getconf($CNF_TAPELIST));
179 my $tapelist = Amanda::Tapelist->new($tapelist_file, !$dry_run);
181 die "Could not read the tapelist";
186 my $t = $tapelist->lookup_tapelabel($label);
188 $t->{'datestamp'} = 0 if $t;
189 } elsif (!defined $t) {
190 print "label '$label' not found in $tapelist_file\n";
193 $tapelist->remove_tapelabel($label);
196 #take a copy in case we roolback
197 my $backup_tapelist_file = dirname($tapelist_file) . "-backup-amrmtape-" . time();
198 if (-x $tapelist_file) {
199 unless (copy($tapelist_file, $backup_tapelist_file)) {
200 die "Failed to copy/backup $tapelist_file to $backup_tapelist_file";
208 my $tmp_curinfo_file = "$AMANDA_TMPDIR/curinfo-amrmtape-" . time();
209 unless (open(AMADMIN, "$amadmin $config_name export |")) {
210 die "Failed to execute $amadmin: $! $?";
212 open(CURINFO, ">$tmp_curinfo_file");
215 print CURINFO "$_[0]";
221 while(my $line = <AMADMIN>) {
222 my @parts = split(/\s+/, $line);
223 if ($parts[0] =~ /^CURINFO|#|(?:command|last_level|consecutive_runs|(?:full|incr)-(?:rate|comp)):$/) {
225 } elsif ($parts[0] eq 'host:') {
228 } elsif ($parts[0] eq 'disk:') {
231 } elsif ($parts[0] eq 'history:') {
233 } elsif ($line eq "//\n") {
236 } elsif ($parts[0] eq 'stats:') {
237 if (scalar(@parts) < 6 || scalar(@parts) > 8) {
238 die "unexpected number of fields in \"stats\" entry for $host:$disk\n\t$line";
240 my $level = $parts[1];
241 my $cur_label = $parts[7];
242 if ($cur_label eq $label) {
243 $dead_level = $level;
244 vlog "Discarding Host: $host, Disk: $disk, Level: $level\n";
245 } elsif ( $level > $dead_level ) {
246 vlog "Discarding Host: $host, Disk: $disk, Level: $level\n";
251 die "Error: unrecognized line of input:\n\t$line";
255 my $rollback_from_curinfo = sub {
256 unlink $tmp_curinfo_file;
257 return if $keep_label;
258 unless (move($backup_tapelist_file, $tapelist_file)) {
259 printf STDERR "Failed to rollback new tapelist.\n";
265 unless (close AMADMIN) {
266 $rollback_from_curinfo->();
267 die "$amadmin exited with non-zero while exporting: $! $?";
271 if (system("$amadmin $config_name import < $tmp_curinfo_file")) {
272 $rollback_from_curinfo->();
273 die "$amadmin exited with non-zero while importing: $! $?";
277 unlink $tmp_curinfo_file;
278 unlink $backup_tapelist_file;
280 if ($cleanup && !$dry_run) {
281 if (system($amtrmlog, $config_name)) {
282 die "$amtrmlog exited with non-zero while scrubbing logs: $! $?";
284 if (system($amtrmidx, $config_name)) {
285 die "$amtrmidx exited with non-zero while scrubbing indexes: $! $?";
289 Amanda::MainLoop::quit();
292 my $erase_volume = make_cb('erase_volume' => sub {
296 open(LOG, ">$log_file");
297 print LOG "INFO amrmtape amrmtape pid $$\n";
299 my $chg = Amanda::Changer->new($changer_name, tapelist => $tapelist);
303 my ($err, $resv) = @_;
306 my $dev = $resv->{'device'};
307 die "Can not erase $label because the device doesn't support this feature"
308 unless $dev->property_get('full_deletion');
311 or die "Failed to erase volume";
314 # label the tape with the same label it had
316 $dev->start($ACCESS_WRITE, $label, undef)
317 or die "Failed to write tape label";
321 $resv->release(finished_cb => sub {
337 Amanda::MainLoop::run();
339 if ($log_created == 1) {
344 Amanda::Util::finish_application();