File Coverage

blib/lib/Paranoid/IO/FileMultiplexer/Block/StreamHeader.pm
Criterion Covered Total %
statement 169 209 80.8
branch 25 50 50.0
condition 10 18 55.5
subroutine 30 32 93.7
pod 14 14 100.0
total 248 323 76.7


line stmt bran cond sub pod time code
1             # Paranoid::IO::FileMultiplexer::Block::StreamHeader -- Stream Header Block
2             #
3             # $Id: lib/Paranoid/IO/FileMultiplexer/Block/StreamHeader.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::StreamHeader;
33              
34 11     11   154 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         231  
38 11     11   44 use vars qw($VERSION);
  11         33  
  11         341  
39 11     11   44 use base qw(Exporter);
  11         22  
  11         737  
40 11     11   55 use Paranoid qw(:all);
  11         22  
  11         1001  
41 11     11   66 use Paranoid::IO qw(:all);
  11         22  
  11         1529  
42 11     11   66 use Paranoid::Debug qw(:all);
  11         22  
  11         1881  
43 11     11   88 use Paranoid::Data;
  11         22  
  11         671  
44 11     11   55 use Fcntl qw(:DEFAULT :flock :mode :seek);
  11         33  
  11         4411  
45              
46             ($VERSION) = ( q$Revision: 2.10 $ =~ /(\d+(?:\.\d+)+)/sm );
47              
48 11     11   66 use base qw(Paranoid::IO::FileMultiplexer::Block);
  11         11  
  11         737  
49              
50             # Signature format:
51             # PIOFMSTRM Name EOS
52             # Z10 Z21 NNx
53             # 40 bytes
54             #
55             # BAT record format:
56             # BlockNum
57             # NN
58             # 8 bytes
59 11     11   55 use constant SIGNATURE => 'Z10Z21NNx';
  11         22  
  11         583  
60 11     11   66 use constant SIG_LEN => 40;
  11         22  
  11         418  
61 11     11   44 use constant SIG_TYPE => 'PIOFMSTRM';
  11         22  
  11         462  
62 11     11   66 use constant EOS_POS => 31;
  11         22  
  11         385  
63 11     11   55 use constant BATS_POS => 40;
  11         22  
  11         484  
64 11     11   55 use constant BATIDX => 'NN';
  11         22  
  11         495  
65 11     11   66 use constant BAT_LEN => 8;
  11         22  
  11         18370  
