1/* 2 * Copyright (c) 2008-2013 Zmanda, Inc. All Rights Reserved. 3 * 4 * This program is free software; you can redistribute it and/or 5 * modify it under the terms of the GNU General Public License 6 * as published by the Free Software Foundation; either version 2 7 * of the License, or (at your option) any later version. 8 * 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 12 * for more details. 13 * 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 17 * 18 * Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300 19 * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com 20 */ 21 22%module "Amanda::Tapelist" 23%include "amglue/amglue.swg" 24%include "exception.i" 25 26%include "Amanda/Tapelist.pod" 27 28%{ 29#include "tapefile.h" 30%} 31 32%perlcode %{ 33use Amanda::Debug qw(:logging); 34use Amanda::Config qw( config_dir_relative ); 35use File::Copy; 36use Fcntl qw(:flock); # import LOCK_* constants 37 38## package functions 39 40sub new { 41 my ($class) = shift; 42 my ($filename, $lock ) = @_; 43 my $self = { 44 filename => $filename, 45 lockname => $filename . '.lock', 46 }; 47 bless $self, $class; 48 49 $self->reload($lock); 50 return $self; 51} 52 53sub clear_tapelist { 54 my $self = shift; 55 56 # clear the C version 57 C_clear_tapelist(); 58 59 $self->{'tles'} = []; 60 61 return $self; 62} 63 64## methods 65 66sub reload { 67 my $self = shift; 68 my ($lock) = @_; 69 70 if ($lock) { 71 $self->_take_lock(); 72 } 73 74 # clear the C copy 75 C_clear_tapelist(); 76 77 # let C read the file 78 C_read_tapelist($self->{'filename'}); 79 80 $self->_read_tapelist(); 81} 82 83sub lookup_tapelabel { 84 my $self = shift; 85 my ($label) = @_; 86 87 for my $tle (@{$self->{'tles'}}) { 88 return $tle if ($tle->{'label'} eq $label); 89 } 90 91 return undef; 92} 93 94sub lookup_by_barcode { 95 my $self = shift; 96 my ($barcode) = @_; 97 98 for my $tle (@{$self->{'tles'}}) { 99 return $tle if (defined $tle->{'barcode'} and 100 $tle->{'barcode'} eq $barcode); 101 } 102 103 return undef; 104} 105 106sub lookup_tapepos { 107 my $self = shift; 108 my ($position) = @_; 109 110 $self->_update_positions(); 111 return $self->{'tles'}->[$position-1]; 112} 113 114sub lookup_tapedate { 115 my $self = shift; 116 my ($datestamp) = @_; 117 118 for my $tle (@{$self->{'tles'}}) { 119 return $tle if ($tle->{'datestamp'} eq $datestamp); 120 } 121 122 return undef; 123} 124 125sub remove_tapelabel { 126 my $self = shift; 127 my ($label) = @_; 128 129 for (my $i = 0; $i < @{$self->{tles}}; $i++) { 130 if ($self->{tles}->[$i]->{'label'} eq $label) { 131 splice @{$self->{tles}}, $i, 1; 132 $self->_update_positions(); 133 return; 134 } 135 } 136} 137 138sub add_tapelabel { 139 my $self = shift; 140 my ($datestamp, $label, $comment, $reuse, $meta, $barcode, $blocksize) = @_; 141 $reuse = 1 if !defined $reuse; 142 143 # prepend this (presumably new) volume to the beginning of the list 144 my $tle = { 145 'datestamp' => $datestamp, 146 'label' => $label, 147 'reuse' => $reuse, 148 'barcode' => $barcode, 149 'meta' => $meta, 150 'blocksize' => $blocksize, 151 'comment' => $comment, 152 }; 153 my $tles = $self->{'tles'}; 154 if (!defined $tles->[0] || 155 $tles->[0]->{'datestamp'} le $datestamp) { 156 unshift @{$tles}, $tle; 157 } elsif (defined $tles->[0] && 158 $tles->[@$tles-1]->{'datestamp'} gt $datestamp) { 159 push @{$tles}, $tle; 160 } else { 161 my $added = 0; 162 for my $i (0..(@$tles-1)) { 163 if ($tles->[$i]->{'datestamp'} le $datestamp) { 164 splice @{$tles}, $i, 0, $tle; 165 $added = 1; 166 last; 167 } 168 } 169 push @{$tles}, $tle if !$added; 170 } 171 $self->_update_positions(); 172} 173 174sub write { 175 my $self = shift; 176 my ($filename) = @_; 177 my $result = TRUE; 178 $filename = $self->{'filename'} if !defined $filename; 179 180 my $new_tapelist_file = $filename . "-new-" . time(); 181 182 open(my $fhn, ">", $new_tapelist_file) or die("Could not open '$new_tapelist_file' for writing: $!"); 183 for my $tle (@{$self->{tles}}) { 184 my $datestamp = $tle->{'datestamp'}; 185 my $label = $tle->{'label'}; 186 my $reuse = $tle->{'reuse'} ? 'reuse' : 'no-reuse'; 187 my $barcode = (defined $tle->{'barcode'})? (" BARCODE:" . $tle->{'barcode'}) : ''; 188 my $meta = (defined $tle->{'meta'})? (" META:" . $tle->{'meta'}) : ''; 189 my $blocksize = (defined $tle->{'blocksize'})? (" BLOCKSIZE:" . $tle->{'blocksize'}) : ''; 190 my $comment = (defined $tle->{'comment'})? (" #" . $tle->{'comment'}) : ''; 191 $result &&= print $fhn "$datestamp $label $reuse$barcode$meta$blocksize$comment\n"; 192 } 193 my $result_close = close($fhn); 194 $result &&= $result_close; 195 196 return if (!$result); 197 198 unless (move($new_tapelist_file, $filename)) { 199 die ("failed to rename '$new_tapelist_file' to '$filename': $!"); 200 } 201 202 # re-read from the C side to synchronize 203 C_read_tapelist($filename); 204 205 $self->unlock(); 206 207 return undef; 208} 209 210sub unlock { 211 my $self = shift; 212 213 return if !exists $self->{'fl'}; 214 215 $self->{'fl'}->unlock(); 216 delete $self->{'fl'} 217} 218 219## private methods 220 221sub _take_lock { 222 my $self = shift; 223 224 if (!-e $self->{'lockname'}) { 225 open(my $fhl, ">>", $self->{'lockname'}); 226 close($fhl); 227 } 228 my $fl = Amanda::Util::file_lock->new($self->{'lockname'}); 229 while(($r = $fl->lock()) == 1) { 230 sleep(1); 231 } 232 if ($r == 0) { 233 $self->{'fl'} = $fl; 234 } 235} 236 237sub _read_tapelist { 238 my $self = shift; 239 240 my @tles; 241 open(my $fh, "<", $self->{'filename'}) or return $self; 242 while (my $line = <$fh>) { 243 my ($datestamp, $label, $reuse, $barcode, $meta, $blocksize, $comment) 244 = $line =~ m/^([0-9]+)\s*([^\s]*)\s*(?:(reuse|no-reuse))?\s*(?:BARCODE:([^\s]*))?\s*(?:META:([^\s]*))?\s*(?:BLOCKSIZE:([^\s]*))?\s*(?:\#(.*))?$/mx; 245 if (!defined $datestamp) { 246 Amanda::Debug::critical("Bogus line in the tapelist ($self->{'filename'}) file: $line"); 247 } 248 push @tles, { 249 'datestamp' => $datestamp, 250 'label' => $label, 251 'reuse' => (!defined $reuse || $reuse eq 'reuse'), 252 'barcode' => $barcode, 253 'meta' => $meta, 254 'blocksize' => $blocksize, 255 'comment' => $comment, 256 }; 257 } 258 close($fh); 259 260 # sort in descending order by datestamp, sorting on position, too, to ensure 261 # that entries with the same datestamp stay in the right order 262 $self->{'tles'} = \@tles; 263 $self->_update_positions(); 264 @tles = sort { 265 $b->{'datestamp'} cmp $a->{'datestamp'} 266 || $a->{'position'} <=> $b->{'position'} 267 } @tles; 268 269 $self->{'tles'} = \@tles; 270 271 # and re-calculate the positions 272 $self->_update_positions(\@tles); 273 274 # check for duplicate labels 275 my %labels; 276 for my $tle (@{$self->{'tles'}}) { 277 my $label = $tle->{'label'}; 278 if (exists $labels{$label}) { 279 debug("Duplicate label '$label' in tapelist file."); 280 die("ERROR: Duplicate label '$label' in tapelist file."); 281 } 282 $labels{$label} = 1; 283 } 284} 285 286# update the 'position' key for each TLE 287sub _update_positions { 288 my $self = shift; 289 my $tles = $self->{'tles'}; 290 for (my $i = 0; $i < scalar @$tles; $i++) { 291 $tles->[$i]->{'position'} = $i+1; 292 } 293} 294 295%} 296 297char *get_last_reusable_tape_label(int skip); 298%newobject list_new_tapes; 299char *list_new_tapes(int nb); 300 301/* C functions -- should be called *only* from within this module */ 302 303%rename(C_read_tapelist) read_tapelist; 304int read_tapelist(char *tapefile); 305 306%rename(C_clear_tapelist) clear_tapelist; 307void clear_tapelist(void); 308