# Copyright (c) 2009-2012 Zmanda, Inc. All Rights Reserved.
# Copyright (c) 2013-2016 Carbonite, Inc. All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Contact information: Carbonite Inc., 756 N Pastoria Ave
# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
package Amanda::Label::Message;
use strict;
use warnings;
use Amanda::Message;
use vars qw( @ISA );
@ISA = qw( Amanda::Message );
sub local_message {
my $self = shift;
if ($self->{'code'} == 1000000) {
return "$self->{'label'}: Can't assign because it is is the '$self->{'config'}' config";
} elsif ($self->{'code'} == 1000001) {
return "$self->{'label'}: Can't assign meta-label without force, old meta-label is '$self->{'meta'}'";
} elsif ($self->{'code'} == 1000002) {
return "$self->{'label'}: Can't assign barcode without force, old barcode is '$self->{'barcode'}'";
} elsif ($self->{'code'} == 1000003) {
return "$self->{'label'}: Can't assign pool without force, old pool is '$self->{'pool'}'";
} elsif ($self->{'code'} == 1000004) {
return "$self->{'label'}: Can't assign storage without force, old storage is '$self->{'storage'}'";
} elsif ($self->{'code'} == 1000005) {
return "$self->{'label'}: Can't assign storage because it is a new labelled tape.";
} elsif ($self->{'code'} == 1000006) {
return "Setting $self->{'label'}";
} elsif ($self->{'code'} == 1000007) {
return "All labels already correctly set.";
} elsif ($self->{'code'} == 1000008) {
return "Reading label...";
} elsif ($self->{'code'} == 1000009) {
return "Found an empty tape.";
} elsif ($self->{'code'} == 1000010) {
return "Found a non-Amanda tape.";
} elsif ($self->{'code'} == 1000011) {
return "Error reading volume label: $self->{'dev_error'}.";
} elsif ($self->{'code'} == 1000012) {
return "Error reading volume label: $self->{'dev_error'}.";
} elsif ($self->{'code'} == 1000013) {
return "Label '$self->{'label'}' doesn't match the labelstr '$self->{'labelstr'}->{'template'}'.";
} elsif ($self->{'code'} == 1000014) {
return "Found label '$self->{'label'}' but it is from config '$self->{'config'}'";
} elsif ($self->{'code'} == 1000015) {
return "Found label '$self->{'label'}' but it is from tape pool '$self->{'pool'}'.";
} elsif ($self->{'code'} == 1000016) {
return "Found label '$self->{'label'}' but it doesn't match the labelstr '$self->{'labelstr'}->{'template'}'.";
} elsif ($self->{'code'} == 1000017) {
return "Volume with label '$self->{'label'}' is active and contains data from this configuration.";
} elsif ($self->{'code'} == 1000018) {
return "Consider using 'amrmtape' to remove volume '$self->{'label'}' from the catalog.";
} elsif ($self->{'code'} == 1000019) {
return "Found label '$self->{'label'}' but it is not in the tapelist file.";
} elsif ($self->{'code'} == 1000020) {
return "Writing label '$self->{'label'}'...";
} elsif ($self->{'code'} == 1000021) {
return "Checking label...";
} elsif ($self->{'code'} == 1000022) {
return "Success!"
} elsif ($self->{'code'} == 1000023) {
return "Label '$self->{'label'}' already on a volume.";
} elsif ($self->{'code'} == 1000024) {
return "No volume with barcode '$self->{'barcode'}' available.";
} elsif ($self->{'code'} == 1000025) {
return "Volume in slot $self->{'slot'} have barcode '$self->{'res_barcode'}, it is not '$self->{'barcode'}'.";
} elsif ($self->{'code'} == 1000026) {
return "Volume in slot $self->{'slot'} have no barcode.";
} elsif ($self->{'code'} == 1000027) {
return "Device meta '$self->{'dev_meta'}' is not the same as the --meta argument '$self->{'meta'}'"
} elsif ($self->{'code'} == 1000028) {
return "Error writing label: $self->{'dev_error'}.";
} elsif ($self->{'code'} == 1000029) {
return "Error finishing label: $self->{'dev_error'}.";
} elsif ($self->{'code'} == 1000030) {
return "Checking the tape label failed: $self->{'dev_error'}.";
} elsif ($self->{'code'} == 1000031) {
return "No label found.";
} elsif ($self->{'code'} == 1000032) {
return "Read back a different label: got '$self->{'got_label'}', but expected '$self->{'label'}'.";
} elsif ($self->{'code'} == 1000033) {
return "Read back a different timetstamp: got '$self->{'got_timestamp'}', but expected 'X'.";
} elsif ($self->{'code'} == 1000034) {
return "Not writing label.";
} elsif ($self->{'code'} == 1000035) {
return "label '$self->{'label'}' not found in tapelist file '$self->{'tapelist_filename'}'.";
} elsif ($self->{'code'} == 1000036) {
return "Failed to copy/backup '$self->{'tapelist_filename'}' to '$self->{'backup_tapelist'}': $self->{'strerror'}.";
} elsif ($self->{'code'} == 1000037) {
return "Failed to execute $self->{'program'}: $self->{'strerror'} $self->{'exit_value'}.";
} elsif ($self->{'code'} == 1000038) {
return "Failed to open $self->{'filename'} for writing: $self->{'strerror'} $self->{'exit_value'}.";
} elsif ($self->{'code'} == 1000039) {
return "unexpected number of fields in \"stats\" entry for $self->{'host'}:$self->{'disk'}\n\t$self->{'line'}"
} elsif ($self->{'code'} == 1000040) {
return "Discarding Host: $self->{'host'}, Disk: $self->{'disk'}, Level: $self->{'level'}.";
} elsif ($self->{'code'} == 1000041) {
return "Error: unrecognized line of input:\n\t$self->{'line'}";
} elsif ($self->{'code'} == 1000042) {
return "Failed to rollback new tapelist: $self->{'strerror'} $self->{'exit_value'}.";
} elsif ($self->{'code'} == 1000043) {
return "'$self->{'program'}' exited with non-zero while exporting: $self->{'strerror'} $self->{'exit_value'}.";
} elsif ($self->{'code'} == 1000044) {
return "'$self->{'program'}' exited with non-zero while importing: $self->{'strerror'} $self->{'exit_value'}.";
} elsif ($self->{'code'} == 1000045) {
return "marking tape '$self->{'label'}' as reusable.";
} elsif ($self->{'code'} == 1000046) {
return "tape '$self->{'label'}' already reusable.";
} elsif ($self->{'code'} == 1000047) {
return "marking tape '$self->{'label'}' as not reusable.";
} elsif ($self->{'code'} == 1000048) {
return "tape '$self->{'label'}' already not reusable.";
} elsif ($self->{'code'} == 1000049) {
return "Erased volume with label '$self->{'label'}'.";
} elsif ($self->{'code'} == 1000050) {
return "Rewrote label '$self->{'label'}' to volume.";
} elsif ($self->{'code'} == 1000051) {
return "Can not erase '$self->{'label'} because the device doesn't support this feature";
} elsif ($self->{'code'} == 1000052) {
return "Removed label '$self->{'label'}' from tapelist file.";
} elsif ($self->{'code'} == 1000053) {
return "Failed to erase volume with label '$self->{'label'}'.";
} elsif ($self->{'code'} == 1000054) {
return "amtrmlog exited with non-zero while scrubbing logs: $self->{'errnostr'} $self->{'child_error'}.";
} elsif ($self->{'code'} == 1000055) {
return "amtrmidx exited with non-zero while scrubbing logs: $self->{'errnostr'} $self->{'child_error'}.";
} elsif ($self->{'code'} == 1000056) {
return "Failed to rewrite label '$self->{'label'}' to volume: $self->{'dev_error'}.";
} elsif ($self->{'code'} == 1000057) {
return "Set datestamp to \"0\" for label '$self->{'label'} in tapelist file.";
} elsif ($self->{'code'} == 1000058) {
return "Can't remove the pool of label '$self->{'label'}'.";
} elsif ($self->{'code'} == 1000059) {
return "Can't remove the storage of label '$self->{'label'}'.";
} elsif ($self->{'code'} == 1000060) {
return "Can't remove the config of label '$self->{'label'}'.";
} elsif ($self->{'code'} == 1000061) {
return "No label matching '$self->{'label'}' in the tapelist file";
} elsif ($self->{'code'} == 1000062) {
return "$self->{'dev_error'}";
} else {
return "no message for code $self->{'code'}";
}
}
package Amanda::Label;
use strict;
use warnings;
use File::Basename;
use File::Copy;
use Amanda::Config qw( :init :getconf config_dir_relative );
use Amanda::Device qw( :constants );
use Amanda::Debug qw( :logging );
use Amanda::Util qw( :constants match_labelstr );
use Amanda::Storage;
use Amanda::Changer;
use Amanda::Header qw( :constants );
use Amanda::MainLoop;
use Amanda::Tapelist;
use Amanda::Paths;
=head1 NAME
Amanda::Label - Amanda module to handle label on volume.
=head1 SYNOPSIS
# create a Label class
my $Label = Amanda::Label->new(storage => $storage,
tapelist => $tl,
user_msg => \&user_msg);
$Label->assign(...);
$Label->label(...);
$Label->erase(...);
$Label->reuse(...);
$label->no_reuse(...);
=head1 user_msg callback
The C<user_msg> is a method that take an Amanda::Message as argument.
This methos is called every time a message must be sent to the caller.
=head1 Label Objects
=head2 assign
$Label->assign(finished_cb => $cb,
label => $label,
meta => $meta,
barcode => $barcode,
pool => $pool,
storage => $storage,
comment => $comment,
force => $force);
Assign the C<meta>, C<barcode>, C<pool> and C<storage> to the label, they can
be undef to not modify the setting. An empty string will remove the setting.
The C<finished_cb> method is called with an Amanda::Message argument.
=head2 label
$Label->label(finished_cb => $cb,
label => $label,
slot => $slot,
barcode => $barcode,
meta => $meta,
force => $force);
Label a volume with the C<label>, the volume is selected by the C<slot> or C<barcode> or the current slot.
C<meta> verifies it is the same as on the changer.
The C<finished_cb> method is called with an Amanda::Message argument.
=head2 erase
$Label->erase(finished_cb => $cb,
labels => @labels,
erase => $erase,
keep_label => $keep_label,
external_copy => $external_copy,
cleanup => $cleanup,
dry_run => $dry_run);
Remove C<labels> from the amanda database, erase the volume is C<erase> is set,
keep the label is C<keep_label> is set,
Run amtrmlog and amtrmidx is C<cleanup> is set.
Do nothing is C<dry_run> is set.
The C<finished_cb> method is called with an Amanda::Message argument.
=head2 reuse
$Label->reuse(finished_cb => $cb,
labels => @labels);
Mark all C<labels> as reuse, calling the changer set_reuse method.
The C<finished_cb> method is called with an Amanda::Message argument.
=head2 no_reuse
$Label->no_reuse(finished_cb => $cb,
labels => @labels);
Mark all C<labels> as reuse, calling the changer set_no_reuse method.
The C<finished_cb> method is called with an Amanda::Message argument.
=cut
my $amadmin = "$sbindir/amadmin";
my $amtrmidx = "$amlibexecdir/amtrmidx";
my $amtrmlog = "$amlibexecdir/amtrmlog";
sub new {
my $class = shift @_;
my %params = @_;
my $self = \%params;
bless $self, $class;
return $self;
}
sub user_msg {
my $self = shift;
my $msg = shift;
if (defined $self->{'user_msg'}) {
$self->{'user_msg'}->($msg);
}
}
sub assign {
my $self = shift;
my %params = @_;
my $finished_cb = $params{'finished_cb'};
my $chg;
my $count = 0;
my $exit_status = 0;
my $steps = define_steps
cb_ref => \$finished_cb;
step start => sub {
$chg = $self->{'storage'}->{'chg'};
return $steps->{'assign'}->();
};
step assign => sub {
my $matched = 0;
my $changed = 0;
$self->{'tapelist'}->reload(1);
for my $tle (@{$self->{'tapelist'}->{'tles'}}) {
if ($tle->{'label'} =~ /$params{'label'}/ or
$tle->{'label'} eq $params{'label'}) {
my $changed1 = 0;
my $error = 0;
$matched = 1;
if ($tle->{'config'} &&
$tle->{'config'} ne Amanda::Config::get_config_name()) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000000,
severity => $Amanda::Message::ERROR,
label => $tle->{'label'},
config => $tle->{'config'}));
$error = 1;
} else {
if (!$tle->{'config'} && $tle->{'datestamp'} ne "0") {
$tle->{'config'} = Amanda::Config::get_config_name();
$changed1 = 1;
}
if (defined $params{'meta'}) {
if (defined($tle->{'meta'}) &&
$params{'meta'} ne $tle->{'meta'} &&
!$params{'force'}) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000001,
severity => $Amanda::Message::ERROR,
label => $tle->{'label'},
meta => $tle->{'meta'}));
$error = 1;
} elsif (!defined $tle->{'meta'} or
$tle->{'meta'} ne $params{'meta'}) {
$tle->{'meta'} = $params{'meta'};
$tle->{'meta'} = undef if $params{'meta'} eq '';
$changed1 = 1;
}
}
if (defined $params{'barcode'}) {
if (defined($tle->{'barcode'}) &&
$params{'barcode'} ne $tle->{'barcode'} &&
!$params{'force'}) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000002,
severity => $Amanda::Message::ERROR,
label => $tle->{'label'},
barcode => $tle->{'barcode'}));
$error = 1;
} elsif (!defined $tle->{'barcode'} or
$tle->{'barcode'} ne $params{'barcode'}) {
$tle->{'barcode'} = $params{'barcode'};
$tle->{'barcode'} = undef if $params{'barcode'} eq '';
$changed1 = 1;
}
}
if (defined $params{'pool'}) {
if ($params{'pool'} eq '') {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000058,
severity => $Amanda::Message::ERROR,
label => $tle->{'label'}));
$error = 1;
} elsif (defined($tle->{'pool'}) &&
$params{'pool'} ne $tle->{'pool'} &&
!$params{'force'}) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000003,
severity => $Amanda::Message::ERROR,
label => $tle->{'label'},
pool => $tle->{'pool'}));
$error = 1;
} elsif (!defined $tle->{'pool'} or
$tle->{'pool'} ne $params{'pool'}) {
$tle->{'pool'} = $params{'pool'};
$changed1 = 1;
}
}
if (defined $params{'storage'}) {
if ($params{'storage'} eq '') {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000059,
severity => $Amanda::Message::ERROR,
label => $tle->{'label'}));
$error = 1;
} elsif (defined($tle->{'storage'}) &&
$params{'storage'} ne $tle->{'storage'} &&
!$params{'force'}) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000004,
severity => $Amanda::Message::ERROR,
label => $tle->{'label'},
storage => $tle->{'storage'}));
$error = 1;
} elsif ($tle->{'datestamp'} eq "0") {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000005,
severity => $Amanda::Message::ERROR,
label => $tle->{'label'}));
} elsif (!defined $tle->{'storage'} or
$tle->{'storage'} ne $params{'storage'}) {
$tle->{'storage'} = $params{'storage'};
$changed1 = 1;
}
}
}
if ($changed1 && !$error) {
if (exists $params{'comment'}) {
$tle->{'comment'} = $params{'comment'};
}
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000006,
severity => $Amanda::Message::SUCCESS,
label => $tle->{'label'}));
$changed++;
}
$exit_status |= $error;
}
}
if ($exit_status == 1) {
return $finished_cb->();
} elsif ($changed) {
$self->{'tapelist'}->write();
} elsif ($matched) {
$self->{'tapelist'}->unlock();
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000007,
severity => $Amanda::Message::INFO));
} else {
$self->{'tapelist'}->unlock();
return $finished_cb->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000061,
severity => $Amanda::Message::ERROR,
label => $params{'label'}));
}
return $finished_cb->() if !$params{'meta'};
$chg->inventory(inventory_cb => $steps->{'assign_inventory'});
};
step assign_inventory => sub {
my ($err, $inv) = @_;
if ($err) {
return $finished_cb->() if $err->notimpl;
return $finished_cb->(Amanda::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1,
severity => $Amanda::Message::ERROR,
message => $err));
}
for my $sl (@$inv) {
if (defined $sl->{'label'} && ($sl->{'label'} =~ /$params{'label'}/ ||
$sl->{'label'} eq $params{'label'})) {
$count++;
$chg->set_meta_label(meta => $params{'meta'},
slot => $sl->{'slot'},
finished_cb => $steps->{'assign_done'});
}
}
return $steps->{'maybe_done'}->();
};
step assign_done => sub {
--$count;
};
step maybe_done => sub {
return $finished_cb->() if $count == 0;
return $steps->{'maybe_done'}->();
}
}
# return undef on success, a string on error.
sub label {
my $self = shift;
my %params = @_;
my $gerr;
my $chg;
my $dev;
my $dev_ok;
my $labelstr;
my $autolabel;
my $tapepool;
my $storage_name;
my $comment;
my $finished_cb = $params{'finished_cb'};
my $res;
my $steps = define_steps
cb_ref => \$finished_cb;
step start => sub {
$chg = $self->{'storage'}->{'chg'};
$labelstr = $self->{'storage'}->{'labelstr'};
$autolabel = $self->{'storage'}->{'autolabel'};
$tapepool = $self->{'storage'}->{'tapepool'};
$storage_name = $self->{'storage'}->{'storage_name'};
$comment = $params{'comment'};
if (defined($params{'label'}) && !$params{'force'}) {
if ($self->{'tapelist'}->lookup_tapelabel($params{'label'})) {
return $steps->{'releasing'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000023,
severity => $Amanda::Message::ERROR,
label => $params{'label'}));
}
}
$steps->{'load'}->();
};
step load => sub {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000008,
severity => $Amanda::Message::INFO));
if ($params{'slot'}) {
$chg->load(slot => $params{'slot'}, mode => "write",
res_cb => $steps->{'loaded'});
} elsif ($params{'barcode'}) {
$chg->inventory(inventory_cb => $steps->{'inventory'});
} else {
$chg->load(relative_slot => "current", mode => "write",
res_cb => $steps->{'loaded'});
}
};
step inventory => sub {
my ($err, $inv) = @_;
return $steps->{'releasing'}->($err) if $err;
for my $sl (@$inv) {
if ($sl->{'barcode'} eq $params{'barcode'}) {
return $chg->load(slot => $sl->{'slot'}, mode => "write",
res_cb => $steps->{'loaded'});
}
}
return $steps->{'releasing'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000024,
severity => $Amanda::Message::ERROR,
barcode => $params{'barcode'}));
};
step loaded => sub {
(my $err, $res) = @_;
return $steps->{'releasing'}->($err) if $err;
if (defined $params{'slot'} && defined $params{'barcode'} &&
$params{'barcode'} ne $res->{'barcode'}) {
if (defined $res->{'barcode'}) {
return $steps->{'releasing'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000025,
severity => $Amanda::Message::ERROR,
slot => $params{'slot'},
barcode => $params{'barcode'},
red_barcode => $res->{'barcode'}));
} else {
return $steps->{'releasing'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000026,
severity => $Amanda::Message::ERROR,
slot => $params{'slot'}));
}
}
$dev = $res->{'device'};
$dev_ok = 1;
if ($dev->status & $DEVICE_STATUS_VOLUME_UNLABELED) {
if (!$dev->volume_header or $dev->volume_header->{'type'} == $F_EMPTY) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000009,
severity => $Amanda::Message::INFO));
} else {
# force is required for non-Amanda tapes
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000010,
severity => $params{'force'}?$Amanda::Message::INFO:$Amanda::Message::ERROR));
$dev_ok = 0 unless ($params{'force'});
}
} elsif ($dev->status & $DEVICE_STATUS_VOLUME_ERROR) {
# it's OK to force through VOLUME_ERROR
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000011,
severity => $params{'force'}?$Amanda::Message::INFO:$Amanda::Message::ERROR,
dev_error => $dev->error_or_status()));
$dev_ok = 0 unless ($params{'force'});
} elsif ($dev->status != $DEVICE_STATUS_SUCCESS) {
# but anything else is fatal
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000012,
severity => $Amanda::Message::ERROR,
dev_error => $dev->error_or_status()));
$dev_ok = 0;
} else {
# this is a labeled Amanda tape
my $label = $dev->volume_label;
my $barcode = $res->{'barcode'};
my $meta = $res->{'meta'};
my $tle = $self->{'tapelist'}->lookup_tapelabel($label);
if ($params{'label'} &&
$labelstr->{'template'} &&
!match_labelstr($labelstr, $autolabel, $params{'label'}, $barcode, $meta, $storage_name)) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000016,
severity => $Amanda::Message::ERROR,
label => $label,
labelstr => $labelstr));
$dev_ok = 0 unless ($params{'force'});
} elsif ($tle) {
if ($tle->{'config'} && $tle->{'config'} ne Amanda::Config::get_config_name()) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000014,
severity => $Amanda::Message::ERROR,
label => $label,
config => $tle->{'config'}));
$dev_ok = 0 unless ($params{'force'});
} elsif ($tle->{'pool'} &&
$tle->{'pool'} ne $tapepool) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000015,
severity => $Amanda::Message::ERROR,
label => $label,
pool => $tle->{'pool'}));
$dev_ok = 0 unless ($params{'force'});
} elsif (!$tle->{'pool'} &&
!match_labelstr($labelstr, $autolabel, $label, $barcode, $meta, $storage_name)) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000016,
severity => $Amanda::Message::ERROR,
label => $label,
labelstr => $labelstr));
$dev_ok = 0 unless ($params{'force'});
} else {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000017,
severity => $params{'force'}?$Amanda::Message::INFO:$Amanda::Message::ERROR,
label => $label));
if ($params{'force'}) {
# if -f, then the user should clean things up..
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000018,
severity => $Amanda::Message::INFO,
label => $label));
# note that we don't run amrmtape automatically, as it could result in data loss when
# multiple volumes have (perhaps accidentally) the same label
} else {
$dev_ok = 0 unless ($params{'force'});
}
}
} else {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000019,
severity => $params{'force'}?$Amanda::Message::INFO:$Amanda::Message::ERROR,
label => $label));
$dev_ok = 0 unless ($params{'force'});
}
}
$res->get_meta_label(finished_cb => $steps->{'got_meta'});
};
step got_meta => sub {
my ($err, $meta) = @_;
return $steps->{'releasing'}->($err) if defined $err;
if (defined $meta && defined $params{'meta'} && $meta ne $params{'meta'}) {
return $steps->{'releasing'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000027,
severity => $Amanda::Message::ERROR,
dev_meta => $meta,
meta => $params{'meta'}));
}
$meta = $params{'meta'} if !defined $meta;
($meta, my $merr) = $res->make_new_meta_label() if !defined $meta;
if (defined $merr) {
return $steps->{'releasing'}->($merr);
}
$params{'meta'} = $meta;
my $label = $params{'label'};
if (!defined($label)) {
($label, my $lerr) = $res->make_new_tape_label(meta => $meta);
if (defined $lerr) {
return $steps->{'releasing'}->($lerr);
}
}
if ($params{'label'} && $dev_ok) {
my $barcode = $res->{'barcode'};
my $meta = $meta;
if (!match_labelstr($labelstr, $autolabel, $params{'label'}, $barcode, $meta, $storage_name)) {
return $steps->{'releasing'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000013,
severity => $Amanda::Message::ERROR,
label => $label,
labelstr => $labelstr));
}
}
if ($dev_ok){
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000020,
severity => $Amanda::Message::INFO,
label => $label));
if (!$dev->start($ACCESS_WRITE, $label, "X")) {
return $steps->{'releasing'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000028,
severity => $Amanda::Message::ERROR,
dev_error => $dev->error_or_status()));
} elsif (!$dev->finish()) {
return $steps->{'releasing'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000029,
severity => $Amanda::Message::ERROR,
dev_error => $dev->error_or_status()));
}
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000021,
severity => $Amanda::Message::INFO));
my $status = $dev->read_label();
if ($status != $DEVICE_STATUS_SUCCESS) {
return $steps->{'releasing'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000030,
severity => $Amanda::Message::ERROR,
dev_error => $dev->error_or_status()));
} elsif (!$dev->volume_label) {
return $steps->{'releasing'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000031,
severity => $Amanda::Message::ERROR));
} elsif ($dev->volume_label ne $label) {
my $got = $dev->volume_label;
return $steps->{'releasing'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000032,
severity => $Amanda::Message::ERROR,
label => $label,
got_label => $got));
} elsif ($dev->volume_time ne "X") {
my $got = $dev->volume_time;
return $steps->{'releasing'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000033,
severity => $Amanda::Message::ERROR,
got_timestamp => $got));
}
# update the tapelist
$self->{'tapelist'}->reload(1);
$self->{'tapelist'}->remove_tapelabel($label);
# the label is not yet assigned a config
$self->{'tapelist'}->add_tapelabel("0", $label, $comment, 1, $meta, $res->{'barcode'},
$dev->block_size/1024,
$tapepool, $storage_name, Amanda::Config::get_config_name());
$self->{'tapelist'}->write();
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000022,
severity => $Amanda::Message::SUCCESS));
# notify the changer
$res->set_label(label => $label, finished_cb => $steps->{'set_meta_label'});
} else {
return $steps->{'releasing'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000034,
severity => $Amanda::Message::ERROR));
}
};
step set_meta_label => sub {
my ($gerr) = @_;
if ($params{'meta'}) {
return $res->set_meta_label(meta => $params{'meta'},
finished_cb => $steps->{'releasing'});
} else {
return $steps->{'releasing'}->();
}
};
step releasing => sub {
my ($err) = @_;
$self->user_msg($err) if $err;
$gerr = $err if !$gerr;
return $res->release(finished_cb => $steps->{'released'}) if $res;
return $steps->{'released'}->();
};
step released => sub {
my ($err) = @_;
$res = undef;
$err = $gerr if defined $gerr;
if ($err) {
if (!$err->isa("Amanda::Message")) {
$err = Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1,
severity => $Amanda::Message::ERROR,
message => "$err");
}
return $finished_cb->($err);
}
$finished_cb->();
};
}
sub erase {
my $self = shift;
my %params = @_;
my $storage;
my $chg;
my $res;
my $label;
my $gerr;
my $backup_tapelist_file;
my $tmp_curinfo_file;
my $finished_cb = $params{'finished_cb'};
my $steps = define_steps
cb_ref => \$finished_cb,
finalize => sub { $storage->quit() if defined $storage;
$chg->quit() if defined $chg };
step start => sub {
$label = shift @{$params{'labels'}};
if (!defined $label) {
return $steps->{'cleanup'}->();
}
my $tle = $self->{'tapelist'}->lookup_tapelabel($label);
if (!defined $tle) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000035,
severity => $Amanda::Message::ERROR,
label => $label,
tapelist_filename =>$self->{'tapelist'}->{'filename'}));
return $steps->{'start'}->();
}
my $storage_name = $tle->{'storage'};
if ($storage_name) {
return $steps->{'erase'}->($storage_name);
}
$storage_name = $params{'storage'};
if ($storage_name) {
return $steps->{'erase'}->($storage_name);
}
# no storage in the tapelist, use the first storage with the same pool
if ($tle->{'pool'}) {
if (getconf_seen($CNF_STORAGE)) {
my $storage_list = getconf($CNF_STORAGE);
my $done = 0;
for $storage_name (@{$storage_list}) {
if (defined $storage and $storage->{'name'} ne $storage_name) {
$storage->quit(); $storage = undef;
$chg->quit(); $chg = undef;
}
if (!$storage) {
$storage = Amanda::Storage->new(
storage_name => $storage_name,
tapelist => $self->{'tapelist'});
if (!$storage->isa("Amanda::Changer::Error")) {
if ($storage->{'tapepool'} eq $tle->{'pool'}) {
debug("Using storage '$storage_name' because it use the same '$tle->{'pool'}' tape pool");
return $steps->{'erase'}->($storage_name);
$done = 1;
last;
}
}
}
}
next if $done;
}
}
# try in the default storage
if (getconf_seen($CNF_STORAGE)) {
my $storage_list = getconf($CNF_STORAGE);
for $storage_name (@{$storage_list}) {
return $steps->{'erase'}->($storage_name);
}
next;
}
# try in the config_name storage;
$storage_name = get_config_name();
if (defined $storage_name) {
return $steps->{'erase'}->($storage_name);
}
return $steps->{'start'}->();
};
step erase => sub {
my $storage_name = shift;
if ($params{'erase'}) {
if (defined $storage and $storage->{'storage_name'} ne $storage_name) {
$storage->quit() if defined $storage; $storage = undef;
$chg->quit() if defined $chg; $chg = undef;
}
if (!$storage) {
$storage = Amanda::Storage->new(storage_name => $storage_name,
tapelist => $self->{'tapelist'});
}
if ($storage->isa("Amanda::Changer::Error")) {
return $steps->{'done'}->($storage);
}
$chg = $storage->{'chg'};
if ($chg->isa("Amanda::Changer::Error")) {
return $steps->{'done'}->($chg);
}
$chg->load(
'label' => $label,
'res_cb' => $steps->{'loaded'});
} else {
return $steps->{'scrub_db'}->();
}
};
step loaded => sub {
(my $err, $res) = @_;
if ($err) {
$self->user_msg($err);
return $steps->{'scrub_db'}->();
}
my $dev = $res->{'device'};
if ($dev->status != $DEVICE_STATUS_SUCCESS) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000062,
severity => $Amanda::Message::ERROR,
dev_error => $dev->error_or_status()));
return $steps->{'releasing'}->();
}
if (!$dev->property_get('full_deletion')) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000051,
severity => $Amanda::Message::ERROR,
label => $label));
return $steps->{'releasing'}->();
}
if (!$params{'dry_run'}) {
if (!$dev->erase()) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000053,
severity => $Amanda::Message::ERROR,
label => $label));
return $steps->{'releasing'}->();
}
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000049,
severity => $Amanda::Message::SUCCESS,
label => $label));
return $res->set_label(finished_cb => sub {
$dev->finish();
if ($params{'external_copy'}) {
$dev->erase();
} elsif ($params{'keep_label'}) {
# label the tape with the same label it had
if (!$dev->start($ACCESS_WRITE, $label, "X")) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000056,
severity => $Amanda::Message::ERROR,
label => $label,
dev_error => $dev->status_or_error));
return $steps->{'releasing'}->();
}
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000050,
severity => $Amanda::Message::SUCCESS,
label => $label));
return $res->set_label(label => $label,
finished_cb => $steps->{'releasing'});
} else {
return $steps->{'releasing'}->();
}
});
} else {
$steps->{'releasing'}->();
}
};
step releasing => sub {
my ($err) = @_;
$gerr = $err if !$gerr;
return $res->release(finished_cb => $steps->{'released'}) if $res;
return $steps->{'released'}->();
};
step released => sub {
my ($err) = @_;
$res = undef;
$err = $gerr if defined $gerr;
if ($err) {
if (!$err->isa("Amanda::Message")) {
$err = Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1,
severity => $Amanda::Message::ERROR,
message => "$err");
}
$self->user_msg($err);
}
return $steps->{'scrub_db'}->();
};
step scrub_db => sub {
my $tle = $self->{'tapelist'}->lookup_tapelabel($label);
my $tapelist_code;
if (!defined $tle) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000035,
severity => $Amanda::Message::ERROR,
label => $label,
tapelist_filename => $self->{'tapelist'}->{'filename'}));
return $steps->{'start'}->();
} elsif ($params{'external_copy'}) {
#do nothing
} elsif ($params{'keep_label'}) {
$tle->{'datestamp'} = 0 if $tle;
$tle->{'storage'} = undef if $tle;
$tle->{'config'} = undef if $tle;
$tapelist_code = 1000057;
} else {
$self->{'tapelist'}->remove_tapelabel($label);
$tapelist_code = 1000052;
}
#take a copy in case we roolback
$backup_tapelist_file = dirname($self->{'tapelist'}->{'filename'}) . "-backup-amrmtape-" . time();
if (-x $self->{'tapelist'}->{'filename'}) {
if (!copy($self->{'tapelist'}->{'filename'}, $backup_tapelist_file)) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000036,
severity => $Amanda::Message::ERROR,
tapelist_filename =>$self->{'tapelist'}->{'filename'},
backup_tapelist =>$backup_tapelist_file,
strerror => $!));
return $steps->{'scrub_cleanup'}->();
}
}
unless ($params{'dry_run'}) {
$self->{'tapelist'}->write();
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => $tapelist_code,
severity => $Amanda::Message::SUCCESS,
label => $label,
tapelist_filename => $self->{'tapelist'}->{'filename'}));
}
$tmp_curinfo_file = "$AMANDA_TMPDIR/curinfo-amrmtape-" . time() . "-" . $$;
my $config_name = get_config_name();
if (!open(AMADMIN, "$amadmin $config_name export |")) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000037,
severity => $Amanda::Message::ERROR,
program => $amadmin,
strerror => $!,
exit_value => $?));
return $steps->{'scrub_cleanup'}->();
}
if (!open(CURINFO, ">$tmp_curinfo_file")) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000038,
severity => $Amanda::Message::ERROR,
filename => $amadmin,
strerror => $!,
exit_value => $?));
close AMADMIN;
return $steps->{'scrub_cleanup'}->();
}
sub info_line($) {
print CURINFO "$_[0]";
}
my $host;
my $disk;
my $dead_level = 10;
while(my $line = <AMADMIN>) {
my @parts = split(/\s+/, $line);
if ($parts[0] =~ /^CURINFO|#|(?:command|last_level|consecutive_runs|(?:full|incr)-(?:rate|comp)):$/) {
info_line $line;
} elsif ($parts[0] eq 'host:') {
$host = $parts[1];
info_line $line;
} elsif ($parts[0] eq 'disk:') {
$disk = $parts[1];
info_line $line;
} elsif ($parts[0] eq 'history:') {
info_line $line;
} elsif ($line eq "//\n") {
info_line $line;
$dead_level = 10;
} elsif ($parts[0] eq 'stats:') {
if (scalar(@parts) < 6 || scalar(@parts) > 8) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000039,
severity => $Amanda::Message::ERROR,
host => $host,
disk => $disk,
line => $line));
close CURINFO;
close AMADMIN;
return $steps->{'scrub_cleanup'}->();
}
my $level = $parts[1];
my $cur_label = $parts[7];
if (defined $cur_label and $cur_label eq $label) {
$dead_level = $level;
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000040,
severity => $Amanda::Message::INFO,
host => $host,
disk => $disk,
level => $level));
} elsif ( $level > $dead_level ) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000040,
severity => $Amanda::Message::INFO,
host => $host,
disk => $disk,
level => $level));
} else {
info_line $line;
}
} else {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000041,
severity => $Amanda::Message::ERROR,
line => $line));
close CURINFO;
close AMADMIN;
return $steps->{'scrub_cleanup'}->();
}
}
close CURINFO;
unless (close AMADMIN) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000043,
severity => $Amanda::Message::ERROR,
program => $amadmin,
strerror => $!,
exit_value => $?));
return $steps->{'rollback_from_curinfo'}->();
}
unless ($params{'dry_run'}) {
my $config_name = get_config_name();
if (system("$amadmin $config_name import < $tmp_curinfo_file")) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000044,
severity => $Amanda::Message::ERROR,
program => $amadmin,
strerror => $!,
exit_value => $?));
return $steps->{'rollback_from_curinfo'}->();
}
}
return $steps->{'scrub_clean'}->();
};
step rollback_from_curinfo => sub {
unlink $tmp_curinfo_file;
return if $params{'keep_label'};
if (!move($backup_tapelist_file, $self->{'tapelist'}->{'filename'})) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000042,
severity => $Amanda::Message::ERROR,
tapelist_filename=> $self->{'tapelist'}->{'filename'},
backup_tapelist =>$backup_tapelist_file,
strerror => $!,
exit_value => $?));
}
return $steps->{'scrub_clean'}->();
};
step scrub_clean => sub {
if ($tmp_curinfo_file) {
unlink $tmp_curinfo_file;
$tmp_curinfo_file = undef;
}
if ($backup_tapelist_file) {
unlink $backup_tapelist_file;
$backup_tapelist_file = undef;
}
return $steps->{'start'}->(); # or abort;
};
step cleanup => sub {
if ($params{'cleanup'} && !$params{'dry_run'}) {
my $config_name = get_config_name();
if (system($amtrmlog, $config_name)) {
return $steps->{'done'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000054,
severity => $Amanda::Message::ERROR,
errno => $!,
child_error => $?));
}
if (system($amtrmidx, $config_name)) {
return $steps->{'done'}->(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000055,
severity => $Amanda::Message::ERROR,
errno => $!,
child_error => $?));
}
}
return $steps->{'done'}->();
};
step done => sub {
my $err = shift;
$self->user_msg($err) if $err;
return $finished_cb->();
};
}
sub reuse {
my $self = shift;
my %params = @_;
my $finished_cb = $params{'finished_cb'};
my $need_write = 0;
my $label;
my @labels = @{$params{'labels'}};
my $steps = define_steps
cb_ref => \$finished_cb;
step start => sub {
$label = shift @{$params{'labels'}};
if (!defined $label) {
return $steps->{'write_tapelist'}->();
}
my $tle = $self->{'tapelist'}->lookup_tapelabel($label);
if (!defined $tle) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000035,
severity => $Amanda::Message::ERROR,
label => $label,
tapelist_filename =>$self->{'tapelist'}->{'filename'}));
return $steps->{'start'}->();
}
if ($tle->{'reuse'} == 0) {
$self->{'tapelist'}->reload(1) if $need_write == 0;;
$self->{'tapelist'}->remove_tapelabel($label);
$self->{'tapelist'}->add_tapelabel($tle->{'datestamp'},
$label, $tle->{'comment'},
1, $tle->{'meta'}, $tle->{'barcode'},
$tle->{'blocksize'}, $tle->{'pool'},
$tle->{'storage'}, $tle->{'config'});
$need_write = 1;
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000045,
severity => $Amanda::Message::INFO,
label => $label));
} else {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000046,
severity => $Amanda::Message::INFO,
label => $label));
}
return $steps->{'start'}->();
};
step write_tapelist => sub {
$self->{'tapelist'}->write() if $need_write == 1;
return $self->{'storage'}->{'chg'}->set_reuse(
labels => \@labels,
finished_cb => $steps->{'reuse_done'});
};
step reuse_done => sub {
my $err = shift;
if ($err and $err->notimpl) {
$err = undef;
}
$self->user_msg($err) if $err;
$finished_cb->();
}
}
sub no_reuse {
my $self = shift;
my %params = @_;
my $finished_cb = $params{'finished_cb'};
my $need_write = 0;
my $label;
my @labels = @{$params{'labels'}};
my $steps = define_steps
cb_ref => \$finished_cb;
step start => sub {
$label = shift @{$params{'labels'}};
if (!defined $label) {
return $steps->{'write_tapelist'}->();
}
my $tle = $self->{'tapelist'}->lookup_tapelabel($label);
if (!defined $tle) {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000035,
severity => $Amanda::Message::ERROR,
label => $label,
tapelist_filename =>$self->{'tapelist'}->{'filename'}));
return $steps->{'start'}->();
}
if ($tle->{'reuse'} == 1) {
$self->{'tapelist'}->reload(1) if $need_write == 0;;
$self->{'tapelist'}->remove_tapelabel($label);
$self->{'tapelist'}->add_tapelabel($tle->{'datestamp'},
$label, $tle->{'comment'},
0, $tle->{'meta'}, $tle->{'barcode'},
$tle->{'blocksize'}, $tle->{'pool'},
$tle->{'storage'}, $tle->{'config'});
$need_write = 1;
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000047,
severity => $Amanda::Message::SUCCESS,
label => $label));
} else {
$self->user_msg(Amanda::Label::Message->new(
source_filename => __FILE__,
source_line => __LINE__,
code => 1000048,
severity => $Amanda::Message::INFO,
label => $label));
}
return $steps->{'start'}->();
};
step write_tapelist => sub {
$self->{'tapelist'}->write() if $need_write == 1;
return $self->{'storage'}->{'chg'}->set_no_reuse(
labels => \@labels,
finished_cb => $steps->{'no_reuse_done'});
};
step no_reuse_done => sub {
my $err = shift;
if ($err and $err->notimpl) {
$err = undef;
}
$self->user_msg($err) if $err;
$finished_cb->();
}
}
1;