File Coverage

blib/lib/Paranoid/IO/FileMultiplexer/Block/FileHeader.pm
Criterion Covered Total %
statement 240 285 84.2
branch 48 74 64.8
condition 13 21 61.9
subroutine 36 39 92.3
pod 15 15 100.0
total 352 434 81.1


line stmt bran cond sub pod time code
1             # Paranoid::IO::FileMultiplexer::Block::FileHeader -- File Header Block
2             #
3             # $Id: lib/Paranoid/IO/FileMultiplexer/Block/FileHeader.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::FileHeader;
33              
34 11     11   176 use 5.008;
  11         44  
35              
36 11     11   55 use strict;
  11         22  
  11         187  
37 11     11   44 use warnings;
  11         22  
  11         242  
38 11     11   44 use vars qw($VERSION);
  11         11  
  11         506  
39 11     11   55 use base qw(Exporter);
  11         22  
  11         671  
40 11     11   44 use Paranoid qw(:all);
  11         22  
  11         968  
41 11     11   66 use Paranoid::IO qw(:all);
  11         22  
  11         1496  
42 11     11   77 use Paranoid::Debug qw(:all);
  11         11  
  11         1397  
43 11     11   3949 use Paranoid::Data;
  11         33  
  11         605  
44 11     11   66 use Fcntl qw(:DEFAULT :flock :mode :seek);
  11         22  
  11         3839  
45 11     11   4851 use Paranoid::IO::FileMultiplexer::Block;
  11         22  
  11         440  
46 11     11   4488 use Paranoid::IO::FileMultiplexer::Block::StreamHeader;
  11         22  
  11         462  
47 11     11   4433 use Paranoid::IO::FileMultiplexer::Block::BATHeader;
  11         33  
  11         715  
48              
49             ($VERSION) = ( q$Revision: 2.10 $ =~ /(\d+(?:\.\d+)+)/sm );
50              
51 11     11   66 use base qw(Paranoid::IO::FileMultiplexer::Block);
  11         22  
  11         715  
52              
53 11     11   66 use constant PIOFMVER => '1.0';
  11         22  
  11         495  
54              
55             # Signature format:
56             # PIOFM VER BS BC
57             # Z6 Z4 NNx NNx
58             # 28 bytes
59             #
60             # Stream record format:
61             # String BN
62             # Z21 NNx
63             # 30 bytes
64 11     11   44 use constant SIGNATURE => 'Z6Z4NNxNNx';
  11         22  
  11         385  
65 11     11   44 use constant SIG_LEN => 28;
  11         11  
  11         374  
66 11     11   44 use constant SIG_TYPE => 'PIOFM';
  11         22  
  11         363  
67 11     11   44 use constant BLOCKS_POS => 10;
  11         22  
  11         341  
68 11     11   55 use constant BLOCKC_POS => 19;
  11         11  
  11         649  
69 11     11   66 use constant STREAMS_POS => 28;
  11         22  
  11         517  
70 11     11   55 use constant STRMIDX => 'Z21NNx';
  11         22  
  11         550  
71 11     11   66 use constant STRM_LEN => 30;
  11         22  
  11         19987  
