File Coverage

blib/lib/Paranoid/IO/FileMultiplexer/Block/BATHeader.pm
Criterion Covered Total %
statement 152 192 79.1
branch 19 40 47.5
condition 7 9 77.7
subroutine 25 27 92.5
pod 10 10 100.0
total 213 278 76.6


line stmt bran cond sub pod time code
1             # Paranoid::IO::FileMultiplexer::Block::BATHeader -- BAT Header Block
2             #
3             # $Id: lib/Paranoid/IO/FileMultiplexer/Block/BATHeader.pm, 2.09 2021/12/28 15:46:49 acorliss Exp $
4             #
5             # This software is free software. Similar to Perl, you can redistribute it
6             # and/or modify it under the terms of either:
7             #
8             # a) the GNU General Public License
9             # as published by the
10             # Free Software Foundation ; either version 1
11             # , or any later version
12             # , or
13             # b) the Artistic License 2.0
14             # ,
15             #
16             # subject to the following additional term: No trademark rights to
17             # "Paranoid" have been or are conveyed under any of the above licenses.
18             # However, "Paranoid" may be used fairly to describe this unmodified
19             # software, in good faith, but not as a trademark.
20             #
21             # (c) 2005 - 2021, Arthur Corliss (corliss@digitalmages.com)
22             # (tm) 2008 - 2021, Paranoid Inc. (www.paranoid.com)
23             #
24             #####################################################################
25              
26             #####################################################################
27             #
28             # Environment definitions
29             #
30             #####################################################################
31              
32             package Paranoid::IO::FileMultiplexer::Block::BATHeader;
33              
34 11     11   198 use 5.008;
  11         55  
35              
36 11     11   66 use strict;
  11         55  
  11         220  
37 11     11   55 use warnings;
  11         22  
  11         286  
38 11     11   55 use vars qw($VERSION);
  11         22  
  11         429  
39 11     11   66 use base qw(Exporter);
  11         22  
  11         759  
40 11     11   66 use Paranoid;
  11         22  
  11         704  
41 11     11   77 use Paranoid::IO qw(:all);
  11         22  
  11         2090  
42 11     11   110 use Paranoid::Debug qw(:all);
  11         22  
  11         1804  
43 11     11   77 use Fcntl qw(:DEFAULT :flock :mode :seek);
  11         22  
  11         6006  
44              
45             ($VERSION) = ( q$Revision: 2.09 $ =~ /(\d+(?:\.\d+)+)/sm );
46              
47 11     11   88 use base qw(Paranoid::IO::FileMultiplexer::Block);
  11         22  
  11         770  
48              
49             # Signature format:
50             # PIOFMBAT Name Sequence
51             # Z9 Z21 NNxx
52             # 40 bytes
53             #
54             # Data record format:
55             # BlockNum
56             # NN
57             # 8 bytes
58 11     11   66 use constant SIGNATURE => 'Z9Z21NNxx';
  11         11  
  11         726  
59 11     11   77 use constant SIG_LEN => 40;
  11         22  
  11         792  
60 11     11   77 use constant SIG_TYPE => 'PIOFMBAT';
  11         22  
  11         506  
61 11     11   242 use constant SEQ_POS => 30;
  11         22  
  11         627  
62 11     11   77 use constant DATA_POS => 40;
  11         22  
  11         638  
63 11     11   55 use constant DATAIDX => 'NN';
  11         22  
  11         836  
64 11     11   77 use constant DATA_LEN => 8;
  11         22  
  11         20108  
