File Coverage

blib/lib/Paranoid/IO/FileMultiplexer/Block/BATHeader.pm
Criterion Covered Total %
statement 145 183 79.2
branch 19 40 47.5
condition 7 9 77.7
subroutine 26 28 92.8
pod 10 10 100.0
total 207 270 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.10 2022/03/08 00:01:04 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   297 use 5.008;
  11         44  
35              
36 11     11   55 use strict;
  11         22  
  11         176  
37 11     11   44 use warnings;
  11         22  
  11         242  
38 11     11   44 use vars qw($VERSION);
  11         22  
  11         1265  
39 11     11   77 use base qw(Exporter);
  11         22  
  11         616  
40 11     11   55 use Paranoid;
  11         11  
  11         407  
41 11     11   55 use Paranoid::IO qw(:all);
  11         11  
  11         1705  
42 11     11   66 use Paranoid::Debug qw(:all);
  11         22  
  11         1408  
43 11     11   66 use Paranoid::Data;
  11         22  
  11         517  
44 11     11   44 use Fcntl qw(:DEFAULT :flock :mode :seek);
  11         22  
  11         4686  
45              
46             ($VERSION) = ( q$Revision: 2.10 $ =~ /(\d+(?:\.\d+)+)/sm );
47              
48 11     11   66 use base qw(Paranoid::IO::FileMultiplexer::Block);
  11         22  
  11         759  
49              
50             # Signature format:
51             # PIOFMBAT Name Sequence
52             # Z9 Z21 NNxx
53             # 40 bytes
54             #
55             # Data record format:
56             # BlockNum
57             # NN
58             # 8 bytes
59 11     11   66 use constant SIGNATURE => 'Z9Z21NNxx';
  11         11  
  11         495  
60 11     11   55 use constant SIG_LEN => 40;
  11         11  
  11         495  
61 11     11   55 use constant SIG_TYPE => 'PIOFMBAT';
  11         22  
  11         396  
62 11     11   44 use constant SEQ_POS => 30;
  11         22  
  11         484  
63 11     11   55 use constant DATA_POS => 40;
  11         22  
  11         484  
64 11     11   66 use constant DATAIDX => 'NN';
  11         22  
  11         385  
65 11     11   44 use constant DATA_LEN => 8;
  11         22  
  11         11847  
