X-Git-Url: https://git.gag.com/?a=blobdiff_plain;ds=sidebyside;f=perl%2FAmanda%2FTapelist.pm;h=6a746ba623e6df860027e927dcf3f6f0f716cda0;hb=b116e9366c7b2ea2c2eb53b0a13df4090e176235;hp=b2792b4574befc902276213e1c0e26d333e74ff8;hpb=fd48f3e498442f0cbff5f3606c7c403d0566150e;p=debian%2Famanda diff --git a/perl/Amanda/Tapelist.pm b/perl/Amanda/Tapelist.pm index b2792b4..6a746ba 100644 --- a/perl/Amanda/Tapelist.pm +++ b/perl/Amanda/Tapelist.pm @@ -71,21 +71,63 @@ Amanda::Tapelist - manipulate the Amanda tapelist use Amanda::Tapelist; - my $tl = Amanda::Tapelist::read_tapelist("/path/to/tapefile"); + # to get a read only copy of the tapelist file: + my $tl = Amanda::Tapelist->new("/path/to/tapefile"); + + # to read/update/write the tapelist file + # read and take lock + my $tl = Amanda::Tapelist->new("/path/to/tapefile", 1); + # modify the memory copy $tl->add_tapelabel($datestamp, $label); - $tl->add_tapelabel($datestamp2, $label2, $comment); - $tl->write("/path/to/tapefile"); + $tl->add_tapelabel($datestamp2, $label2, $comment, 1); + # write it and unlock + $tl->write(); + + # If you already have a read only copy and want to modify it + # take a read only copy + my $tl = Amanda::Tapelist->new("/path/to/tapefile"); + # reload and take lock + $tl->reload(1); + # modify the memory copy + tl->add_tapelabel($datestamp, $label); + $tl->add_tapelabel($datestamp2, $label2, $comment, 1); + # write it and unlock + $tl->write(); =head1 OBJECT-ORIENTED INTERFACE -The package-level functions C and -C both return a new tapelist object. -C returns C if the tapelist does not exist. +C returns a hash with no C set if the tapelist does +not exist. C is an empty array if the tapelist is empty. Invalid entries are silently ignored. -A tapelist object is a sequence of tapelist elements (referred to as TLEs in -this document), sorted by datestamp from newest to oldest. Each TLE is a hash -with the following keys: +=head2 tapelist object + +A tapelist object is a hash with the following keys: + +=over + +=item C + + The filename of the tapelist file. + +=item C + + The filename of the lock file. + +=item C + + A Amanda::Util::file_lock is the file is locked. + +=item C + +A sequence of tapelist elements (referred to as TLEs in this document), +sorted by datestamp from newest to oldest. + +=back + +=head2 tapelist element + +A tapelist elementas a hash with the following keys: =over @@ -111,10 +153,16 @@ the comment for this tape, or undef if no comment was given =back +=head1 Method + The following methods are available on a tapelist object C<$tl>: =over +=item C + +reload the tapelist file, lock it if $lock is set + =item C look up and return a reference to the TLE with the given label @@ -131,14 +179,24 @@ look up and return a reference to the TLE with the given datestamp remove the tape with the given label -=item C +=item C + +add a tape with the given date, label, comment and reuse to the end of the +tapelist. reuse can be 1 or undef for a reusable volume, it must be 0 for +a no-reusable volume. + +=item C or C -add a tape with the given date, label, and comment to the end of the -tapelist, marking it reusable. +write the tapelist out to the same file as when read or to C<$filename> if it +is set, remove the lock if a lock was taken -=item C +=item C -write the tapelist out to C<$filename>. +remove the lock if a lock was taken + +=item C + +remove all tle from the tles. =back @@ -146,7 +204,7 @@ write the tapelist out to C<$filename>. The C portions of Amanda treat the tapelist as a global variable, while this package treats it as an object (and can thus handle more -than one tapelist simultaneously). Every call to C +than one tapelist simultaneously). Every call to C fills this global variable with a copy of the tapelist, and likewise C clears the global. However, any changes made from Perl are not reflected in the C copy, nor are changes made by C @@ -157,65 +215,60 @@ modules reflected in the Perl copy. use Amanda::Debug qw(:logging); +use Amanda::Config qw( config_dir_relative ); +use File::Copy; +use Fcntl qw(:flock); # import LOCK_* constants ## package functions -sub read_tapelist { - my ($filename) = @_; - - # let C read the file - C_read_tapelist($filename); - - # and then read it ourselves - open(my $fh, "<", $filename) or return undef; - my @tles; - while (my $line = <$fh>) { - my ($datestamp, $label, $reuse, $comment) - = $line =~ m/^([0-9]*)\s([^\s]*)\s(reuse|no-reuse)\s*(?:\#(.*))?$/mx; - next if !defined $datestamp; # silently filter out bogus lines - push @tles, { - 'datestamp' => $datestamp, - 'label' => $label, - 'reuse' => ($reuse eq 'reuse'), - 'comment' => $comment, - }; - } - close($fh); - - # sort in descending order by datestamp, sorting on position, too, to ensure - # that entries with the same datestamp stay in the right order - update_positions(\@tles); # call a method with an explicit $self - @tles = sort { - $b->{'datestamp'} cmp $a->{'datestamp'} - || $a->{'position'} <=> $b->{'position'} - } @tles; - - # and re-calculate the positions - update_positions(\@tles); - - my $self = bless \@tles, "Amanda::Tapelist"; +sub new { + my ($class) = shift; + my ($filename, $lock ) = @_; + my $self = { + filename => $filename, + lockname => $filename . '.lock', + }; + bless $self, $class; + $self->reload($lock); return $self; } sub clear_tapelist { + my $self = shift; + # clear the C version C_clear_tapelist(); - # and produce an empty object - my $self = bless [], "Amanda::Tapelist"; - $self->update_positions(); + $self->{'tles'} = []; return $self; } ## methods +sub reload { + my $self = shift; + my ($lock) = @_; + + if ($lock) { + $self->_take_lock(); + } + + # clear the C copy + C_clear_tapelist(); + + # let C read the file + C_read_tapelist($self->{'filename'}); + + $self->_read_tapelist(); +} + sub lookup_tapelabel { my $self = shift; my ($label) = @_; - for my $tle (@$self) { + for my $tle (@{$self->{'tles'}}) { return $tle if ($tle->{'label'} eq $label); } @@ -226,15 +279,15 @@ sub lookup_tapepos { my $self = shift; my ($position) = @_; - $self->update_positions(); - return $self->[$position-1]; + $self->_update_positions(); + return $self->{'tles'}->[$position-1]; } sub lookup_tapedate { my $self = shift; my ($datestamp) = @_; - for my $tle (@$self) { + for my $tle (@{$self->{'tles'}}) { return $tle if ($tle->{'datestamp'} eq $datestamp); } @@ -245,10 +298,10 @@ sub remove_tapelabel { my $self = shift; my ($label) = @_; - for (my $i = 0; $i < @$self; $i++) { - if ($self->[$i]->{'label'} eq $label) { - splice @$self, $i, 1; - $self->update_positions(); + for (my $i = 0; $i < @{$self->{tles}}; $i++) { + if ($self->{tles}->[$i]->{'label'} eq $label) { + splice @{$self->{tles}}, $i, 1; + $self->_update_positions(); return; } } @@ -256,43 +309,118 @@ sub remove_tapelabel { sub add_tapelabel { my $self = shift; - my ($datestamp, $label, $comment) = @_; + my ($datestamp, $label, $comment, $reuse) = @_; + $reuse = 1 if !defined $reuse; # prepend this (presumably new) volume to the beginning of the list - unshift @$self, { + unshift @{$self->{'tles'}}, { 'datestamp' => $datestamp, 'label' => $label, - 'reuse' => 1, + 'reuse' => $reuse, 'comment' => $comment, }; - $self->update_positions(); + $self->_update_positions(); } sub write { my $self = shift; my ($filename) = @_; + my $result = TRUE; + $filename = $self->{'filename'} if !defined $filename; + + my $new_tapelist_file = $filename . "-new-" . time(); - open(my $fh, ">", $filename) or die("Could not open '$filename' for writing: $!"); - for my $tle (@$self) { + open(my $fhn, ">", $new_tapelist_file) or die("Could not open '$new_tapelist_file' for writing: $!"); + for my $tle (@{$self->{tles}}) { my $datestamp = $tle->{'datestamp'}; my $label = $tle->{'label'}; my $reuse = $tle->{'reuse'} ? 'reuse' : 'no-reuse'; my $comment = (defined $tle->{'comment'})? (" #" . $tle->{'comment'}) : ''; - print $fh "$datestamp $label $reuse$comment\n"; + $result &&= print $fhn "$datestamp $label $reuse$comment\n"; + } + my $result_close = close($fhn); + $result &&= $result_close; + + return if (!$result); + + unless (move($new_tapelist_file, $filename)) { + die ("failed to rename '$new_tapelist_file' to '$filename': $!"); } - close($fh); # re-read from the C side to synchronize C_read_tapelist($filename); + + $self->unlock(); + + return undef; +} + +sub unlock { + my $self = shift; + + return if !exists $self->{'fl'}; + + $self->{'fl'}->unlock(); + delete $self->{'fl'} } ## private methods +sub _take_lock { + my $self = shift; + + if (!-e $self->{'lockname'}) { + open(my $fhl, ">>", $self->{'lockname'}); + close($fhl); + } + my $fl = Amanda::Util::file_lock->new($self->{'lockname'}); + while(($r = $fl->lock()) == 1) { + sleep(1); + } + if ($r == 0) { + $self->{'fl'} = $fl; + } +} + +sub _read_tapelist { + my $self = shift; + + my @tles; + open(my $fh, "<", $self->{'filename'}) or return $self; + while (my $line = <$fh>) { + my ($datestamp, $label, $reuse, $comment) + = $line =~ m/^([0-9]*)\s([^\s]*)\s(reuse|no-reuse)\s*(?:\#(.*))?$/mx; + next if !defined $datestamp; # silently filter out bogus lines + push @tles, { + 'datestamp' => $datestamp, + 'label' => $label, + 'reuse' => ($reuse eq 'reuse'), + 'comment' => $comment, + }; + } + close($fh); + + # sort in descending order by datestamp, sorting on position, too, to ensure + # that entries with the same datestamp stay in the right order + $self->{'tles'} = \@tles; + $self->_update_positions(); + @tles = sort { + $b->{'datestamp'} cmp $a->{'datestamp'} + || $a->{'position'} <=> $b->{'position'} + } @tles; + + $self->{'tles'} = \@tles; + + # and re-calculate the positions + $self->_update_positions(\@tles); +} + # update the 'position' key for each TLE -sub update_positions { +sub _update_positions { my $self = shift; - for (my $i = 0; $i < @$self; $i++) { - $self->[$i]->{'position'} = $i+1; + my $tles = $self->{'tles'}; + for (my $i = 0; $i < scalar @$tles; $i++) { + $tles->[$i]->{'position'} = $i+1; } }