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 );
38 my $amadmin = "$sbindir/amadmin";
39 my $amtrmidx = "$amlibexecdir/amtrmidx";
40 my $amtrmlog = "$amlibexecdir/amtrmlog";
54 if ($log_created == 1) {
59 $SIG{__DIE__} = \&die_handler;
62 if ($log_created == 1) {
68 $SIG{INT} = \&int_handler;
72 $0 [-n] [-v] [-q] [-d] [config-overwrites] <config> <label>
73 \t--changer changer-name
74 \t\tSpecify the name of the changer to use (for --erase).
76 \t\tRemove indexes and logs immediately
78 \t\tDo nothing to original files, leave new ones in database directory.
80 \t\tErase the media, if possible
82 \t\tDisplay this message.
84 \t\tDo not remove label from the tapelist
86 \t\tQuiet, opposite of -v.
88 \t\tVerbose, list backups of hosts and disks that are being discarded.
90 This program allows you to invalidate the contents of an existing
91 backup tape within the Amanda current tape database. This is meant as
92 a recovery mecanism for when a good backup is damaged either by faulty
93 hardware or user error, i.e. the tape is eaten by the tape drive, or
94 the tape has been overwritten.
99 foreach my $msg (@_) {
101 print "$0: $msg\n" if $verbose;
105 Amanda::Util::setup_application("amrmtape", "server", $CONTEXT_CMDLINE);
107 my $config_overrides = new_config_overrides( scalar(@ARGV) + 1 );
109 my $opts_ok = GetOptions(
110 "changer=s" => \$changer_name,
111 "cleanup" => \$cleanup,
112 "dryrun|n" => \$dry_run,
115 "keep-label" => \$keep_label,
116 'o=s' => sub { add_config_override_opt( $config_overrides, $_[1] ); },
117 "quiet|q" => sub { undef $verbose; },
118 "verbose|v" => \$verbose,
121 unless ($opts_ok && scalar(@ARGV) == 2) {
122 unless (scalar(@ARGV) == 2) {
123 print STDERR "Specify a configuration and label.\n";
134 my ($config_name, $label) = @ARGV;
136 set_config_overrides($config_overrides);
137 my $cfg_ok = config_init( $CONFIG_INIT_EXPLICIT_NAME, $config_name );
139 my ($cfgerr_level, @cfgerr_errors) = config_errors();
140 if ($cfgerr_level >= $CFGERR_WARNINGS) {
141 config_print_errors();
142 if ($cfgerr_level >= $CFGERR_ERRORS) {
143 die "Errors processing config file";
147 Amanda::Util::finish_setup($RUNNING_AS_DUMPUSER);
148 $logdir = config_dir_relative(getconf($CNF_LOGDIR));
149 $log_file = "$logdir/log";
152 # Check for log file existance
154 `amcleanup -p $config_name`;
159 open(LOG, $log_file);
160 my $info_line = <LOG>;
162 $info_line =~ /^INFO (.*) .* pid .*$/;
163 my $process_name = $1;
164 print "$process_name is running, or you must run amcleanup\n";
169 # amadmin may later try to load this and will die if it has errors
170 # load it now to catch the problem sooner (before we might erase data)
171 my $diskfile = config_dir_relative(getconf($CNF_DISKFILE));
172 $cfgerr_level = Amanda::Disklist::read_disklist('filename' => $diskfile);
173 if ($cfgerr_level >= $CFGERR_ERRORS) {
174 die "Errors processing disklist";
178 my $tapelist_file = config_dir_relative(getconf($CNF_TAPELIST));
179 my $tapelist = Amanda::Tapelist::read_tapelist($tapelist_file);
181 die "Could not read the tapelist";
185 my $t = $tapelist->lookup_tapelabel($label);
186 $t->{'datestamp'} = 0 if $t;
188 $tapelist->remove_tapelabel($label);
190 my $tmp_tapelist_file = "$AMANDA_TMPDIR/tapelist-amrmtape-" . time();
191 my $backup_tapelist_file = "$AMANDA_TMPDIR/tapelist-backup-amrmtape-" . time();
192 unless (copy($tapelist_file, $backup_tapelist_file)) {
193 die "Failed to copy/backup $tapelist_file to $backup_tapelist_file";
195 # writing to temp and then moving is generally safer than writing directly
197 $tapelist->write($tmp_tapelist_file);
198 unless (move($tmp_tapelist_file, $tapelist_file)) {
199 die "Failed to replace old tapelist with new tapelist.";
203 my $tmp_curinfo_file = "$AMANDA_TMPDIR/curinfo-amrmtape-" . time();
204 unless (open(AMADMIN, "$amadmin $config_name export |")) {
205 die "Failed to execute $amadmin: $! $?";
207 open(CURINFO, ">$tmp_curinfo_file");
210 print CURINFO "$_[0]";
216 while(my $line = <AMADMIN>) {
217 my @parts = split(/\s+/, $line);
218 if ($parts[0] =~ /^CURINFO|#|(?:command|last_level|consecutive_runs|(?:full|incr)-(?:rate|comp)):$/) {
220 } elsif ($parts[0] eq 'host:') {
223 } elsif ($parts[0] eq 'disk:') {
226 } elsif ($parts[0] eq 'history:') {
228 } elsif ($line eq "//\n") {
231 } elsif ($parts[0] eq 'stats:') {
232 if (scalar(@parts) < 6 || scalar(@parts) > 8) {
233 die "unexpected number of fields in \"stats\" entry for $host:$disk\n\t$line";
235 my $level = $parts[1];
236 my $cur_label = $parts[7];
237 if ($cur_label eq $label) {
238 $dead_level = $level;
239 vlog "Discarding Host: $host, Disk: $disk, Level: $level\n";
240 } elsif ( $level > $dead_level ) {
241 vlog "Discarding Host: $host, Disk: $disk, Level: $level\n";
246 die "Error: unrecognized line of input:\n\t$line";
250 my $rollback_from_curinfo = sub {
251 unlink $tmp_curinfo_file;
252 return if $keep_label;
253 unless (move($backup_tapelist_file, $tapelist_file)) {
254 printf STDERR "Failed to rollback new tapelist.\n";
260 unless (close AMADMIN) {
261 $rollback_from_curinfo->();
262 die "$amadmin exited with non-zero while exporting: $! $?";
266 if (system("$amadmin $config_name import < $tmp_curinfo_file")) {
267 $rollback_from_curinfo->();
268 die "$amadmin exited with non-zero while importing: $! $?";
272 unlink $tmp_curinfo_file;
273 unlink $backup_tapelist_file;
275 if ($cleanup && !$dry_run) {
276 if (system($amtrmlog, $config_name)) {
277 die "$amtrmlog exited with non-zero while scrubbing logs: $! $?";
279 if (system($amtrmidx, $config_name)) {
280 die "$amtrmidx exited with non-zero while scrubbing indexes: $! $?";
284 Amanda::MainLoop::quit();
287 my $erase_volume = make_cb('erase_volume' => sub {
291 open(LOG, ">$log_file");
292 print LOG "INFO amrmtape amrmtape pid $$\n";
294 my $chg = Amanda::Changer->new($changer_name);
298 my ($err, $resv) = @_;
301 my $dev = $resv->{'device'};
302 die "Can not erase $label because the device doesn't support this feature"
303 unless $dev->property_get('full_deletion');
306 or die "Failed to erase volume";
309 # label the tape with the same label it had
311 $dev->start($ACCESS_WRITE, $label, undef)
312 or die "Failed to write tape label";
316 $resv->release(finished_cb => sub {
330 Amanda::MainLoop::run();
332 if ($log_created == 1) {
337 Amanda::Util::finish_application();