File Coverage

blib/lib/Paranoid/IO/FileMultiplexer/Block/StreamHeader.pm
Criterion Covered Total %
statement 182 224 81.2
branch 25 50 50.0
condition 10 18 55.5
subroutine 29 31 93.5
pod 14 14 100.0
total 260 337 77.1


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.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::StreamHeader;
33              
34 11     11   242 use 5.008;
  11         44  
35              
36 11     11   66 use strict;
  11         22  
  11         209  
37 11     11   55 use warnings;
  11         22  
  11         484  
38 11     11   66 use vars qw($VERSION);
  11         22  
  11         605  
39 11     11   165 use base qw(Exporter);
  11         33  
  11         759  
40 11     11   66 use Paranoid;
  11         22  
  11         616  
41 11     11   88 use Paranoid::IO qw(:all);
  11         22  
  11         2013  
42 11     11   99 use Paranoid::Debug qw(:all);
  11         22  
  11         1771  
43 11     11   88 use Fcntl qw(:DEFAULT :flock :mode :seek);
  11         22  
  11         5764  
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             # PIOFMSTRM Name EOS
51             # Z10 Z21 NNx
52             # 40 bytes
53             #
54             # BAT record format:
55             # BlockNum
56             # NN
57             # 8 bytes
58 11     11   66 use constant SIGNATURE => 'Z10Z21NNx';
  11         22  
  11         737  
59 11     11   77 use constant SIG_LEN => 40;
  11         22  
  11         616  
60 11     11   77 use constant SIG_TYPE => 'PIOFMSTRM';
  11         33  
  11         506  
61 11     11   66 use constant EOS_POS => 31;
  11         22  
  11         572  
62 11     11   77 use constant BATS_POS => 40;
  11         11  
  11         484  
63 11     11   66 use constant BATIDX => 'NN';
  11         11  
  11         627  
64 11     11   66 use constant BAT_LEN => 8;
  11         22  
  11         20086  
