1# This file was automatically generated by SWIG (http://www.swig.org).
2# Version 3.0.7
3#
4# Do not make changes to this file unless you know what you are doing--modify
5# the SWIG interface file instead.
6
7package Amanda::Tapelist;
8use base qw(Exporter);
9use base qw(DynaLoader);
10package Amanda::Tapelistc;
11bootstrap Amanda::Tapelist;
12package Amanda::Tapelist;
13@EXPORT = qw();
14
15# ---------- BASE METHODS -------------
16
17package Amanda::Tapelist;
18
19sub TIEHASH {
20    my ($classname,$obj) = @_;
21    return bless $obj, $classname;
22}
23
24sub CLEAR { }
25
26sub FIRSTKEY { }
27
28sub NEXTKEY { }
29
30sub FETCH {
31    my ($self,$field) = @_;
32    my $member_func = "swig_${field}_get";
33    $self->$member_func();
34}
35
36sub STORE {
37    my ($self,$field,$newval) = @_;
38    my $member_func = "swig_${field}_set";
39    $self->$member_func($newval);
40}
41
42sub this {
43    my $ptr = shift;
44    return tied(%$ptr);
45}
46
47
48# ------- FUNCTION WRAPPERS --------
49
50package Amanda::Tapelist;
51
52*get_last_reusable_tape_label = *Amanda::Tapelistc::get_last_reusable_tape_label;
53*list_new_tapes = *Amanda::Tapelistc::list_new_tapes;
54*C_read_tapelist = *Amanda::Tapelistc::C_read_tapelist;
55*C_clear_tapelist = *Amanda::Tapelistc::C_clear_tapelist;
56
57# ------- VARIABLE STUBS --------
58
59package Amanda::Tapelist;
60
61
62@EXPORT_OK = ();
63%EXPORT_TAGS = ();
64
65
66=head1 NAME
67
68Amanda::Tapelist - manipulate the Amanda tapelist
69
70=head1 SYNOPSIS
71
72    use Amanda::Tapelist;
73
74    # to get a read only copy of the tapelist file:
75    my $tl = Amanda::Tapelist->new("/path/to/tapefile");
76
77    # to read/update/write the tapelist file
78    # read and take lock
79    my $tl = Amanda::Tapelist->new("/path/to/tapefile", 1);
80    # modify the memory copy
81    $tl->add_tapelabel($datestamp, $label);
82    $tl->add_tapelabel($datestamp2, $label2, $comment, 1);
83    # write it and unlock
84    $tl->write();
85
86    # If you already have a read only copy and want to modify it
87    # take a read only copy
88    my $tl = Amanda::Tapelist->new("/path/to/tapefile");
89    # reload and take lock
90    $tl->reload(1);
91    # modify the memory copy
92    tl->add_tapelabel($datestamp, $label);
93    $tl->add_tapelabel($datestamp2, $label2, $comment, 1);
94    # write it and unlock
95    $tl->write();
96
97=head1 OBJECT-ORIENTED INTERFACE
98
99C<new> returns a hash with no C<tles> set if the tapelist does
100not exist. C<tles> is an empty array if the tapelist is empty.
101Invalid entries are silently ignored.
102
103=head2 tapelist object
104
105A tapelist object is a hash with the following keys:
106
107=over
108
109=item C<filename>
110
111  The filename of the tapelist file.
112
113=item C<filename_lock>
114
115  The filename of the lock file.
116
117=item C<fl>
118
119  A Amanda::Util::file_lock is the file is locked.
120
121=item C<tles>
122
123A sequence of tapelist elements (referred to as TLEs in this document),
124sorted by datestamp from newest to oldest.
125
126=back
127
128=head2 tapelist element
129
130A tapelist elementas a hash with the following keys:
131
132=over
133
134=item C<position>
135
136the one-based position of the TLE in the tapelist
137
138=item C<datestamp>
139
140the datestamp on which this was written, or "0" for an unused tape
141
142=item C<reuse>
143
144true if this tape can be reused when it is no longer active
145
146=item C<label>
147
148tape label
149
150=item C<comment>
151
152the comment for this tape, or undef if no comment was given
153
154=back
155
156=head1 Method
157
158The following methods are available on a tapelist object C<$tl>:
159
160=over
161
162=item C<relod($lock)>
163
164reload the tapelist file, lock it if $lock is set
165
166=item C<lookup_tapelabel($lbl)>
167
168look up and return a reference to the TLE with the given label
169
170=item C<lookup_tapepos($pos)>
171
172look up and return a reference to the TLE in the given position
173
174=item C<lookup_tapedate($date)>
175
176look up and return a reference to the TLE with the given datestamp
177
178=item C<remove_tapelabel($lbl)>
179
180remove the tape with the given label
181
182=item C<add_tapelabel($date, $lbl, $comment, $reuse)>
183
184add a tape with the given date, label, comment and reuse to the end of the
185tapelist. reuse can be 1 or undef for a reusable volume, it must be 0 for
186a no-reusable volume.
187
188=item C<write()> or C<write($filename)>
189
190write the tapelist out to the same file as when read or to C<$filename> if it
191is set, remove the lock if a lock was taken
192
193=item C<unlock()>
194
195remove the lock if a lock was taken
196
197=item C<clear_tapelist()>
198
199remove all tle from the tles.
200
201=back
202
203=head1 INTERACTION WITH C CODE
204
205The C portions of Amanda treat the tapelist as a global variable,
206while this package treats it as an object (and can thus handle more
207than one tapelist simultaneously).  Every call to C<reload>
208fills this global variable with a copy of the tapelist, and likewise
209C<clear_tapelist> clears the global.  However, any changes made from
210Perl are not reflected in the C copy, nor are changes made by C
211modules reflected in the Perl copy.
212
213=cut
214
215
216
217use Amanda::Debug qw(:logging);
218use Amanda::Config qw( config_dir_relative );
219use File::Copy;
220use Fcntl qw(:flock); # import LOCK_* constants
221
222## package functions
223
224sub new {
225    my ($class)  = shift;
226    my ($filename, $lock ) = @_;
227    my $self = {
228	filename => $filename,
229	lockname => $filename . '.lock',
230    };
231    bless $self, $class;
232
233    $self->reload($lock);
234    return $self;
235}
236
237sub clear_tapelist {
238    my $self = shift;
239
240    # clear the C version
241    C_clear_tapelist();
242
243    $self->{'tles'} = [];
244
245    return $self;
246}
247
248## methods
249
250sub reload {
251    my $self = shift;
252    my ($lock) = @_;
253
254    if ($lock) {
255	$self->_take_lock();
256    }
257
258    # clear the C copy
259    C_clear_tapelist();
260
261    # let C read the file
262    C_read_tapelist($self->{'filename'});
263
264    $self->_read_tapelist();
265}
266
267sub lookup_tapelabel {
268    my $self = shift;
269    my ($label) = @_;
270
271    for my $tle (@{$self->{'tles'}}) {
272	return $tle if ($tle->{'label'} eq $label);
273    }
274
275    return undef;
276}
277
278sub lookup_by_barcode {
279    my $self = shift;
280    my ($barcode) = @_;
281
282    for my $tle (@{$self->{'tles'}}) {
283	return $tle if (defined $tle->{'barcode'} and
284			$tle->{'barcode'} eq $barcode);
285    }
286
287    return undef;
288}
289
290sub lookup_tapepos {
291    my $self = shift;
292    my ($position) = @_;
293
294    $self->_update_positions();
295    return $self->{'tles'}->[$position-1];
296}
297
298sub lookup_tapedate {
299    my $self = shift;
300    my ($datestamp) = @_;
301
302    for my $tle (@{$self->{'tles'}}) {
303	return $tle if ($tle->{'datestamp'} eq $datestamp);
304    }
305
306    return undef;
307}
308
309sub remove_tapelabel {
310    my $self = shift;
311    my ($label) = @_;
312
313    for (my $i = 0; $i < @{$self->{tles}}; $i++) {
314	if ($self->{tles}->[$i]->{'label'} eq $label) {
315	    splice @{$self->{tles}}, $i, 1;
316	    $self->_update_positions();
317	    return;
318	}
319    }
320}
321
322sub add_tapelabel {
323    my $self = shift;
324    my ($datestamp, $label, $comment, $reuse, $meta, $barcode, $blocksize) = @_;
325    $reuse = 1 if !defined $reuse;
326
327    # prepend this (presumably new) volume to the beginning of the list
328    my $tle = {
329        'datestamp' => $datestamp,
330        'label'     => $label,
331        'reuse'     => $reuse,
332        'barcode'   => $barcode,
333        'meta'      => $meta,
334        'blocksize' => $blocksize,
335        'comment'   => $comment,
336    };
337    my $tles = $self->{'tles'};
338    if (!defined $tles->[0] ||
339	$tles->[0]->{'datestamp'} le $datestamp) {
340	unshift @{$tles}, $tle;
341    } elsif (defined $tles->[0] &&
342	$tles->[@$tles-1]->{'datestamp'} gt $datestamp) {
343	push @{$tles}, $tle;
344    } else {
345	my $added = 0;
346	for my $i (0..(@$tles-1)) {
347	    if ($tles->[$i]->{'datestamp'} le $datestamp) {
348		splice @{$tles}, $i, 0, $tle;
349		$added = 1;
350		last;
351	    }
352	}
353	push @{$tles}, $tle if !$added;
354    }
355    $self->_update_positions();
356}
357
358sub write {
359    my $self = shift;
360    my ($filename) = @_;
361    my $result = TRUE;
362    $filename = $self->{'filename'} if !defined $filename;
363
364    my $new_tapelist_file = $filename . "-new-" . time();
365
366    open(my $fhn, ">", $new_tapelist_file) or die("Could not open '$new_tapelist_file' for writing: $!");
367    for my $tle (@{$self->{tles}}) {
368	my $datestamp = $tle->{'datestamp'};
369	my $label = $tle->{'label'};
370	my $reuse = $tle->{'reuse'} ? 'reuse' : 'no-reuse';
371	my $barcode = (defined $tle->{'barcode'})? (" BARCODE:" . $tle->{'barcode'}) : '';
372	my $meta = (defined $tle->{'meta'})? (" META:" . $tle->{'meta'}) : '';
373	my $blocksize = (defined $tle->{'blocksize'})? (" BLOCKSIZE:" . $tle->{'blocksize'}) : '';
374	my $comment = (defined $tle->{'comment'})? (" #" . $tle->{'comment'}) : '';
375	$result &&= print $fhn "$datestamp $label $reuse$barcode$meta$blocksize$comment\n";
376    }
377    my $result_close = close($fhn);
378    $result &&= $result_close;
379
380    return if (!$result);
381
382    unless (move($new_tapelist_file, $filename)) {
383	die ("failed to rename '$new_tapelist_file' to '$filename': $!");
384    }
385
386    # re-read from the C side to synchronize
387    C_read_tapelist($filename);
388
389    $self->unlock();
390
391    return undef;
392}
393
394sub unlock {
395    my $self = shift;
396
397    return if !exists $self->{'fl'};
398
399    $self->{'fl'}->unlock();
400    delete $self->{'fl'}
401}
402
403## private methods
404
405sub _take_lock {
406    my $self = shift;
407
408    if (!-e $self->{'lockname'}) {
409	open(my $fhl, ">>", $self->{'lockname'});
410	close($fhl);
411    }
412    my $fl = Amanda::Util::file_lock->new($self->{'lockname'});
413    while(($r = $fl->lock()) == 1) {
414	sleep(1);
415    }
416    if ($r == 0) {
417	$self->{'fl'} = $fl;
418    }
419}
420
421sub _read_tapelist {
422    my $self = shift;
423
424    my @tles;
425    open(my $fh, "<", $self->{'filename'}) or return $self;
426    while (my $line = <$fh>) {
427	my ($datestamp, $label, $reuse, $barcode, $meta, $blocksize, $comment)
428	    = $line =~ m/^([0-9]+)\s*([^\s]*)\s*(?:(reuse|no-reuse))?\s*(?:BARCODE:([^\s]*))?\s*(?:META:([^\s]*))?\s*(?:BLOCKSIZE:([^\s]*))?\s*(?:\#(.*))?$/mx;
429	if (!defined $datestamp) {
430	    Amanda::Debug::critical("Bogus line in the tapelist ($self->{'filename'}) file: $line");
431	}
432	push @tles, {
433	    'datestamp' => $datestamp,
434	    'label' => $label,
435	    'reuse' => (!defined $reuse || $reuse eq 'reuse'),
436	    'barcode' => $barcode,
437	    'meta' => $meta,
438	    'blocksize' => $blocksize,
439	    'comment' => $comment,
440	};
441    }
442    close($fh);
443
444    # sort in descending order by datestamp, sorting on position, too, to ensure
445    # that entries with the same datestamp stay in the right order
446    $self->{'tles'} = \@tles;
447    $self->_update_positions();
448    @tles = sort {
449	   $b->{'datestamp'} cmp $a->{'datestamp'}
450	|| $a->{'position'} <=> $b->{'position'}
451	} @tles;
452
453    $self->{'tles'} = \@tles;
454
455    # and re-calculate the positions
456    $self->_update_positions(\@tles);
457
458    # check for duplicate labels
459    my %labels;
460    for my $tle (@{$self->{'tles'}}) {
461	my $label = $tle->{'label'};
462	if (exists $labels{$label}) {
463	    debug("Duplicate label '$label' in tapelist file.");
464	    die("ERROR: Duplicate label '$label' in tapelist file.");
465	}
466	$labels{$label} = 1;
467    }
468}
469
470# update the 'position' key for each TLE
471sub _update_positions {
472    my $self = shift;
473    my $tles = $self->{'tles'};
474    for (my $i = 0; $i < scalar @$tles; $i++) {
475	$tles->[$i]->{'position'} = $i+1;
476    }
477}
478
4791;
480