72              
73             #####################################################################
74             #
75             # Module code follows
76             #
77             #####################################################################
78              
79             sub new {
80              
81             # Purpose: Creates a new file header object
82             # Returns: Object reference/undef
83             # Usage: $obj =
84             # Paranoid::IO::FileMultiplexer::Block::FileHeader->new($file, $blockSize);
85              
86 338     338 1 776 my $class = shift;
87 338         436 my $file = shift;
88 338         575 my $bsize = shift;
89 338         470 my $self;
90              
91 338         884 subPreamble( PDLEVEL3, '$$', $file, $bsize );
92              
93 338         1399 $self = __PACKAGE__->SUPER::new( $file, 0, $bsize );
94              
95 338 100       872 if ( defined $self ) {
96 239         703 $$self{version} = PIOFMVER;
97 239         622 $$self{blocks} = 1;
98 239         679 $$self{streamidx} = {}; # name => idx of rec in streams
99 239         529 $$self{streams} = []; # array of [ name, blockNum ]
100 239         988 $$self{maxStreams} = int( ( $bsize - SIG_LEN ) / STRM_LEN );
101             }
102              
103 338         1012 subPostamble( PDLEVEL3, '$', $self );
104              
105 338         792 return $self;
106             }
107              
108             sub blocks {
109              
110             # Purpose: Returns the number of blocks recorded in the signature
111             # Returns: Integer
112             # Usage: $count = $obj->blocks;
113              
114 279     279 1 814 my $self = shift;
115 279         1020 return $$self{blocks};
116             }
117              
118             sub version {
119              
120             # Purpose: Returns the version of the file format
121             # Returns: String
122             # Usage: $ver = $obj->version;
123              
124 0     0 1 0 my $self = shift;
125 0         0 return $$self{version};
126             }
127              
128             sub streams {
129              
130             # Purpose: Returns a hash of stream names => blockNums
131             # Returns: Hash
132             # Usage: %streams = $obj->streams;
133              
134 100     100 1 266 my $self = shift;
135 100         240 my @streams = @{ $$self{streams} };
  100         364  
136 100         217 my ( %rv, $stream );
137              
138 100         312 foreach $stream (@streams) {
139 167         625 $rv{ $$stream[0] } = $$stream[1];
140             }
141              
142 100         651 return %rv;
143             }
144              
145             sub maxStreams {
146              
147             # Purpose: Returns the maximum number of streams supported by this file
148             # Returns: Integer
149             # Usage: $max = $obj->maxStreams;
150              
151 0     0 1 0 my $self = shift;
152              
153 0         0 return $$self{maxStreams};
154             }
155              
156             sub _transHuman {
157              
158             # Purpose: Translates raw integers into human-readable values
159             # Returns: String
160             # Usage: $rv = _transHuman($n);
161              
162 222     222   418 my $n = shift;
163 222         343 my $u = 'B';
164              
165 222         516 while ( $n > 1024 ) {
166 721 100       1917 $u =
    100          
    100          
    100          
    100          
167             $u eq 'B' ? 'KB'
168             : $u eq 'KB' ? 'MB'
169             : $u eq 'MB' ? 'GB'
170             : $u eq 'GB' ? 'TB'
171             : $u eq 'TB' ? 'PB'
172             : 'EX';
173 721         936 $n /= 1024;
174 721 100       1753 last if $u eq 'EX';
175             }
176 222         1785 $n = sprintf( '%0.2f', $n );
177              
178 222         897 return "$n$u";
179             }
180              
181             sub model {
182              
183             # Purpose: Returns a hash of file statistics
184             # Returns: Hash
185             # Usage: $stats = $obj->model;
186              
187 74     74 1 167 my $self = shift;
188 74         236 my $bs = $$self{blockSize};
189 74         264 my $blks = $$self{blocks};
190 74         168 my $strms = scalar keys %{ $$self{streamidx} };
  74         292  
191 74         146 my ( $block, $maxBATs, $maxData, %rv );
192              
193             # Get reference max values
194             $block =
195             Paranoid::IO::FileMultiplexer::Block::StreamHeader->new( $$self{file},
196 74         853 1, $bs, 'foo' );
197 74         362 $maxBATs = $block->maxBATs;
198             $block =
199 74         912 Paranoid::IO::FileMultiplexer::Block::BATHeader->new( $$self{file}, 1,
200             $bs, 'foo', 0 );
201 74         284 $maxData = $block->maxData;
202              
203             # Current stats
204 74         308 $rv{intSize} = ( 1 << 32 ) == 1 ? 32 : 64;
205 74         246 $rv{curFileSize} = $bs * $blks;
206 74         236 $rv{curFSHuman} = _transHuman( $rv{curFileSize} );
207 74         217 $rv{curStreams} = $strms;
208              
209             # Predicted max
210 74         168 $rv{maxFileSize} = 0b11111111_11111111_11111111_11111111;
211             $rv{maxFileSize} = $rv{maxFileSize} | ( $rv{maxFileSize} << 32 )
212 74 50       283 if $rv{intSize} == 64;
213 74         165 $rv{maxStreams} = $$self{maxStreams};
214 74         311 $rv{maxStreamSize} = $bs * $maxBATs * $maxData;
215             $rv{maxStreamSize} = $rv{maxFileSize}
216 74 50       280 if $rv{maxStreamSize} > $rv{maxFileSize};
217 74         172 $rv{maxSSHuman} = _transHuman( $rv{maxStreamSize} );
218              
219             # Provide human-readable values
220 74         208 $rv{maxFSHuman} = _transHuman( $rv{maxFileSize} );
221              
222 74         993 return %rv;
223             }
224              
225             sub writeSig {
226              
227             # Purpose: Writes the file signature to the file
228             # Returns: Boolean
229             # Usage: $rv = $obj->writeSig;
230              
231 99     99 1 209 my $self = shift;
232 99         220 my $file = $$self{file};
233 99         176 my $ver = $$self{version};
234 99         154 my $rv = 0;
235             my $sig = pack SIGNATURE, SIG_TYPE, PIOFMVER,
236             quad2Longs( $$self{blockSize} ),
237 99         363 quad2Longs( $$self{blocks} );
238              
239 99         341 pdebug( 'entering', PDLEVEL3 );
240 99         330 pIn();
241              
242 99         385 $rv = $self->bwrite($sig);
243              
244 99         330 pOut();
245 99         220 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
246              
247 99         330 return $rv;
248             }
249              
250             sub readSig {
251              
252             # Purpose: Reads the block signature from the file
253             # Returns: Boolean
254             # Usage: $rv = $obj->readSig;
255              
256 129     129 1 260 my $self = shift;
257 129         282 my $file = $$self{file};
258 129         284 my $rv = 0;
259 129         412 my ( $raw, $type, $ver, $bs, $bc, $tblock );
260 129         0 my ( $lbs, $ubs, $lbc, $ubc );
261              
262 129         388 subPreamble(PDLEVEL3);
263              
264 129 50       335 if ( pflock( $file, LOCK_SH ) ) {
265 129 50       628 if ( $self->bread( \$raw, 0, SIG_LEN ) == SIG_LEN ) {
266 129         353 $rv = 1;
267              
268             # Unpack the signature
269 129         1374 ( $type, $ver, $lbs, $ubs, $lbc, $ubc ) = unpack SIGNATURE, $raw;
270              
271             # Validate contents
272             #
273             # Start with file type
274 129 50       557 unless ( $type eq SIG_TYPE ) {
275 0         0 $rv = 0;
276 0         0 pdebug( 'Invalid file header type (%s)', PDLEVEL1, $type );
277             }
278              
279             # format version
280 129 50       573 unless ( $ver eq PIOFMVER ) {
281 0         0 $rv = 0;
282 0         0 pdebug( 'Invalid file header version (%s)', PDLEVEL1, $ver );
283             }
284              
285             # Make sure block size is legitimate
286 129         373 $bs = longs2Quad( $lbs, $ubs );
287 129 50       404 if ( defined $bs ) {
288 129         859 $tblock = __PACKAGE__->new( $file, $bs );
289 129 100       332 unless ( defined $tblock ) {
290 22         55 $rv = 0;
291 22         66 pdebug( 'blockSize error in file header: %s',
292             PDLEVEL1, $bs );
293             }
294             } else {
295 0         0 pdebug(
296             'this platform does not support 64b values for block size',
297             PDLEVEL1
298             );
299 0         0 $rv = 0;
300             }
301              
302             # Validate end of file matches block count
303 129         464 $bc = longs2Quad( $lbc, $ubc );
304 129 50       382 if ( defined $bc ) {
305 129         414 pseek( $file, 0, SEEK_END );
306 129 100       500 unless ( ptell($file) == $bc * $bs ) {
307 44         77 $rv = 0;
308 44         154 pdebug(
309             'incorrect file size based on block count (%s * %s = %s)',
310             PDLEVEL1, $bc, $bs, $bc * $bs
311             );
312             }
313             } else {
314 0         0 pdebug(
315             'this platform does not support 64b values for block count',
316             PDLEVEL1
317             );
318 0         0 $rv = 0;
319             }
320              
321             # Update internal values
322 129 100       356 if ($rv) {
323 85         258 $$self{version} = $ver;
324 85         281 $$self{blockSize} = $bs;
325 85         193 $$self{blocks} = $bc;
326 85         348 $self->recalibrate;
327             } else {
328 44         110 pdebug( 'file signature verification failure', PDLEVEL1 );
329             }
330              
331             } else {
332 0         0 pdebug( 'failed to read file header signature', PDLEVEL1 );
333             }
334              
335 129         351 pflock( $file, LOCK_UN );
336             }
337              
338 129         407 subPostamble( PDLEVEL3, '$', $rv );
339              
340 129         1202 return $rv;
341             }
342              
343             sub writeBlocks {
344              
345             # Purpose: Updates the blocks counter and writes it to disk
346             # Returns: Boolean
347             # Usage: $rv = $obj->writeBlocks($count);
348              
349 257     257 1 602 my $self = shift;
350 257         578 my $bcount = shift;
351 257         493 my ( $raw, $rv );
352              
353 257         811 subPreamble( PDLEVEL3, '$', $bcount );
354              
355 257 50 33     1322 if ( defined $bcount and $bcount > 0 ) {
356 257         824 $raw = pack 'NN', quad2Longs($bcount);
357 257 50       970 if ( $self->bwrite( $raw, BLOCKC_POS ) == 8 ) {
358 257         745 $$self{blocks} = $bcount;
359 257         460 $rv = 1;
360             }
361             } else {
362 0         0 pdebug( 'invalid value for blocks (%s)', PDLEVEL1, $bcount );
363             }
364              
365 257         795 subPostamble( PDLEVEL3, '$', $rv );
366              
367 257         1526 return $rv;
368             }
369              
370             sub readBlocks {
371              
372             # Purpose: Reads the blocks counter from disk
373             # Returns: Integer/undef on error
374             # Usage: $count = $obj->readBlocks;
375              
376 3003     3003 1 6043 my $self = shift;
377 3003         5272 my ( $rv, $raw );
378              
379 3003         8193 subPreamble(PDLEVEL3);
380              
381 3003 50       12490 if ( $self->bread( \$raw, BLOCKC_POS, 8 ) == 8 ) {
382 3003         28486 $rv = longs2Quad( unpack 'NN', $raw );
383 3003 50 33     16689 $rv = PTRUE_ZERO if defined $rv and $rv == 0;
384             }
385              
386 3003         9437 subPostamble( PDLEVEL3, '$', $rv );
387              
388 3003         12375 return $rv;
389             }
390              
391             sub incrBlocks {
392              
393             # Purpose: Increments the block count and writes the field to disk
394             # Returns: Boolean
395             # Usage: $rv = $obj->incrBlocks;
396              
397 191     191 1 628 my $self = shift;
398              
399 191         904 return $self->writeBlocks( $$self{blocks} + 1 );
400             }
401              
402             sub validateBlocks {
403              
404             # Purpose: Compares in-memory block counter to what's stored in the file
405             # Returns: Boolean
406             # Usage: $rv = $obj->validateBlocks;
407              
408 2992     2992 1 6272 my $self = shift;
409 2992         5379 my $rv = 0;
410              
411 2992         8798 subPreamble(PDLEVEL3);
412              
413 2992 100       10022 $rv = 1 if $$self{blocks} == $self->readBlocks;
414              
415 2992         10524 subPostamble( PDLEVEL3, '$', $rv );
416              
417 2992         11928 return $rv;
418             }
419              
420             sub writeStreams {
421              
422             # Purpose: Writes all the stream index records to the file
423             # Returns: Boolean
424             # Usage: $rv = $obj->writeStreams;
425              
426 0     0 1 0 my $self = shift;
427 0         0 my $file = $$self{file};
428 0         0 my $rv = 0;
429 0         0 my ( $rec, $i, $pos );
430              
431 0         0 subPreamble(PDLEVEL3);
432              
433             # Hold an exclusive lock for the entire transaction
434 0 0       0 if ( pflock( $file, LOCK_EX ) ) {
435 0         0 $rv = 1;
436 0         0 $i = 0;
437 0         0 foreach $rec ( @{ $$self{streams} } ) {
  0         0  
438 0         0 @$rec = ( $$rec[0], quad2Longs( $$rec[1] ) );
439 0         0 $pos = STREAMS_POS + $i * STRM_LEN;
440 0 0       0 $rv = 0
441             unless $self->bwrite( pack( STRMIDX, @$rec ), $pos ) ==
442             STRM_LEN;
443 0         0 $i++;
444 0 0       0 last unless $rv;
445             }
446              
447 0         0 pflock( $file, LOCK_UN );
448             }
449              
450 0 0       0 pdebug( 'failed to write all stream records to the file header',
451             PDLEVEL1 )
452             unless $rv;
453              
454 0         0 subPostamble( PDLEVEL3, '$', $rv );
455              
456 0         0 return $rv;
457             }
458              
459             sub readStreams {
460              
461             # Purpose: Reads the stream records from the file header
462             # Returns: Boolean
463             # Usage: $rv = $obj->readStreams;
464              
465 52     52 1 153 my $self = shift;
466 52         111 my $rv = 1;
467 52         209 my ( $raw, $sname, $bn, @sraw, $prev );
468 52         0 my ( %sidx, @streams, %model, $maxstreams );
469              
470 52         198 subPreamble(PDLEVEL3);
471              
472             # Read the streams section of the block
473 52 50       249 if ( $self->bread( \$raw, STREAMS_POS ) ) {
474              
475             # Get the model so we know how many streams we can support
476 52         307 %model = $self->model;
477 52         198 $maxstreams = $model{maxStreams};
478              
479 52         22907 @sraw = unpack '(' . STRMIDX . ")$maxstreams", $raw;
480 52         2310 while (@sraw) {
481 112         398 $sname = shift @sraw;
482 112         453 $bn = longs2Quad( shift @sraw, shift @sraw );
483              
484             # Stop processing when it looks like we're not getting legitmate
485             # values
486 112 100 66     1152 last unless defined $sname and length $sname and $bn > 0;
      66        
487              
488             # Make sure we're not getting repeated streams
489 60 50       229 if ( exists $sidx{$sname} ) {
490 0         0 pdebug( 'stream (%s) listed more than once',
491             PDLEVEL1, $sname );
492 0         0 $rv = 0;
493 0         0 last;
494             }
495              
496             # Error out if stream block numbers aren't ascending
497 60 50 66     518 unless ( !defined $prev or $bn > $prev ) {
498 0         0 pdebug( 'stream block number appearing out of sequence',
499             PDLEVEL1 );
500 0         0 $rv = 0;
501 0         0 last;
502             }
503              
504             # Save entry
505 60         428 push @streams, [ $sname, $bn ];
506 60         248 $sidx{$sname} = $#streams;
507 60         190 $prev = $bn;
508             }
509              
510             # Save everything extracted
511 52         332 $$self{streamidx} = {%sidx};
512 52         367 $$self{streams} = [@streams];
513 52         226 pdebug( 'found %s streams', PDLEVEL4, scalar @streams );
514              
515             } else {
516 0         0 pdebug( 'failed to read list of streams from file header', PDLEVEL1 );
517 0         0 $rv = 0;
518             }
519              
520 52         248 subPostamble( PDLEVEL3, '$', $rv );
521              
522 52         2265 return $rv;
523             }
524              
525             sub addStream {
526              
527             # Purpose: Adds a stream record to the file header
528             # Returns: Boolean
529             # Usage: $rv = $obj->addStream($sname, $bn);
530              
531 88     88 1 187 my $self = shift;
532 88         198 my $sname = shift;
533 88         132 my $bn = shift;
534 88         165 my %sidx = %{ $$self{streamidx} };
  88         308  
535 88         154 my $rv = 1;
536              
537 88         297 subPreamble( PDLEVEL3, '$$', $sname, $bn );
538              
539 88 100 66     429 if ( defined $sname and length $sname ) {
540 77 50       209 if ( exists $sidx{$sname} ) {
541 0         0 pdebug( 'stream already exists (%s)', PDLEVEL1, $sname );
542 0         0 $rv = 0;
543             }
544              
545 77 50       187 if ( length $sname > 20 ) {
546 0         0 pdebug( 'stream name is too long (%s)', PDLEVEL1, $sname );
547 0         0 $rv = 0;
548             }
549              
550 77 100 100     407 if ( !defined $bn or $bn < 1 ) {
551 22         66 pdebug( 'invalid stream block number (%s)', PDLEVEL1, $bn );
552 22         55 $rv = 0;
553             }
554              
555 77 100       198 if ($rv) {
556 55         110 push @{ $$self{streams} }, [ $sname, $bn ];
  55         220  
557 55         99 ${ $$self{streamidx} }{$sname} = $#{ $$self{streams} };
  55         154  
  55         154  
558             $rv = 0
559             unless $self->bwrite( pack( STRMIDX, $sname, $bn ),
560 55 50       429 STREAMS_POS + STRM_LEN * $#{ $$self{streams} } ) == STRM_LEN;
  55         341  
561             }
562              
563             } else {
564 11         55 pdebug( 'invalid stream name (%s)', PDLEVEL1, $sname );
565 11         22 $rv = 0;
566             }
567              
568 88         341 subPostamble( PDLEVEL3, '$', $rv );
569              
570 88         517 return $rv;
571             }
572              
573             1;
574              
575             __END__