65              
66             #####################################################################
67             #
68             # Module code follows
69             #
70             #####################################################################
71              
72             sub new {
73              
74             # Purpose: Creates a new stream header object
75             # Returns: Object reference/undef
76             # Usage: $obj =
77             # Paranoid::IO::FileMultiplexer::Block::StreamHeader->new($file,
78             # $blockNo, $blockSize, $strmName);
79              
80 207     207 1 16056 my $class = shift;
81 207         464 my $file = shift;
82 207         393 my $bnum = shift;
83 207         448 my $bsize = shift;
84 207         448 my $sname = shift;
85 207         350 my $self;
86              
87 207         736 pdebug( 'entering w/(%s)(%s)(%s)(%s)',
88             PDLEVEL3, $file, $bnum, $bsize, $sname );
89 207         751 pIn();
90              
91 207 50 33     1635 if ( defined $sname and length $sname and length $sname <= 20 ) {
      33        
92 207         939 $self = __PACKAGE__->SUPER::new( $file, $bnum, $bsize );
93             } else {
94 0         0 pdebug( 'invalid stream name (%s)', PDLEVEL1, $sname );
95             }
96              
97 207 50       695 if ( defined $self ) {
98 207         685 $$self{streamName} = $sname;
99 207         549 $$self{bats} = []; # array of blockNum
100 207         753 $$self{eos} = 0; # address of stream EOF
101 207         1327 $$self{maxBATs} = int( ( $$self{blockSize} - SIG_LEN ) / BAT_LEN );
102             }
103              
104 207         725 pOut();
105 207         633 pdebug( 'leaving w/rv: %s', PDLEVEL3, $self );
106              
107 207         898 return $self;
108             }
109              
110             sub streamName {
111              
112             # Purpose: Returns the current stream name
113             # Returns: String
114             # Usage: $name = $obj->streamName;
115              
116 11     11 1 5654 my $self = shift;
117              
118 11         66 return $$self{streamName};
119             }
120              
121             sub maxBATs {
122              
123             # Purpose: Returns the max BAT blocks for the stream
124             # Returns: Integer
125             # Usage: $max = $obj->maxBATs;
126              
127 74     74 1 177 my $self = shift;
128              
129 74         383 return $$self{maxBATs};
130             }
131              
132             sub eos {
133              
134             # Purpose: Returns the current stream EOS
135             # Returns: Integer
136             # Usage: $eos = $obj->eos;
137              
138 1178     1178 1 2711 my $self = shift;
139              
140 1178         6262 return $$self{eos};
141             }
142              
143             sub bats {
144              
145             # Purpose: Returns an array of bat nums
146             # Returns: Hash
147             # Usage: @bats = $obj->bats;
148              
149 1836     1836 1 3507 my $self = shift;
150              
151 1836         2524 return @{ $$self{bats} };
  1836         7224  
152             }
153              
154             sub full {
155              
156             # Purpose: Returns whether the streams's array of BAT blocks is full
157             # Returns: Boolean
158             # Usage: $rv = $obj->full;
159              
160 0     0 1 0 my $self = shift;
161              
162 0         0 return $self->maxBATs == scalar $self->bats;
163             }
164              
165             sub writeSig {
166              
167             # Purpose: Writes the stream signature to the file
168             # Returns: Boolean
169             # Usage: $rv = $obj->writeSig;
170              
171 55     55 1 143 my $self = shift;
172 55         165 my $file = $$self{file};
173 55         132 my $sname = $$self{streamName};
174 55         132 my $eos = $$self{eos};
175 55         121 my $rv = 0;
176 55         264 my $sig = pack SIGNATURE, SIG_TYPE, $sname, $self->splitInt($eos);
177              
178 55         242 pdebug( 'entering', PDLEVEL3 );
179 55         209 pIn();
180              
181 55         297 $rv = $self->bwrite($sig);
182              
183 55         231 pOut();
184 55         176 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
185              
186 55         429 return $rv;
187             }
188              
189             sub readSig {
190              
191             # Purpose: Reads the block signature from the file
192             # Returns: Boolean
193             # Usage: $rv = $obj->readSig;
194              
195 89     89 1 5062 my $self = shift;
196 89         214 my $file = $$self{file};
197 89         154 my $rv = 0;
198 89         231 my ( $raw, $type, $sname, $eos, $leos, $ueos );
199              
200 89         271 pdebug( 'entering', PDLEVEL3 );
201 89         310 pIn();
202              
203 89 50       282 if ( pflock( $file, LOCK_SH ) ) {
204 89 100       615 if ( $self->bread( \$raw, 0, SIG_LEN ) == SIG_LEN ) {
205 67         201 $rv = 1;
206              
207             # Unpack the signature
208 67         761 ( $type, $sname, $leos, $ueos ) = unpack SIGNATURE, $raw;
209              
210             # Validate contents
211             #
212             # Start with file type
213 67 50       347 unless ( $type eq SIG_TYPE ) {
214 0         0 $rv = 0;
215 0         0 pdebug( 'Invalid stream header type (%s)', PDLEVEL1, $type );
216             }
217              
218             # stream name
219 67 50       260 unless ( $sname eq $$self{streamName} ) {
220 0         0 $rv = 0;
221 0         0 pdebug( 'Invalid stream name (%s)', PDLEVEL1, $sname );
222             }
223              
224             # Make sure eos is legitimate
225 67         258 $eos = $self->joinInt( $leos, $ueos );
226 67 50       267 unless ( defined $eos ) {
227 0         0 pdebug( 'this platform does not support 64b values for eos',
228             PDLEVEL1 );
229 0         0 $rv = 0;
230             }
231              
232             # Update internal values
233 67 50       206 if ($rv) {
234 67         191 $$self{eos} = $eos;
235             } else {
236 0         0 pdebug( 'stream signature verification failure', PDLEVEL1 );
237             }
238              
239             } else {
240 22         88 pdebug( 'failed to read stream header signature', PDLEVEL1 );
241             }
242              
243 89         294 pflock( $file, LOCK_UN );
244             }
245              
246 89         295 pOut();
247 89         302 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
248              
249 89         934 return $rv;
250             }
251              
252             sub writeEOS {
253              
254             # Purpose: Updates the EOS counter and writes it to disk
255             # Returns: Boolean
256             # Usage: $rv = $obj->writeEOS($pos);
257              
258 566     566 1 1552 my $self = shift;
259 566         1260 my $eos = shift;
260 566         1303 my ( $raw, $rv );
261              
262 566         1897 pdebug( 'entering w/%s', PDLEVEL3, $eos );
263 566         1667 pIn();
264              
265 566 50       1911 if ( defined $eos ) {
266 566         2614 $raw = pack 'NN', $self->splitInt($eos);
267 566 50       2378 if ( $self->bwrite( $raw, EOS_POS ) == 8 ) {
268 566         1903 $$self{eos} = $eos;
269 566         1244 $rv = 1;
270             }
271             } else {
272 0         0 pdebug( 'invalid value for eos (%s)', PDLEVEL1, $eos );
273             }
274              
275 566         1770 pOut();
276 566         1785 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
277              
278 566         2564 return $rv;
279             }
280              
281             sub readEOS {
282              
283             # Purpose: Reads the EOS counter from disk
284             # Returns: Integer/undef on error
285             # Usage: $pos = $obj->readEOS;
286              
287 2959     2959 1 4640 my $self = shift;
288 2959         4985 my ( $rv, $raw );
289              
290 2959         7168 pdebug( 'entering', PDLEVEL3 );
291 2959         7564 pIn();
292              
293 2959 50       8978 if ( $self->bread( \$raw, EOS_POS, 8 ) == 8 ) {
294 2959         17319 $rv = $self->joinInt( unpack 'NN', $raw );
295 2959 100 66     17806 $rv = '0 but true' if defined $rv and $rv == 0;
296             }
297              
298 2959         8986 pOut();
299 2959         7360 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
300              
301 2959         10885 return $rv;
302             }
303              
304             sub validateEOS {
305              
306             # Purpose: Compares in-memory EOS counter to what's stored in the file
307             # Returns: Boolean
308             # Usage: $rv = $obj->validateBlocks;
309              
310 2959     2959 1 5626 my $self = shift;
311 2959         6641 my $rv = 0;
312              
313 2959         9076 pdebug( 'entering', PDLEVEL3 );
314 2959         7653 pIn();
315              
316 2959 50       8361 $rv = 1 if $$self{eos} == $self->readEOS;
317              
318 2959         8907 pOut();
319 2959         7283 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
320              
321 2959         11574 return $rv;
322             }
323              
324             sub writeBATs {
325              
326             # Purpose: Writes all the BAT block numbers to the file
327             # Returns: Boolean
328             # Usage: $rv = $obj->writeBATs;
329              
330 0     0 1 0 my $self = shift;
331 0         0 my $file = $$self{file};
332 0         0 my $rv = 0;
333 0         0 my ( $rec, $i, $pos );
334              
335 0         0 pdebug( 'entering', PDLEVEL3 );
336 0         0 pIn();
337              
338             # Hold an exclusive lock for the entire transaction
339 0 0       0 if ( pflock( $file, LOCK_EX ) ) {
340              
341 0         0 $rv = 1;
342 0         0 $i = 0;
343 0         0 foreach $rec ( @{ $$self{bats} } ) {
  0         0  
344 0         0 $pos = BATS_POS + $i * BAT_LEN;
345 0 0       0 $rv = 0
346             unless $self->bwrite( pack( BATIDX, $self->splitInt($rec) ),
347             $pos ) == BAT_LEN;
348 0         0 $i++;
349 0 0       0 last unless $rv;
350             }
351              
352 0         0 pflock( $file, LOCK_UN );
353             }
354              
355 0 0       0 pdebug( 'failed to write all BAT block numbers to the stream header',
356             PDLEVEL1 )
357             unless $rv;
358              
359 0         0 pOut();
360 0         0 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
361              
362 0         0 return $rv;
363             }
364              
365             sub readBATs {
366              
367             # Purpose: Reads the BAT records from the stream header
368             # Returns: Boolean
369             # Usage: $rv = $obj->readBATs;
370              
371 67     67 1 194 my $self = shift;
372 67         145 my $rv = 1;
373 67         214 my ( $raw, @sraw, $bn, $lbn, $ubn, $prev );
374 67         0 my @bats;
375              
376 67         224 pdebug( 'entering', PDLEVEL3 );
377 67         273 pIn();
378              
379             # Read the BATs section of the block
380 67 50       318 if ( $self->bread( \$raw, BATS_POS ) ) {
381              
382 67         62265 @sraw = unpack '(' . BATIDX . ")$$self{maxBATs}", $raw;
383 67         8256 while (@sraw) {
384              
385 112         442 $lbn = shift @sraw;
386 112         335 $ubn = shift @sraw;
387 112         613 $bn = $self->joinInt( $lbn, $ubn );
388              
389             # Stop processing when it looks like we're not getting legitmate
390             # values
391 112 100 66     960 last unless defined $bn and $bn > $$self{blockNum};
392              
393             # Error out if block numbers aren't ascending
394 45 50 33     182 unless ( !defined $prev or $bn > $prev ) {
395 0         0 pdebug( 'BAT block number appearing out of sequence',
396             PDLEVEL1 );
397 0         0 $rv = 0;
398 0         0 last;
399             }
400              
401             # Save entry
402 45         125 push @bats, $bn;
403 45         210 $prev = $bn;
404             }
405              
406             # Save everything extracted
407 67         258 $$self{bats} = [@bats];
408 67         286 pdebug( 'found %s bats', PDLEVEL4, scalar @bats );
409              
410             } else {
411 0         0 pdebug( 'failed to read list of BATs from stream header', PDLEVEL1 );
412 0         0 $rv = 0;
413             }
414              
415 67         272 pOut();
416 67         190 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
417              
418 67         7977 return $rv;
419             }
420              
421             sub addBAT {
422              
423             # Purpose: Adds a BAT block number to the stream header
424             # Returns: Boolean
425             # Usage: $rv = $obj->addBAT($bn);
426              
427 77     77 1 187 my $self = shift;
428 77         209 my $bn = shift;
429 77         132 my $rv = 1;
430              
431 77         297 pdebug( 'entering w/(%s)', PDLEVEL3, $bn );
432 77         264 pIn();
433              
434 77 100 100     539 if ( defined $bn and $bn > $$self{blockNum} ) {
435              
436             # Make sure we're not adding redundant entries
437 55 50       132 if ( scalar grep { $_ eq $bn } @{ $$self{bats} } ) {
  0         0  
  55         286  
438 0         0 $rv = 0;
439 0         0 pdebug( 'redundant entry for an existing BAT', PDLEVEL1 );
440             }
441              
442             # Make sure new BAT is a higher block number than all previous BATs
443 55 50       121 if ( scalar grep { $_ > $bn } @{ $$self{bats} } ) {
  0         0  
  55         220  
444 0         0 $rv = 0;
445 0         0 pdebug( 'BAT block number is lower than previous BATs',
446             PDLEVEL1 );
447             }
448              
449 55 50       143 if ($rv) {
450 55         121 push @{ $$self{bats} }, $bn;
  55         132  
451             $rv = 0
452             unless $self->bwrite(
453             pack( BATIDX, $self->splitInt($bn) ),
454 55 50       242 BATS_POS + BAT_LEN * $#{ $$self{bats} } ) == BAT_LEN;
  55         286  
455             }
456              
457             } else {
458 22         110 pdebug( 'invalid BAT block number (%s)', PDLEVEL1, $bn );
459 22         198 $rv = 0;
460             }
461              
462 77         319 pOut();
463 77         264 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
464              
465 77         308 return $rv;
466             }
467              
468             1;
469              
470             __END__