66              
67             #####################################################################
68             #
69             # Module code follows
70             #
71             #####################################################################
72              
73             sub new {
74              
75             # Purpose: Creates a new BAT header object
76             # Returns: Object reference/undef
77             # Usage: $obj =
78             # Paranoid::IO::FileMultiplexer::Block::BATHeader->new($file,
79             # $blockNo, $blockSize, $strmName, $sequenceNo);
80              
81 2231     2231 1 5140 my $class = shift;
82 2231         3937 my $file = shift;
83 2231         4516 my $bnum = shift;
84 2231         4019 my $bsize = shift;
85 2231         3434 my $sname = shift;
86 2231         3399 my $seq = shift;
87 2231         2956 my $self;
88              
89 2231         6503 subPreamble( PDLEVEL3, '$$$$$', $file, $bnum, $bsize, $sname, $seq );
90              
91 2231         8508 $self = __PACKAGE__->SUPER::new( $file, $bnum, $bsize );
92 2231 50       6683 if ( defined $self ) {
93 2231         5318 $$self{streamName} = $sname;
94 2231         5432 $$self{data} = []; # array of data blockNums
95 2231         6923 $$self{sequence} = 0; # sequence no of BAT
96 2231         9577 $$self{maxData} = int( ( $$self{blockSize} - SIG_LEN ) / DATA_LEN );
97             }
98              
99 2231         7218 subPostamble( PDLEVEL3, '$', $self );
100              
101 2231         6610 return $self;
102             }
103              
104             sub maxData {
105              
106             # Purpose: Returns the max data blocks for the BAT
107             # Returns: Integer
108             # Usage: $max = $obj->maxData;
109              
110 939     939 1 1800 my $self = shift;
111              
112 939         2935 return $$self{maxData};
113             }
114              
115             sub sequence {
116              
117             # Purpose: Returns the current BAT sequence number
118             # Returns: Integer
119             # Usage: $seq = $obj->sequence;
120              
121 70     70 1 178 my $self = shift;
122              
123 70         335 return $$self{sequence};
124             }
125              
126             sub dataBlocks {
127              
128             # Purpose: Returns an array of data block nums
129             # Returns: Array
130             # Usage: @data = $obj->dataBlocks;
131              
132 1307     1307 1 3848 my $self = shift;
133              
134 1307         2574 return @{ $$self{data} };
  1307         6431  
135             }
136              
137             sub full {
138              
139             # Purpose: Returns whether the BAT's array of data blocks is full
140             # Returns: Boolean
141             # Usage: $rv = $obj->full;
142              
143 0     0 1 0 my $self = shift;
144              
145 0         0 return $self->maxData == scalar $self->dataBlocks;
146             }
147              
148             sub writeSig {
149              
150             # Purpose: Writes the BAT signature to the file
151             # Returns: Boolean
152             # Usage: $rv = $obj->writeSig;
153              
154 55     55 1 187 my $self = shift;
155 55         121 my $file = $$self{file};
156 55         143 my $sname = $$self{streamName};
157 55         154 my $seq = $$self{sequence};
158 55         110 my $rv = 0;
159 55         209 my $sig = pack SIGNATURE, SIG_TYPE, $sname, quad2Longs($seq);
160              
161 55         220 subPreamble(PDLEVEL3);
162              
163 55         352 $rv = $self->bwrite($sig);
164              
165 55         308 subPostamble( PDLEVEL3, '$', $rv );
166              
167 55         429 return $rv;
168             }
169              
170             sub readSig {
171              
172             # Purpose: Reads the block signature from the file
173             # Returns: Boolean
174             # Usage: $rv = $obj->readSig;
175              
176 1248     1248 1 5517 my $self = shift;
177 1248         3019 my $file = $$self{file};
178 1248         1941 my $rv = 0;
179 1248         2404 my ( $raw, $type, $sname, $seq, $lseq, $useq );
180              
181 1248         3737 subPreamble(PDLEVEL3);
182              
183 1248 50       4078 if ( pflock( $file, LOCK_SH ) ) {
184 1248 100       5261 if ( $self->bread( \$raw, 0, SIG_LEN ) == SIG_LEN ) {
185 1237         3067 $rv = 1;
186              
187             # Unpack the signature
188 1237         10654 ( $type, $sname, $lseq, $useq ) = unpack SIGNATURE, $raw;
189              
190             # Validate contents
191             #
192             # Start with file type
193 1237 50       5527 unless ( $type eq SIG_TYPE ) {
194 0         0 $rv = 0;
195 0         0 pdebug( 'Invalid BAT header type (%s)', PDLEVEL1, $type );
196             }
197              
198             # stream name
199 1237 50       4648 unless ( $sname eq $$self{streamName} ) {
200 0         0 $rv = 0;
201 0         0 pdebug( 'Invalid stream name (%s)', PDLEVEL1, $sname );
202             }
203              
204             # Make sure seq is legitimate
205 1237         4563 $seq = longs2Quad( $lseq, $useq );
206 1237 50       4526 unless ( defined $seq ) {
207 0         0 pdebug(
208             'this platform does not support 64b values for sequence',
209             PDLEVEL1
210             );
211 0         0 $rv = 0;
212             }
213 1237 50       3854 unless ( $seq == $$self{sequence} ) {
214 0         0 pdebug( 'Invalid sequence number for BAT (%s)',
215             PDLEVEL1, $seq );
216 0         0 $rv = 0;
217             }
218              
219             # Update internal values
220 1237 50       3355 pdebug( 'BAT signature verification failure', PDLEVEL1 )
221             unless $rv;
222              
223             } else {
224 11         44 pdebug( 'failed to read BAT header signature', PDLEVEL1 );
225             }
226              
227 1248         4058 pflock( $file, LOCK_UN );
228             }
229              
230 1248         4841 subPostamble( PDLEVEL3, '$', $rv );
231              
232 1248         11226 return $rv;
233             }
234              
235             sub writeData {
236              
237             # Purpose: Writes all the data block numbers to the file
238             # Returns: Boolean
239             # Usage: $rv = $obj->writeData;
240              
241 0     0 1 0 my $self = shift;
242 0         0 my $file = $$self{file};
243 0         0 my $rv = 0;
244 0         0 my ( $rec, $i, $pos, $maxbats );
245              
246 0         0 subPreamble(PDLEVEL3);
247              
248             # Hold an exclusive lock for the entire transaction
249 0 0       0 if ( pflock( $file, LOCK_EX ) ) {
250              
251             # Calculate the maximum possible number of BATs
252 0         0 $maxbats = int( ( $$self{blockSize} - SIG_LEN ) / DATA_LEN );
253              
254 0         0 $rv = 1;
255 0         0 $i = 0;
256 0         0 foreach $rec ( @{ $$self{data} } ) {
  0         0  
257 0         0 $pos = DATA_POS + $i * DATA_LEN;
258 0 0       0 $rv = 0
259             unless $self->bwrite( pack( DATAIDX, quad2Longs($rec) ),
260             $pos ) == DATA_LEN;
261 0         0 $i++;
262 0 0       0 last unless $rv;
263             }
264              
265 0         0 pflock( $file, LOCK_UN );
266             }
267              
268 0 0       0 pdebug( 'failed to write all data block numbers to the BAT header',
269             PDLEVEL1 )
270             unless $rv;
271              
272 0         0 subPostamble( PDLEVEL3, '$', $rv );
273              
274 0         0 return $rv;
275             }
276              
277             sub readData {
278              
279             # Purpose: Reads the data block numbers from the BAT header
280             # Returns: Boolean
281             # Usage: $rv = $obj->readData;
282              
283 1237     1237 1 2532 my $self = shift;
284 1237         1963 my $rv = 1;
285 1237         3341 my ( $raw, @sraw, $bn, $lbn, $ubn, $prev );
286 1237         0 my @data;
287              
288 1237         3291 subPreamble(PDLEVEL3);
289              
290             # Read the BATs section of the block
291 1237 50       4696 if ( $self->bread( \$raw, DATA_POS ) ) {
292              
293 1237         621492 @sraw = unpack '(' . DATAIDX . ")$$self{maxData}", $raw;
294 1237         87938 while (@sraw) {
295              
296 3910         7943 $lbn = shift @sraw;
297 3910         7515 $ubn = shift @sraw;
298 3910         11099 $bn = longs2Quad( $lbn, $ubn );
299              
300             # Stop processing when it looks like we're not getting legitmate
301             # values
302 3910 100 66     19237 last unless defined $bn and $bn > $$self{blockNum};
303              
304             # Error out if block numbers aren't ascending
305 2673 50 66     10513 unless ( !defined $prev or $bn > $prev ) {
306 0         0 pdebug( 'data block number appearing out of sequence',
307             PDLEVEL1 );
308 0         0 $rv = 0;
309 0         0 last;
310             }
311              
312             # Save entry
313 2673         5563 push @data, $bn;
314 2673         7080 $prev = $bn;
315             }
316              
317             # Save everything extracted
318 1237         4220 $$self{data} = [@data];
319 1237         4006 pdebug( 'found %s data blocks', PDLEVEL4, scalar @data );
320              
321             } else {
322 0         0 pdebug( 'failed to read list of data blocks from BAT header',
323             PDLEVEL1 );
324 0         0 $rv = 0;
325             }
326              
327 1237         5756 subPostamble( PDLEVEL3, '$', $rv );
328              
329 1237         85830 return $rv;
330             }
331              
332             sub addData {
333              
334             # Purpose: Adds a data block number to the BAT header
335             # Returns: Boolean
336             # Usage: $rv = $obj->addData($bn);
337              
338 147     147 1 341 my $self = shift;
339 147         310 my $bn = shift;
340 147         238 my $rv = 1;
341 147         217 my $n;
342              
343 147         446 subPreamble( PDLEVEL3, '$', $bn );
344              
345 147 100 100     1088 if ( defined $bn and $bn > $$self{blockNum} ) {
346              
347             # Make sure we're not adding redundant entries
348 125 50       276 if ( scalar grep { $_ eq $bn } @{ $$self{data} } ) {
  149         449  
  125         434  
349 0         0 $rv = 0;
350 0         0 pdebug( 'redundant entry for an existing data block', PDLEVEL1 );
351             }
352              
353             # Make sure new data block is a higher block number than all previous
354             # data blocks
355 125 50       217 if ( scalar grep { $_ > $bn } @{ $$self{data} } ) {
  149         312  
  125         373  
356 0         0 $rv = 0;
357 0         0 pdebug( 'data block number is lower than previous blocks',
358             PDLEVEL1 );
359             }
360              
361 125 50       342 if ($rv) {
362              
363             # Write the block to the header
364 125         182 push @{ $$self{data} }, $bn;
  125         379  
365             $rv = 0
366             unless $self->bwrite(
367             pack( DATAIDX, quad2Longs($bn) ),
368 125 50       448 DATA_POS + DATA_LEN * $#{ $$self{data} } ) == DATA_LEN;
  125         646  
369             }
370              
371             } else {
372 22         77 pdebug( 'invalid data block number (%s)', PDLEVEL1, $bn );
373 22         44 $rv = 0;
374             }
375              
376 147         578 subPostamble( PDLEVEL3, '$', $rv );
377              
378 147         409 return $rv;
379             }
380              
381             1;
382              
383             __END__