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