File Coverage

blib/lib/Paranoid/IO/FileMultiplexer/Block.pm
Criterion Covered Total %
statement 117 133 87.9
branch 36 48 75.0
condition 15 21 71.4
subroutine 17 20 85.0
pod 9 9 100.0
total 194 231 83.9


line stmt bran cond sub pod time code
1             # Paranoid::IO::FileMultiplexer::Block -- Block-level Allocator/Accessor
2             #
3             # $Id: lib/Paranoid/IO/FileMultiplexer/Block.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;
33              
34 11     11   165 use 5.008;
  11         33  
35              
36 11     11   55 use strict;
  11         11  
  11         176  
37 11     11   44 use warnings;
  11         11  
  11         231  
38 11     11   44 use vars qw($VERSION);
  11         11  
  11         330  
39 11     11   44 use base qw(Exporter);
  11         110  
  11         627  
40 11     11   55 use Paranoid qw(:all);
  11         22  
  11         891  
41 11     11   55 use Paranoid::IO qw(:all);
  11         22  
  11         1507  
42 11     11   66 use Paranoid::Debug qw(:all);
  11         22  
  11         1573  
43 11     11   77 use Fcntl qw(:DEFAULT :flock :mode :seek);
  11         11  
  11         4213  
44              
45             ($VERSION) = ( q$Revision: 2.10 $ =~ /(\d+(?:\.\d+)+)/sm );
46              
47 11     11   66 use constant MINBSIZE => 4_096;
  11         22  
  11         594  
48 11     11   66 use constant MAXBSIZE => 1_048_576;
  11         11  
  11         9790  