66              
67             #####################################################################
68             #
69             # Module code follows
70             #
71             #####################################################################
72              
73             sub new {
74              
75             # Purpose: Creates a new stream header object
76             # Returns: Object reference/undef
77             # Usage: $obj =
78             # Paranoid::IO::FileMultiplexer::Block::StreamHeader->new($file,
79             # $blockNo, $blockSize, $strmName);
80              
81 207     207 1 10574 my $class = shift;
82 207         361 my $file = shift;
83 207         387 my $bnum = shift;
84 207         437 my $bsize = shift;
85 207         500 my $sname = shift;
86 207         324 my $self;
87              
88 207         768 subPreamble( PDLEVEL3, '$$$$', $file, $bnum, $bsize, $sname );
89              
90 207 50 33     1388 if ( defined $sname and length $sname and length $sname <= 20 ) {
      33        
91 207         835 $self = __PACKAGE__->SUPER::new( $file, $bnum, $bsize );
92             } else {
93 0         0 pdebug( 'invalid stream name (%s)', PDLEVEL1, $sname );
94             }
95              
96 207 50       695 if ( defined $self ) {
97 207         508 $$self{streamName} = $sname;
98 207         491 $$self{bats} = []; # array of blockNum
99 207         638 $$self{eos} = 0; # address of stream EOF
100 207         962 $$self{maxBATs} = int( ( $$self{blockSize} - SIG_LEN ) / BAT_LEN );
101             }
102              
103 207         677 subPostamble( PDLEVEL3, '$', $self );
104              
105 207         567 return $self;
106             }
107              
108             sub streamName {
109              
110             # Purpose: Returns the current stream name
111             # Returns: String
112             # Usage: $name = $obj->streamName;
113              
114 11     11 1 4224 my $self = shift;
115              
116 11         77 return $$self{streamName};
117             }
118              
119             sub maxBATs {
120              
121             # Purpose: Returns the max BAT blocks for the stream
122             # Returns: Integer
123             # Usage: $max = $obj->maxBATs;
124              
125 74     74 1 182 my $self = shift;
126              
127 74         261 return $$self{maxBATs};
128             }
129              
130             sub eos {
131              
132             # Purpose: Returns the current stream EOS
133             # Returns: Integer
134             # Usage: $eos = $obj->eos;
135              
136 1178     1178 1 2429 my $self = shift;
137              
138 1178         5852 return $$self{eos};
139             }
140              
141             sub bats {
142              
143             # Purpose: Returns an array of bat nums
144             # Returns: Hash
145             # Usage: @bats = $obj->bats;
146              
147 1836     1836 1 3168 my $self = shift;
148              
149 1836         2521 return @{ $$self{bats} };
  1836         7508  
150             }
151              
152             sub full {
153              
154             # Purpose: Returns whether the streams's array of BAT blocks is full
155             # Returns: Boolean
156             # Usage: $rv = $obj->full;
157              
158 0     0 1 0 my $self = shift;
159              
160 0         0 return $self->maxBATs == scalar $self->bats;
161             }
162              
163             sub writeSig {
164              
165             # Purpose: Writes the stream signature to the file
166             # Returns: Boolean
167             # Usage: $rv = $obj->writeSig;
168              
169 55     55 1 143 my $self = shift;
170 55         154 my $file = $$self{file};
171 55         132 my $sname = $$self{streamName};
172 55         110 my $eos = $$self{eos};
173 55         110 my $rv = 0;
174 55         264 my $sig = pack SIGNATURE, SIG_TYPE, $sname, quad2Longs($eos);
175              
176 55         220 subPreamble(PDLEVEL3);
177              
178 55         330 $rv = $self->bwrite($sig);
179              
180 55         638 subPostamble( PDLEVEL3, '$', $rv );
181              
182 55         429 return $rv;
183             }
184              
185             sub readSig {
186              
187             # Purpose: Reads the block signature from the file
188             # Returns: Boolean
189             # Usage: $rv = $obj->readSig;
190              
191 89     89 1 3048 my $self = shift;
192 89         269 my $file = $$self{file};
193 89         276 my $rv = 0;
194 89         221 my ( $raw, $type, $sname, $eos, $leos, $ueos );
195              
196 89         296 subPreamble(PDLEVEL3);
197              
198 89 50       305 if ( pflock( $file, LOCK_SH ) ) {
199 89 100       571 if ( $self->bread( \$raw, 0, SIG_LEN ) == SIG_LEN ) {
200 67         198 $rv = 1;
201              
202             # Unpack the signature
203 67         641 ( $type, $sname, $leos, $ueos ) = unpack SIGNATURE, $raw;
204              
205             # Validate contents
206             #
207             # Start with file type
208 67 50       350 unless ( $type eq SIG_TYPE ) {
209 0         0 $rv = 0;
210 0         0 pdebug( 'Invalid stream header type (%s)', PDLEVEL1, $type );
211             }
212              
213             # stream name
214 67 50       317 unless ( $sname eq $$self{streamName} ) {
215 0         0 $rv = 0;
216 0         0 pdebug( 'Invalid stream name (%s)', PDLEVEL1, $sname );
217             }
218              
219             # Make sure eos is legitimate
220 67         251 $eos = longs2Quad( $leos, $ueos );
221 67 50       262 unless ( defined $eos ) {
222 0         0 pdebug( 'this platform does not support 64b values for eos',
223             PDLEVEL1 );
224 0         0 $rv = 0;
225             }
226              
227             # Update internal values
228 67 50       191 if ($rv) {
229 67         205 $$self{eos} = $eos;
230             } else {
231 0         0 pdebug( 'stream signature verification failure', PDLEVEL1 );
232             }
233              
234             } else {
235 22         99 pdebug( 'failed to read stream header signature', PDLEVEL1 );
236             }
237              
238 89         399 pflock( $file, LOCK_UN );
239             }
240              
241 89         315 subPostamble( PDLEVEL3, '$', $rv );
242              
243 89         865 return $rv;
244             }
245              
246             sub writeEOS {
247              
248             # Purpose: Updates the EOS counter and writes it to disk
249             # Returns: Boolean
250             # Usage: $rv = $obj->writeEOS($pos);
251              
252 566     566 1 1638 my $self = shift;
253 566         1198 my $eos = shift;
254 566         1644 my ( $raw, $rv );
255              
256 566         2030 subPreamble( PDLEVEL3, '$', $eos );
257              
258 566 50       2271 if ( defined $eos ) {
259 566         2650 $raw = pack 'NN', quad2Longs($eos);
260 566 50       2810 if ( $self->bwrite( $raw, EOS_POS ) == 8 ) {
261 566         2139 $$self{eos} = $eos;
262 566         1405 $rv = 1;
263             }
264             } else {
265 0         0 pdebug( 'invalid value for eos (%s)', PDLEVEL1, $eos );
266             }
267              
268 566         2350 subPostamble( PDLEVEL3, '$', $rv );
269              
270 566         2542 return $rv;
271             }
272              
273             sub readEOS {
274              
275             # Purpose: Reads the EOS counter from disk
276             # Returns: Integer/undef on error
277             # Usage: $pos = $obj->readEOS;
278              
279 2959     2959 1 5094 my $self = shift;
280 2959         4635 my ( $rv, $raw );
281              
282 2959         7531 subPreamble(PDLEVEL3);
283              
284 2959 50       11179 if ( $self->bread( \$raw, EOS_POS, 8 ) == 8 ) {
285 2959         16653 $rv = longs2Quad( unpack 'NN', $raw );
286 2959 100 66     16190 $rv = PTRUE_ZERO if defined $rv and $rv == 0;
287             }
288              
289 2959         10237 subPostamble( PDLEVEL3, '$', $rv );
290              
291 2959         9993 return $rv;
292             }
293              
294             sub validateEOS {
295              
296             # Purpose: Compares in-memory EOS counter to what's stored in the file
297             # Returns: Boolean
298             # Usage: $rv = $obj->validateBlocks;
299              
300 2959     2959 1 5447 my $self = shift;
301 2959         4737 my $rv = 0;
302              
303 2959         8081 subPreamble(PDLEVEL3);
304              
305 2959 50       9072 $rv = 1 if $$self{eos} == $self->readEOS;
306              
307 2959         8640 subPostamble( PDLEVEL3, '$', $rv );
308              
309 2959         9432 return $rv;
310             }
311              
312             sub writeBATs {
313              
314             # Purpose: Writes all the BAT block numbers to the file
315             # Returns: Boolean
316             # Usage: $rv = $obj->writeBATs;
317              
318 0     0 1 0 my $self = shift;
319 0         0 my $file = $$self{file};
320 0         0 my $rv = 0;
321 0         0 my ( $rec, $i, $pos );
322              
323 0         0 subPreamble(PDLEVEL3);
324              
325             # Hold an exclusive lock for the entire transaction
326 0 0       0 if ( pflock( $file, LOCK_EX ) ) {
327              
328 0         0 $rv = 1;
329 0         0 $i = 0;
330 0         0 foreach $rec ( @{ $$self{bats} } ) {
  0         0  
331 0         0 $pos = BATS_POS + $i * BAT_LEN;
332 0 0       0 $rv = 0
333             unless $self->bwrite( pack( BATIDX, quad2Longs($rec) ), $pos )
334             == BAT_LEN;
335 0         0 $i++;
336 0 0       0 last unless $rv;
337             }
338              
339 0         0 pflock( $file, LOCK_UN );
340             }
341              
342 0 0       0 pdebug( 'failed to write all BAT block numbers to the stream header',
343             PDLEVEL1 )
344             unless $rv;
345              
346 0         0 subPostamble( PDLEVEL3, '$', $rv );
347              
348 0         0 return $rv;
349             }
350              
351             sub readBATs {
352              
353             # Purpose: Reads the BAT records from the stream header
354             # Returns: Boolean
355             # Usage: $rv = $obj->readBATs;
356              
357 67     67 1 160 my $self = shift;
358 67         138 my $rv = 1;
359 67         226 my ( $raw, @sraw, $bn, $lbn, $ubn, $prev );
360 67         0 my @bats;
361              
362 67         194 subPreamble(PDLEVEL3);
363              
364             # Read the BATs section of the block
365 67 50       296 if ( $self->bread( \$raw, BATS_POS ) ) {
366              
367 67         55362 @sraw = unpack '(' . BATIDX . ")$$self{maxBATs}", $raw;
368 67         6826 while (@sraw) {
369              
370 112         460 $lbn = shift @sraw;
371 112         338 $ubn = shift @sraw;
372 112         428 $bn = longs2Quad( $lbn, $ubn );
373              
374             # Stop processing when it looks like we're not getting legitmate
375             # values
376 112 100 66     935 last unless defined $bn and $bn > $$self{blockNum};
377              
378             # Error out if block numbers aren't ascending
379 45 50 33     284 unless ( !defined $prev or $bn > $prev ) {
380 0         0 pdebug( 'BAT block number appearing out of sequence',
381             PDLEVEL1 );
382 0         0 $rv = 0;
383 0         0 last;
384             }
385              
386             # Save entry
387 45         130 push @bats, $bn;
388 45         151 $prev = $bn;
389             }
390              
391             # Save everything extracted
392 67         220 $$self{bats} = [@bats];
393 67         252 pdebug( 'found %s bats', PDLEVEL4, scalar @bats );
394              
395             } else {
396 0         0 pdebug( 'failed to read list of BATs from stream header', PDLEVEL1 );
397 0         0 $rv = 0;
398             }
399              
400 67         416 subPostamble( PDLEVEL3, '$', $rv );
401              
402 67         6639 return $rv;
403             }
404              
405             sub addBAT {
406              
407             # Purpose: Adds a BAT block number to the stream header
408             # Returns: Boolean
409             # Usage: $rv = $obj->addBAT($bn);
410              
411 77     77 1 209 my $self = shift;
412 77         176 my $bn = shift;
413 77         154 my $rv = 1;
414              
415 77         308 subPreamble( PDLEVEL3, '$', $bn );
416              
417 77 100 100     539 if ( defined $bn and $bn > $$self{blockNum} ) {
418              
419             # Make sure we're not adding redundant entries
420 55 50       132 if ( scalar grep { $_ eq $bn } @{ $$self{bats} } ) {
  0         0  
  55         275  
421 0         0 $rv = 0;
422 0         0 pdebug( 'redundant entry for an existing BAT', PDLEVEL1 );
423             }
424              
425             # Make sure new BAT is a higher block number than all previous BATs
426 55 50       132 if ( scalar grep { $_ > $bn } @{ $$self{bats} } ) {
  0         0  
  55         209  
427 0         0 $rv = 0;
428 0         0 pdebug( 'BAT block number is lower than previous BATs',
429             PDLEVEL1 );
430             }
431              
432 55 50       143 if ($rv) {
433 55         99 push @{ $$self{bats} }, $bn;
  55         165  
434             $rv = 0
435             unless $self->bwrite(
436             pack( BATIDX, quad2Longs($bn) ),
437 55 50       231 BATS_POS + BAT_LEN * $#{ $$self{bats} } ) == BAT_LEN;
  55         308  
438             }
439              
440             } else {
441 22         77 pdebug( 'invalid BAT block number (%s)', PDLEVEL1, $bn );
442 22         55 $rv = 0;
443             }
444              
445 77         308 subPostamble( PDLEVEL3, '$', $rv );
446              
447 77         264 return $rv;
448             }
449              
450             1;
451              
452             __END__