65              
66             #####################################################################
67             #
68             # Module code follows
69             #
70             #####################################################################
71              
72             sub new {
73              
74             # Purpose: Creates a new BAT header object
75             # Returns: Object reference/undef
76             # Usage: $obj =
77             # Paranoid::IO::FileMultiplexer::Block::BATHeader->new($file,
78             # $blockNo, $blockSize, $strmName, $sequenceNo);
79              
80 2225     2225 1 5487 my $class = shift;
81 2225         3553 my $file = shift;
82 2225         4218 my $bnum = shift;
83 2225         3984 my $bsize = shift;
84 2225         3612 my $sname = shift;
85 2225         3443 my $seq = shift;
86 2225         3513 my $self;
87              
88 2225         6155 pdebug( 'entering w/(%s)(%s)(%s)(%s)(%s)',
89             PDLEVEL3, $file, $bnum, $bsize, $sname, $seq );
90 2225         6134 pIn();
91              
92 2225         9030 $self = __PACKAGE__->SUPER::new( $file, $bnum, $bsize );
93 2225 50       6045 if ( defined $self ) {
94 2225         6013 $$self{streamName} = $sname;
95 2225         4861 $$self{data} = []; # array of data blockNums
96 2225         7888 $$self{sequence} = 0; # sequence no of BAT
97 2225         9577 $$self{maxData} = int( ( $$self{blockSize} - SIG_LEN ) / DATA_LEN );
98             }
99              
100 2225         6603 pOut();
101 2225         6595 pdebug( 'leaving w/rv: %s', PDLEVEL3, $self );
102              
103 2225         7712 return $self;
104             }
105              
106             sub maxData {
107              
108             # Purpose: Returns the max data blocks for the BAT
109             # Returns: Integer
110             # Usage: $max = $obj->maxData;
111              
112 933     933 1 2026 my $self = shift;
113              
114 933         3137 return $$self{maxData};
115             }
116              
117             sub sequence {
118              
119             # Purpose: Returns the current BAT sequence number
120             # Returns: Integer
121             # Usage: $seq = $obj->sequence;
122              
123 70     70 1 141 my $self = shift;
124              
125 70         364 return $$self{sequence};
126             }
127              
128             sub dataBlocks {
129              
130             # Purpose: Returns an array of data block nums
131             # Returns: Array
132             # Usage: @data = $obj->dataBlocks;
133              
134 1307     1307 1 2654 my $self = shift;
135              
136 1307         2455 return @{ $$self{data} };
  1307         6354  
137             }
138              
139             sub full {
140              
141             # Purpose: Returns whether the BAT's array of data blocks is full
142             # Returns: Boolean
143             # Usage: $rv = $obj->full;
144              
145 0     0 1 0 my $self = shift;
146              
147 0         0 return $self->maxData == scalar $self->dataBlocks;
148             }
149              
150             sub writeSig {
151              
152             # Purpose: Writes the BAT signature to the file
153             # Returns: Boolean
154             # Usage: $rv = $obj->writeSig;
155              
156 55     55 1 165 my $self = shift;
157 55         187 my $file = $$self{file};
158 55         110 my $sname = $$self{streamName};
159 55         143 my $seq = $$self{sequence};
160 55         132 my $rv = 0;
161 55         286 my $sig = pack SIGNATURE, SIG_TYPE, $sname, $self->splitInt($seq);
162              
163 55         275 pdebug( 'entering', PDLEVEL3 );
164 55         209 pIn();
165              
166 55         209 $rv = $self->bwrite($sig);
167              
168 55         209 pOut();
169 55         187 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
170              
171 55         473 return $rv;
172             }
173              
174             sub readSig {
175              
176             # Purpose: Reads the block signature from the file
177             # Returns: Boolean
178             # Usage: $rv = $obj->readSig;
179              
180 1248     1248 1 7408 my $self = shift;
181 1248         2600 my $file = $$self{file};
182 1248         2010 my $rv = 0;
183 1248         2569 my ( $raw, $type, $sname, $seq, $lseq, $useq );
184              
185 1248         3815 pdebug( 'entering', PDLEVEL3 );
186 1248         3692 pIn();
187              
188 1248 50       2950 if ( pflock( $file, LOCK_SH ) ) {
189 1248 100       5494 if ( $self->bread( \$raw, 0, SIG_LEN ) == SIG_LEN ) {
190 1237         2940 $rv = 1;
191              
192             # Unpack the signature
193 1237         10832 ( $type, $sname, $lseq, $useq ) = unpack SIGNATURE, $raw;
194              
195             # Validate contents
196             #
197             # Start with file type
198 1237 50       5813 unless ( $type eq SIG_TYPE ) {
199 0         0 $rv = 0;
200 0         0 pdebug( 'Invalid BAT header type (%s)', PDLEVEL1, $type );
201             }
202              
203             # stream name
204 1237 50       4380 unless ( $sname eq $$self{streamName} ) {
205 0         0 $rv = 0;
206 0         0 pdebug( 'Invalid stream name (%s)', PDLEVEL1, $sname );
207             }
208              
209             # Make sure seq is legitimate
210 1237         5075 $seq = $self->joinInt( $lseq, $useq );
211 1237 50       4009 unless ( defined $seq ) {
212 0         0 pdebug(
213             'this platform does not support 64b values for sequence',
214             PDLEVEL1
215             );
216 0         0 $rv = 0;
217             }
218 1237 50       3813 unless ( $seq == $$self{sequence} ) {
219 0         0 pdebug( 'Invalid sequence number for BAT (%s)',
220             PDLEVEL1, $seq );
221 0         0 $rv = 0;
222             }
223              
224             # Update internal values
225 1237 50       3632 pdebug( 'BAT signature verification failure', PDLEVEL1 )
226             unless $rv;
227              
228             } else {
229 11         55 pdebug( 'failed to read BAT header signature', PDLEVEL1 );
230             }
231              
232 1248         3488 pflock( $file, LOCK_UN );
233             }
234              
235 1248         3558 pOut();
236 1248         3441 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
237              
238 1248         10174 return $rv;
239             }
240              
241             sub writeData {
242              
243             # Purpose: Writes all the data block numbers to the file
244             # Returns: Boolean
245             # Usage: $rv = $obj->writeData;
246              
247 0     0 1 0 my $self = shift;
248 0         0 my $file = $$self{file};
249 0         0 my $rv = 0;
250 0         0 my ( $rec, $i, $pos, $maxbats );
251              
252 0         0 pdebug( 'entering', PDLEVEL3 );
253 0         0 pIn();
254              
255             # Hold an exclusive lock for the entire transaction
256 0 0       0 if ( pflock( $file, LOCK_EX ) ) {
257              
258             # Calculate the maximum possible number of BATs
259 0         0 $maxbats = int( ( $$self{blockSize} - SIG_LEN ) / DATA_LEN );
260              
261 0         0 $rv = 1;
262 0         0 $i = 0;
263 0         0 foreach $rec ( @{ $$self{data} } ) {
  0         0  
264 0         0 $pos = DATA_POS + $i * DATA_LEN;
265 0 0       0 $rv = 0
266             unless $self->bwrite( pack( DATAIDX, $self->splitInt($rec) ),
267             $pos ) == DATA_LEN;
268 0         0 $i++;
269 0 0       0 last unless $rv;
270             }
271              
272 0         0 pflock( $file, LOCK_UN );
273             }
274              
275 0 0       0 pdebug( 'failed to write all data block numbers to the BAT header',
276             PDLEVEL1 )
277             unless $rv;
278              
279 0         0 pOut();
280 0         0 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
281              
282 0         0 return $rv;
283             }
284              
285             sub readData {
286              
287             # Purpose: Reads the data block numbers from the BAT header
288             # Returns: Boolean
289             # Usage: $rv = $obj->readData;
290              
291 1237     1237 1 2678 my $self = shift;
292 1237         2199 my $rv = 1;
293 1237         3292 my ( $raw, @sraw, $bn, $lbn, $ubn, $prev );
294 1237         0 my @data;
295              
296 1237         3634 pdebug( 'entering', PDLEVEL3 );
297 1237         3126 pIn();
298              
299             # Read the BATs section of the block
300 1237 50       4411 if ( $self->bread( \$raw, DATA_POS ) ) {
301              
302 1237         588027 @sraw = unpack '(' . DATAIDX . ")$$self{maxData}", $raw;
303 1237         96034 while (@sraw) {
304              
305 3896         8560 $lbn = shift @sraw;
306 3896         7950 $ubn = shift @sraw;
307 3896         11685 $bn = $self->joinInt( $lbn, $ubn );
308              
309             # Stop processing when it looks like we're not getting legitmate
310             # values
311 3896 100 66     20722 last unless defined $bn and $bn > $$self{blockNum};
312              
313             # Error out if block numbers aren't ascending
314 2659 50 66     10613 unless ( !defined $prev or $bn > $prev ) {
315 0         0 pdebug( 'data block number appearing out of sequence',
316             PDLEVEL1 );
317 0         0 $rv = 0;
318 0         0 last;
319             }
320              
321             # Save entry
322 2659         5900 push @data, $bn;
323 2659         7683 $prev = $bn;
324             }
325              
326             # Save everything extracted
327 1237         4251 $$self{data} = [@data];
328 1237         4268 pdebug( 'found %s data blocks', PDLEVEL4, scalar @data );
329              
330             } else {
331 0         0 pdebug( 'failed to read list of data blocks from BAT header',
332             PDLEVEL1 );
333 0         0 $rv = 0;
334             }
335              
336 1237         4468 pOut();
337 1237         3170 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
338              
339 1237         97053 return $rv;
340             }
341              
342             sub addData {
343              
344             # Purpose: Adds a data block number to the BAT header
345             # Returns: Boolean
346             # Usage: $rv = $obj->addData($bn);
347              
348 147     147 1 365 my $self = shift;
349 147         329 my $bn = shift;
350 147         292 my $rv = 1;
351 147         283 my $n;
352              
353 147         463 pdebug( 'entering w/(%s)', PDLEVEL3, $bn );
354 147         566 pIn();
355              
356 147 100 100     963 if ( defined $bn and $bn > $$self{blockNum} ) {
357              
358             # Make sure we're not adding redundant entries
359 125 50       276 if ( scalar grep { $_ eq $bn } @{ $$self{data} } ) {
  149         514  
  125         559  
360 0         0 $rv = 0;
361 0         0 pdebug( 'redundant entry for an existing data block', PDLEVEL1 );
362             }
363              
364             # Make sure new data block is a higher block number than all previous
365             # data blocks
366 125 50       273 if ( scalar grep { $_ > $bn } @{ $$self{data} } ) {
  149         358  
  125         716  
367 0         0 $rv = 0;
368 0         0 pdebug( 'data block number is lower than previous blocks',
369             PDLEVEL1 );
370             }
371              
372 125 50       406 if ($rv) {
373              
374             # Write the block to the header
375 125         215 push @{ $$self{data} }, $bn;
  125         376  
376             $rv = 0
377             unless $self->bwrite(
378             pack( DATAIDX, $self->splitInt($bn) ),
379 125 50       545 DATA_POS + DATA_LEN * $#{ $$self{data} } ) == DATA_LEN;
  125         616  
380             }
381              
382             } else {
383 22         99 pdebug( 'invalid data block number (%s)', PDLEVEL1, $bn );
384 22         121 $rv = 0;
385             }
386              
387 147         599 pOut();
388 147         441 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
389              
390 147         507 return $rv;
391             }
392              
393             1;
394              
395             __END__