49              
50             #####################################################################
51             #
52             # Module code follows
53             #
54             #####################################################################
55              
56             sub new {
57              
58             # Purpose: Creates a block object
59             # Returns: Object reference or undef
60             # Usage: $obj = Paranoid::IO::FileMultiplexer::Block->new(
61             # $filename, $bnum, $bsize);
62              
63 3697     3697 1 7158 my $class = shift;
64 3697         5199 my $file = shift;
65 3697         6234 my $bnum = shift;
66 3697         5898 my $bsize = shift;
67 3697         15002 my $self = {
68             file => $file,
69             blockNum => 0,
70             blockSize => MINBSIZE,
71             minPos => 0,
72             maxPos => MINBSIZE - 1,
73             };
74              
75 3697         10841 subPreamble( PDLEVEL3, '$$$', $file, $bnum, $bsize );
76              
77 3697         7456 bless $self, $class;
78              
79             # Check mandatory values
80 3697 50 100     27209 $self = undef
      66        
      66        
81             unless defined $bnum
82             and defined $bsize
83             and defined $file
84             and length $file;
85 3697 100       8247 pdebug( 'invalid or missing arguments', PDLEVEL1 )
86             unless defined $self;
87              
88 3697 100       6127 if ( defined $self ) {
89              
90             # Make sure we only have positive values for the block number and size
91 3675         8892 $$self{blockNum} = int $bnum;
92 3675 50       9966 $$self{blockSize} = int $bsize if defined $bsize;
93 3675         9672 $$self{minPos} = $$self{blockNum} * $$self{blockSize};
94 3675         9831 $$self{maxPos} = $$self{minPos} + $$self{blockSize} - 1;
95              
96             # Make sure block size is in range and a multiple of MINBSIZE
97             $self = undef
98             unless $$self{blockSize} >= MINBSIZE
99             and $$self{blockSize} <= MAXBSIZE
100 3675 100 100     24730 and $$self{blockSize} % MINBSIZE == 0;
      100        
101 3675 100       9595 pdebug( 'invalid block size', PDLEVEL1 ) unless defined $self;
102             }
103              
104 3697         11405 subPostamble( PDLEVEL3, '$', $self );
105              
106 3697         11542 return $self;
107             }
108              
109             sub recalibrate {
110              
111             # Purpose: Recalibrates min/max positions in the block based
112             # on block size.
113             # Returns: Boolean
114             # Usage: $rv = $obj->recalibrate;
115              
116 85     85 1 147 my $self = shift;
117              
118 85         247 $$self{minPos} = $$self{blockNum} * $$self{blockSize};
119 85         414 $$self{maxPos} = $$self{minPos} + $$self{blockSize} - 1;
120              
121 85         277 return 1;
122             }
123              
124             sub blockNum {
125              
126             # Purpose: Returns the block number
127             # Returns: Integer
128             # Usage: $bn = $obj->blockNum;
129              
130 0     0 1 0 my $self = shift;
131 0         0 return $$self{blockNum};
132             }
133              
134             sub blockSize {
135              
136             # Purpose: Returns the block size
137             # Returns: Integer
138             # Usage: $bs = $obj->blockSize;
139              
140 166     166 1 335 my $self = shift;
141 166         1171 return $$self{blockSize};
142             }
143              
144             sub minPos {
145              
146             # Purpose: Returns the min writable file position for the block
147             # Returns: Integer
148             # Usage: $minp = $obj->minPos;
149              
150 0     0 1 0 my $self = shift;
151 0         0 return $$self{minPos};
152             }
153              
154             sub maxPos {
155              
156             # Purpose: Returns the max writable file position for the block
157             # Returns: Integer
158             # Usage: $maxp = $obj->maxPos;
159              
160 0     0 1 0 my $self = shift;
161 0         0 return $$self{maxPos};
162             }
163              
164             sub allocate {
165              
166             # Purpose: Writes a new block to disk
167             # Returns: Boolean
168             # Usage: $rv = $obj->allocate;
169              
170 345     345 1 6301 my $self = shift;
171 345         702 my $file = $$self{file};
172 345         740 my $minPos = $$self{minPos};
173 345         771 my $maxPos = $$self{maxPos};
174 345         626 my $rv = 0;
175              
176 345         981 subPreamble(PDLEVEL3);
177              
178 345 50       1273 if ( pflock( $$self{file}, LOCK_EX ) ) {
179              
180             # Seek and write a null byte at the end of the block
181 345         971 pdebug( 'end of file should be at %s', PDLEVEL4, $minPos );
182 345         1093 pseek( $file, 0, SEEK_END );
183 345 100       1027 if ( ptell($file) == $minPos ) {
184 323         1024 pseek( $file, $maxPos, SEEK_SET );
185 323         1153 $rv = pwrite( $file, pack 'x' );
186             } else {
187 22         55 pdebug('block already allocated');
188             }
189              
190 345         1140 pflock( $$self{file}, LOCK_UN );
191             }
192              
193 345         1117 subPostamble( PDLEVEL3, '$', $rv );
194              
195 345         2873 return $rv;
196             }
197              
198             sub bread {
199              
200             # Purpose: Reads the contents of the entire block. or a specified range
201             # Returns: Integer (bytes read) or undef on error
202             # Usage: $bytesRead = $obj->bread(\$content);
203             # Usage: $bytesRead = $obj->bread(\$content, $start);
204             # Usage: $bytesRead = $obj->bread(\$content, undef, $bytes);
205             # Usage: $bytesRead = $obj->bread(\$content, $start, $bytes);
206              
207 8955     8955 1 16032 my $self = shift;
208 8955         13495 my $cref = shift;
209 8955         15384 my $start = shift;
210 8955         14193 my $bytes = shift;
211 8955         16967 my $file = $$self{file};
212 8955         19814 my $bsize = $$self{blockSize};
213 8955         17341 my $minp = $$self{minPos};
214 8955         16924 my $maxp = $$self{maxPos};
215 8955         14558 my $rv = PTRUE_ZERO;
216              
217 8955         23470 subPreamble( PDLEVEL3, '$;$$', $cref, $start, $bytes );
218              
219             # NOTE: This method intentionally allows reads of a length greater than
220             # the block size, but it will only return content from within the block
221             # boundaries.
222              
223             # Error out if we were not given a valid scalar ref
224 8955 50 33     36501 unless ( defined $cref and ref($cref) eq 'SCALAR' ) {
225 0         0 $rv = undef;
226 0         0 pdebug( 'invalid argument for content ref', PDLEVEL1 );
227             }
228              
229             # Set start to beginning of block if not specified
230 8955 100       19729 $start = 0 unless defined $start;
231              
232             # Set default bytes if not specified
233 8955 100       22721 $bytes = $bsize - $start unless defined $bytes;
234              
235             # Make sure start is in range
236 8955 50       26978 if ( $minp + $start > $maxp ) {
237 0         0 pdebug( 'starting position is out of range', PDLEVEL1 );
238 0         0 $rv = undef;
239             }
240              
241 8955 50       17565 if ($rv) {
242              
243             # Make sure we limit read to our block
244 8955 100       24472 $bytes = ( $maxp + 1 ) - ( $minp + $start )
245             if ( $minp + $start + $bytes ) > ( $maxp + 1 );
246              
247             # Perform the read
248 8955 50       30383 if ( pseek( $file, $minp + $start, SEEK_SET ) ) {
249 8955         26828 $rv = pread( $file, $$cref, $bytes );
250             } else {
251 0         0 $rv = undef;
252             }
253             }
254              
255 8955         29850 subPostamble( PDLEVEL3, '$', $rv );
256              
257 8955         46820 return $rv;
258             }
259              
260             sub bwrite {
261              
262             # Purpose: Writes the contents of the entire block. or a specified range
263             # Returns: Integer (bytes written) or undef on error
264             # Usage: $bytesWritten = $obj->bwrite($content);
265             # Usage: $bytesWritten = $obj->bwrite($content, $start );
266             # Usage: $bytesWritten = $obj->bwrite($content, $start, $length );
267             # Usage: $bytesWritten = $obj->bwrite($content, $start, $length, $offset );
268              
269 1925     1925 1 3514 my $self = shift;
270 1925         2944 my $content = shift;
271 1925         3297 my $start = shift;
272 1925         3061 my $length = shift;
273 1925         3424 my $offset = shift;
274 1925         4116 my $file = $$self{file};
275 1925         4033 my $bsize = $$self{blockSize};
276 1925         3781 my $minp = $$self{minPos};
277 1925         3554 my $maxp = $$self{maxPos};
278 1925         3299 my $rv = PTRUE_ZERO;
279 1925 50       6988 my $cdata = defined $content ? ( length $content ) . ' bytes' : undef;
280 1925         2890 my $blkLeft;
281              
282 1925         5253 subPreamble( PDLEVEL3, '$;$$$', $cdata, $start, $length, $offset );
283              
284             # NOTE: This method intentionally allows writes of a length greater than
285             # the block size, but it will only write content from within the block
286             # boundaries.
287              
288             # Error out if we were not given a valid scalar ref
289 1925 50 33     9275 unless ( defined $content and length $content ) {
290 0         0 $rv = undef;
291 0         0 pdebug( 'invalid argument for content', PDLEVEL1 );
292             }
293              
294             # Set start to beginning of block if not specified
295 1925 100       5145 $start = 0 unless defined $start;
296              
297             # Set offset to zero if not specified
298 1925 100       4796 $offset = 0 unless defined $offset;
299              
300             # Set length to max content length available if not defined
301 1925 100       4563 $length = length($content) - $offset unless defined $length;
302 1925         3989 $blkLeft = $bsize - $start;
303 1925 100       4649 $length = $blkLeft if $blkLeft < $length;
304              
305             # Make sure start is in range
306 1925 50       5431 if ( $minp + $start > $maxp ) {
307 0         0 pdebug( 'starting position is out of range', PDLEVEL1 );
308 0         0 $rv = undef;
309             }
310              
311 1925 50       4099 if ($rv) {
312              
313             # Perform the write
314 1925 50       6384 if ( pseek( $file, $minp + $start, SEEK_SET ) ) {
315 1925         5891 $rv = pwrite( $file, $content, $length, $offset );
316             } else {
317 0         0 $rv = undef;
318             }
319             }
320              
321 1925         6615 subPostamble( PDLEVEL3, '$', $rv );
322              
323 1925         10552 return $rv;
324             }
325              
326             1;
327              
328             __END__