X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=perl%2FAmanda%2FChanger%2Fdisk.pm;h=b31ad54b64fa666a4f1d3ebd4787f4e6256ede0e;hb=fd48f3e498442f0cbff5f3606c7c403d0566150e;hp=3b0636252c59dd0bbddcf891c5a22dda97ce364c;hpb=96f35b20267e8b1a1c846d476f27fcd330e0b018;p=debian%2Famanda diff --git a/perl/Amanda/Changer/disk.pm b/perl/Amanda/Changer/disk.pm index 3b06362..b31ad54 100644 --- a/perl/Amanda/Changer/disk.pm +++ b/perl/Amanda/Changer/disk.pm @@ -1,20 +1,20 @@ -# Copyright (c) 2005-2008 Zmanda, Inc. All Rights Reserved. +# Copyright (c) 2008,2009,2010 Zmanda, Inc. All Rights Reserved. # -# This library is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License version 2.1 as -# published by the Free Software Foundation. +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 as published +# by the Free Software Foundation. # -# This library is distributed in the hope that it will be useful, but +# 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 Lesser General Public -# License for more details. +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. # -# You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, -# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. +# 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: Zmanda Inc., 465 S Mathlida Ave, Suite 300 -# Sunnyvale, CA 94086, USA, or: http://www.zmanda.com +# Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300 +# Sunnyvale, CA 94085, USA, or: http://www.zmanda.com package Amanda::Changer::disk; @@ -29,6 +29,7 @@ use Amanda::Config qw( :getconf ); use Amanda::Debug; use Amanda::Changer; use Amanda::MainLoop; +use Amanda::Device qw( :constants ); =head1 NAME @@ -44,36 +45,57 @@ string, which it arranges as follows: | | data -> '../slot4' |- drive1/ -| | | data -> '../slot1' - |- current -> slot5 + |- data -> slot5 |- slot1/ |- slot2/ |- ... |- slot$n/ -The user should create the desired number C subdirectories, and -the changer will take care of dynamically creating the drives as needed, -and track the "current" slot using the eponymous symlink. +The user should create the desired number of C subdirectories. The +changer will take care of dynamically creating the drives as needed, and track +the current slot using a "data" symlink. This allows use of "file:$dir" as a +device operating on the current slot, although note that it is unlocked. Drives are dynamically allocated as Amanda applications request access to particular slots. Each drive is represented as a subdirectory containing a 'data' symlink pointing to the "loaded" slot. -=head1 TODO - - - better locking (at least to work on a shared filesystem, if not NFS) - - manpage +See the amanda-changers(7) manpage for usage information. =cut +# STATE +# +# The device state is shared between all changers accessing the same changer. +# It is a hash with keys: +# drives - see below +# +# The 'drives' key is a hash, with drive as keys and hashes +# as values. Each drive's hash has keys: +# pid - the pid that reserved that drive. +# + + sub new { my $class = shift; - my ($cc, $tpchanger) = @_; + my ($config, $tpchanger) = @_; my ($dir) = ($tpchanger =~ /chg-disk:(.*)/); + unless (-d $dir) { + return Amanda::Changer->make_error("fatal", undef, + message => "directory '$dir' does not exist"); + } + # note that we don't track outstanding Reservation objects -- we know # they're gone when they delete their drive directory my $self = { dir => $dir, + config => $config, + state_filename => "$dir/state", + + # this is set to 0 by various test scripts, + # notably Amanda_Taper_Scan_traditional + support_fast_search => 1, }; bless ($self, $class); @@ -83,36 +105,48 @@ sub new { sub load { my $self = shift; my %params = @_; + my $old_res_cb = $params{'res_cb'}; + my $state; - die "no res_cb supplied" unless (exists $params{'res_cb'}); + $self->validate_params('load', \%params); - if (exists $params{'slot'}) { - $self->_load_by_slot(%params); - } elsif (exists $params{'label'}) { - $self->_load_by_label(%params); - } else { - die "Invalid parameters to 'load'"; - } + return if $self->check_error($params{'res_cb'}); + + $self->with_locked_state($self->{'state_filename'}, + $params{'res_cb'}, sub { + my ($state, $res_cb) = @_; + $params{'state'} = $state; + + # overwrite the callback for _load_by_xxx + $params{'res_cb'} = $res_cb; + + if (exists $params{'slot'} or exists $params{'relative_slot'}) { + $self->_load_by_slot(%params); + } elsif (exists $params{'label'}) { + $self->_load_by_label(%params); + } + }); } -sub info { +sub info_key { my $self = shift; - my %params = @_; + my ($key, %params) = @_; my %results; - die "no info_cb supplied" unless (exists $params{'info_cb'}); - die "no info supplied" unless (exists $params{'info'}); + return if $self->check_error($params{'info_cb'}); - for my $inf (@{$params{'info'}}) { - if ($inf eq 'num_slots') { - my @slots = $self->_all_slots(); - $results{$inf} = scalar @slots; - } else { - warn "Ignoring request for info key '$inf'"; - } + # no need for synchronization -- all of these values are static + + if ($key eq 'num_slots') { + my @slots = $self->_all_slots(); + $results{$key} = scalar @slots; + } elsif ($key eq 'vendor_string') { + $results{$key} = 'chg-disk'; # mostly just for testing + } elsif ($key eq 'fast_search') { + $results{$key} = $self->{'support_fast_search'}; } - Amanda::MainLoop::call_later($params{'info_cb'}, undef, %results); + $params{'info_cb'}->(undef, %results) if $params{'info_cb'}; } sub reset { @@ -121,47 +155,102 @@ sub reset { my $slot; my @slots = $self->_all_slots(); - $slot = (scalar @slots)? $slots[0] : 0; - $self->_set_current($slot); + return if $self->check_error($params{'finished_cb'}); - if (exists $params{'finished_cb'}) { - Amanda::MainLoop::call_later($params{'finished_cb'}); - } + $self->with_locked_state($self->{'state_filename'}, + $params{'finished_cb'}, sub { + my ($state, $finished_cb) = @_; + + $slot = (scalar @slots)? $slots[0] : 0; + $self->_set_current($slot); + + $finished_cb->(); + }); +} + +sub inventory { + my $self = shift; + my %params = @_; + + return if $self->check_error($params{'inventory_cb'}); + + my @slots = $self->_all_slots(); + + $self->with_locked_state($self->{'state_filename'}, + $params{'inventory_cb'}, sub { + my ($state, $finished_cb) = @_; + my @inventory; + + my $current = $self->_get_current(); + for my $slot (@slots) { + my $s = { slot => $slot, state => Amanda::Changer::SLOT_FULL }; + $s->{'reserved'} = $self->_is_slot_in_use($state, $slot); + my $label = $self->_get_slot_label($slot); + if ($label) { + $s->{'label'} = $self->_get_slot_label($slot); + $s->{'f_type'} = "".$Amanda::Header::F_TAPESTART; + } else { + $s->{'label'} = undef; + $s->{'f_type'} = "".$Amanda::Header::F_EMPTY; + } + $s->{'device_status'} = "".$DEVICE_STATUS_SUCCESS; + $s->{'current'} = 1 if $slot eq $current; + push @inventory, $s; + } + $finished_cb->(undef, \@inventory); + }); } sub _load_by_slot { my $self = shift; my %params = @_; - my $slot = $params{'slot'}; my $drive; + my $slot; - if ($slot eq "current") { - $slot = $self->_get_current(); - } elsif ($slot eq "next") { - $slot = $self->_get_current(); - $slot = $self->_get_next($slot); + if (exists $params{'relative_slot'}) { + if ($params{'relative_slot'} eq "current") { + $slot = $self->_get_current(); + } elsif ($params{'relative_slot'} eq "next") { + if (exists $params{'slot'}) { + $slot = $params{'slot'}; + } else { + $slot = $self->_get_current(); + } + $slot = $self->_get_next($slot); + $self->_set_current($slot) if ($params{'set_current'}); + } else { + return $self->make_error("failed", $params{'res_cb'}, + reason => "invalid", + message => "Invalid relative slot '$params{relative_slot}'"); + } + } else { + $slot = $params{'slot'}; + } + + if (exists $params{'except_slots'} and exists $params{'except_slots'}->{$slot}) { + return $self->make_error("failed", $params{'res_cb'}, + reason => "notfound", + message => "all slots have been loaded"); } if (!$self->_slot_exists($slot)) { - Amanda::MainLoop::call_later($params{'res_cb'}, - "Slot $slot not found", undef); - return; + return $self->make_error("failed", $params{'res_cb'}, + reason => "invalid", + message => "Slot $slot not found"); } - if ($drive = $self->_is_slot_in_use($slot)) { - Amanda::MainLoop::call_later($params{'res_cb'}, - "Slot $slot is already in use by drive '$drive'", undef); - return; + if ($drive = $self->_is_slot_in_use($params{'state'}, $slot)) { + return $self->make_error("failed", $params{'res_cb'}, + reason => "volinuse", + slot => $slot, + message => "Slot $slot is already in use by drive '$drive' and process '$params{state}->{drives}->{$drive}->{pid}'"); } $drive = $self->_alloc_drive(); $self->_load_drive($drive, $slot); $self->_set_current($slot) if ($params{'set_current'}); - my $next_slot = $self->_get_next($slot); - - Amanda::MainLoop::call_later($params{'res_cb'}, - undef, Amanda::Changer::disk::Reservation->new($self, $drive, $slot, $next_slot)); + $self->_make_res($params{'state'}, $params{'res_cb'}, $drive, $slot); } sub _load_by_label { @@ -173,24 +262,48 @@ sub _load_by_label { $slot = $self->_find_label($label); if (!defined $slot) { - Amanda::MainLoop::call_later($params{'res_cb'}, - "Label '$label' not found", undef); - return; + return $self->make_error("failed", $params{'res_cb'}, + reason => "notfound", + message => "Label '$label' not found"); } - if ($drive = $self->_is_slot_in_use($slot)) { - Amanda::MainLoop::call_later($params{'res_cb'}, - "Slot $slot, containing '$label', is already in use by drive '$drive'", undef); + if ($drive = $self->_is_slot_in_use($params{'state'}, $slot)) { + return $self->make_error("failed", $params{'res_cb'}, + reason => "volinuse", + message => "Slot $slot, containing '$label', is already " . + "in use by drive '$drive'"); } $drive = $self->_alloc_drive(); $self->_load_drive($drive, $slot); $self->_set_current($slot) if ($params{'set_current'}); - my $next_slot = $self->_get_next($slot); + $self->_make_res($params{'state'}, $params{'res_cb'}, $drive, $slot); +} + +sub _make_res { + my $self = shift; + my ($state, $res_cb, $drive, $slot) = @_; + my $res; + + my $device = Amanda::Device->new("file:$drive"); + if ($device->status != $DEVICE_STATUS_SUCCESS) { + return $self->make_error("failed", $res_cb, + reason => "device", + message => "opening 'file:$drive': " . $device->error_or_status()); + } + + if (my $err = $self->{'config'}->configure_device($device)) { + return $self->make_error("failed", $res_cb, + reason => "device", + message => $err); + } + + $res = Amanda::Changer::disk::Reservation->new($self, $device, $drive, $slot); + $state->{drives}->{$drive}->{pid} = $$; + $device->read_label(); - Amanda::MainLoop::call_later($params{'res_cb'}, - undef, Amanda::Changer::disk::Reservation->new($self, $drive, $slot, $next_slot)); + $res_cb->(undef, $res); } # Internal function to find an unused (nonexistent) driveN subdirectory and @@ -236,7 +349,7 @@ sub _slot_exists { # Internal function to determine if a slot (specified by number) is in use by a # drive, and return the path for that drive if so. sub _is_slot_in_use { - my ($self, $slot) = @_; + my ($self, $state, $slot) = @_; my $dir = _quote_glob($self->{'dir'}); for my $symlink (bsd_glob("$dir/drive*/data")) { @@ -258,14 +371,38 @@ sub _is_slot_in_use { } if ($tslot+0 == $slot) { - $symlink =~ s{/data$}{}; # strip the trailing '/data' - return $symlink; + my $drive = $symlink; + $drive =~ s{/data$}{}; # strip the trailing '/data' + + #check if process is alive + my $pid = $state->{drives}->{$drive}->{pid}; + if (defined $pid && !Amanda::Util::is_pid_alive($pid)) { + unlink("$drive/data") + or warn("Could not unlink '$drive/data': $!"); + rmdir("$drive") + or warn("Could not rmdir '$drive': $!"); + delete $state->{drives}->{$drive}->{pid}; + next; + } + return $drive; } } return 0; } +sub _get_slot_label { + my ($self, $slot) = @_; + my $dir = _quote_glob($self->{'dir'}); + + for my $symlink (bsd_glob("$dir/slot$slot/00000.*")) { + my ($label) = ($symlink =~ qr{\/00000\.([^/]*)$}); + return $label; + } + + return ''; # known, but blank +} + # Internal function to point a drive to a slot sub _load_drive { my ($self, $drive, $slot) = @_; @@ -320,10 +457,16 @@ sub _get_next { return $all_slots[0]; } -# Get the 'current' slot, represented as a symlink named 'current' +# Get the 'current' slot, represented as a symlink named 'data' sub _get_current { my ($self) = @_; - my $curlink = $self->{'dir'} . "/current"; + my $curlink = $self->{'dir'} . "/data"; + + # for 2.6.1-compatibility, also parse a "current" symlink + my $oldlink = $self->{'dir'} . "/current"; + if (-l $oldlink and ! -e $curlink) { + rename($oldlink, $curlink); + } if (-l $curlink) { my $target = readlink($curlink); @@ -341,11 +484,11 @@ sub _get_current { # Set the 'current' slot sub _set_current { my ($self, $slot) = @_; - my $curlink = $self->{'dir'} . "/current"; + my $curlink = $self->{'dir'} . "/data"; if (-e $curlink) { unlink($curlink) - or die("Could not unlink '$curlink'"); + or warn("Could not unlink '$curlink'"); } # TODO: locking @@ -365,15 +508,14 @@ use vars qw( @ISA ); sub new { my $class = shift; - my ($chg, $drive, $slot, $next_slot) = @_; + my ($chg, $device, $drive, $slot) = @_; my $self = Amanda::Changer::Reservation::new($class); $self->{'chg'} = $chg; $self->{'drive'} = $drive; - $self->{'device_name'} = "file:$drive"; + $self->{'device'} = $device; $self->{'this_slot'} = $slot; - $self->{'next_slot'} = $next_slot; return $self; } @@ -388,7 +530,21 @@ sub do_release { rmdir("$drive") or warn("Could not rmdir '$drive': $!"); - if (exists $params{'finished_cb'}) { - Amanda::MainLoop::call_later($params{'finished_cb'}, undef); + # unref the device, for good measure + $self->{'device'} = undef; + + if (exists $params{'unlocked'}) { + my $state = $params{state}; + delete $state->{drives}->{$drive}->{pid}; + return $params{'finished_cb'}->(); } + + $self->{chg}->with_locked_state($self->{chg}->{'state_filename'}, + $params{'finished_cb'}, sub { + my ($state, $finished_cb) = @_; + + delete $state->{drives}->{$drive}->{pid}; + + $finished_cb->(); + }); }