File Coverage

blib/lib/Paranoid/IO/Line.pm
Criterion Covered Total %
statement 211 224 94.2
branch 59 66 89.3
condition 18 22 81.8
subroutine 25 28 89.2
pod 8 8 100.0
total 321 348 92.2


line stmt bran cond sub pod time code
1             # Paranoid::IO::Line -- Paranoid Line-based I/O functions
2             #
3             # $Id: lib/Paranoid/IO/Line.pm, 2.08 2020/12/31 12:10:06 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 - 2020, Arthur Corliss (corliss@digitalmages.com)
22             # (tm) 2008 - 2020, Paranoid Inc. (www.paranoid.com)
23             #
24             #####################################################################
25              
26             #####################################################################
27             #
28             # Environment definitions
29             #
30             #####################################################################
31              
32             package Paranoid::IO::Line;
33              
34 26     26   18846 use 5.008;
  26         116  
35              
36 26     26   160 use strict;
  26         52  
  26         517  
37 26     26   130 use warnings;
  26         45  
  26         925  
38 26     26   151 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  26         53  
  26         1925  
39 26     26   186 use base qw(Exporter);
  26         51  
  26         2255  
40 26     26   182 use Fcntl qw(:DEFAULT :seek :flock :mode);
  26         51  
  26         12548  
41 26     26   201 use Paranoid qw(:all);
  26         53  
  26         2998  
42 26     26   221 use Paranoid::Debug qw(:all);
  26         52  
  26         4116  
43 26     26   3279 use Paranoid::IO qw(:all);
  26         52  
  26         3751  
44 26     26   189 use Paranoid::Input qw(:all);
  26         52  
  26         4563  
45              
46             ($VERSION) = ( q$Revision: 2.08 $ =~ /(\d+(?:\.\d+)+)/sm );
47              
48             @EXPORT = qw(sip nlsip tailf nltailf slurp nlslurp piolClose);
49             @EXPORT_OK = ( @EXPORT, qw(PIOMAXLNSIZE) );
50             %EXPORT_TAGS = ( all => [@EXPORT_OK], );
51              
52 26     26   186 use constant STAT_INO => 1;
  26         70  
  26         1661  
53 26     26   176 use constant STAT_SIZ => 7;
  26         52  
  26         1366  
54 26     26   186 use constant PDEFLNSZ => 2048;
  26         60  
  26         1499  
55              
56 26     26   156 use constant PBFLAG => 0;
  26         51  
  26         1376  
57 26     26   156 use constant PBBUFF => 1;
  26         52  
  26         1194  
58              
59 26     26   139 use constant PBF_DRAIN => 0;
  26         52  
  26         1388  
60 26     26   178 use constant PBF_NORMAL => 1;
  26         51  
  26         1502  
61 26     26   163 use constant PBF_DELETE => -1;
  26         52  
  26         47444  
62              
63             #####################################################################
64             #
65             # Module code follows
66             #
67             #####################################################################
68              
69             {
70             my $mlnsz = PDEFLNSZ;
71              
72             sub PIOMAXLNSIZE : lvalue {
73              
74             # Purpose: Gets/sets default line size of I/O
75             # Returns: $mlnsz
76             # Usage: $limit = PIOMAXLNSIZE;
77             # Usage: FSZLIMIT = 100;
78              
79 2305     2305 1 4685 $mlnsz;
80             }
81              
82             # Manage buffers: $buffers{$name} => [$flag, $content ];
83             my %buffers;
84              
85 9     9   28 sub _chkBuffer { return exists $buffers{ $_[0] } }
86              
87             sub _chkStat {
88              
89             # Purpose: Checks stat data to see if the underlying
90             # file has changed
91             # Returns: Boolean
92             # Usage: $rv = _chkStat($file);
93              
94 69     69   125 my $file = shift;
95 69         121 my $rv = 0;
96 69         549 my ( $fh, $fpos, @fstat, @fhstat );
97              
98 69         204 pdebug( 'entering w/(%s)', PDLEVEL3, $file );
99 69         208 pIn();
100              
101             # Check to see if we can get a valid file handle
102 69 100       257 if ( defined( $fh = popen( $file, O_RDONLY ) ) ) {
103 67         1564 @fhstat = stat $fh;
104 67         476 $fpos = ptell($fh);
105              
106 67 100 66     359 if ( @fhstat and $fpos < $fhstat[STAT_SIZ] ) {
107              
108             # Still have content to read, continue on
109 59         174 pdebug( 'still have content to drain', PDLEVEL3 );
110 59         113 $rv = 1;
111              
112             } else {
113              
114             # Check the file system to see if we're still
115             # operating on the same file
116 8         151 @fstat = stat $file;
117              
118 8 100       28 if ( scalar @fstat ) {
119              
120             # Check inode
121 7 100       18 if ( $fhstat[STAT_INO] != $fstat[STAT_INO] ) {
122 1         21 pdebug( 'file was replaced with a new file',
123             PDLEVEL3 );
124             } else {
125 6 100       19 if ( $fstat[STAT_SIZ] < $fpos ) {
126 1         6 pdebug( 'file was truncated', PDLEVEL3 );
127             } else {
128 5         21 pdebug( 'file is unchanged', PDLEVEL3 );
129 5         14 $rv = 1;
130             }
131             }
132              
133             } else {
134 1         5 pdebug( 'file was deleted', PDLEVEL3 );
135             }
136             }
137             } else {
138 2         7 pdebug( 'invalid/non-existent file', PDLEVEL3 );
139             }
140              
141 69         199 pOut();
142 69         171 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
143              
144 69         234 return $rv;
145             }
146              
147             sub piolClose {
148              
149             # Purpose: Closes file handles and deletes the associated
150             # buffer
151             # Returns: Boolean
152             # Usage: $rv = piolClose($file);
153              
154 51     51 1 94 my $file = shift;
155              
156 51         189 delete $buffers{$file};
157              
158 51         185 return pclose($file);
159             }
160              
161             sub sip ($\@;$$) {
162              
163             # Purpose: Reads a chunk from the passwed handle or file name
164             # Returns: Number of lines read or undef critical failures
165             # Usage: $nlines = sip($fh, @lines);
166             # Usage: $nlines = sip($filename, @lines);
167             # Usage: $nlines = sip($filename, @lines, 1);
168              
169 69     69 1 659 my $file = shift;
170 69         128 my $aref = shift;
171 69         124 my $doChomp = shift;
172 69         293 my $noLocks = shift;
173 69         150 my $rv = 1;
174 69         150 my ( $buffer, $bflag, $in, $content, $bread, $irv, @tmp, $line );
175              
176 69         206 pdebug( 'entering w/(%s)(%s)(%s)', PDLEVEL1, $file, $aref, $doChomp );
177 69         208 pIn();
178              
179 69         201 @$aref = ();
180              
181             # Check the file
182 69 100       194 piolClose($file) unless _chkStat($file);
183              
184             # Get/initialize buffer
185 69 100       207 if ( exists $buffers{$file} ) {
186 21         40 $bflag = $buffers{$file}[PBFLAG];
187 21         50 $buffer = $buffers{$file}[PBBUFF];
188             } else {
189 48         148 $buffers{$file} = [ PBF_NORMAL, '' ];
190 48         77 $buffer = '';
191 48         100 $bflag = PBF_NORMAL;
192             }
193              
194             # Read what we can
195 69         297 $content = '';
196 69         116 $bread = 0;
197 69         216 while ( $bread < PIOMAXFSIZE ) {
198 231 50       726 $irv = $noLocks ? pnlread( $file, $in ) : pread( $file, $in );
199 231 100       625 if ( defined $irv ) {
200 228         444 $bread += $irv;
201 228         857 $content .= $in;
202 228 100       633 last if $irv < PIOBLKSIZE;
203             } else {
204 3         7 $rv = undef;
205 3         7 last;
206             }
207             }
208              
209             # Post processing
210 69 100       419 if ($rv) {
211              
212 66 100       253 if ( length $content ) {
213              
214             # Add the buffer
215 61         363 $content = "$buffer$content";
216              
217             # Process buffer drain conditions
218 61         301 pdebug( 'starting buffer flag: (%s)', PDLEVEL4, $bflag );
219 61         176 pdebug( 'starting buffer: (%s)', PDLEVEL4, $buffer );
220 61 100 100     242 if ( !$bflag and $content =~ /@{[NEWLINE_REGEX]}/so ) {
  1         17  
221 4         13 pdebug( 'draining to next newline', PDLEVEL4 );
222 4         214 $content =~ s/^.*?@{[NEWLINE_REGEX]}//so;
  1         171  
223 4         12 $bflag = PBF_NORMAL;
224 4         9 $buffer = '';
225             }
226              
227             # Check for newlines
228 61 100       273 if ( $content =~ /@{[NEWLINE_REGEX]}/so ) {
  20         307  
229              
230             # Split lines along newline boundaries
231 58         2213 @tmp = split m/(@{[NEWLINE_REGEX]})/so, $content;
  20         2172  
232 58         275 while ( scalar @tmp > 1 ) {
233 2286 100       4075 if ( length $tmp[0] > PIOMAXLNSIZE ) {
234 1         4 splice @tmp, 0, 2;
235 1         3 $line = undef;
236             } else {
237 2285         5812 $line = join '', splice @tmp, 0, 2;
238             }
239 2286         6267 push @$aref, $line;
240             }
241              
242             # Check for undefined lines
243 58         114 $rv = scalar @$aref;
244 58         320 @$aref = grep {defined} @$aref;
  2286         3982  
245 58 100       168 if ( $rv != scalar @$aref ) {
246 1         5 Paranoid::ERROR =
247             pdebug( 'found %s lines over PIOMAXLNSIZE',
248             PDLEVEL1, $rv - @$aref );
249 1         3 $rv = undef;
250             }
251              
252             # Check for an unterminated line at the end and
253             # buffer appropriately
254 58 100       162 if ( scalar @tmp ) {
255              
256             # Content left over, update the buffer
257 13 100       40 if ( length $tmp[0] > PIOMAXLNSIZE ) {
258 4         11 $buffer = '';
259 4         7 $bflag = PBF_DRAIN;
260 4         8 $rv = undef;
261 4         11 Paranoid::ERROR =
262             pdebug( 'buffer is over PIOMAXLNSIZE',
263             PDLEVEL1 );
264             } else {
265 9         27 $buffer = $tmp[0];
266 9         43 $bflag = PBF_NORMAL;
267             }
268             } else {
269              
270             # Nothing left over, make sure the buffer is empty
271 45         109 $buffer = '';
272 45         96 $bflag = PBF_NORMAL;
273             }
274              
275             } else {
276              
277             # Check buffered block for PIOILNSIZE limit
278 3 100       13 if ( length $content > PIOMAXLNSIZE ) {
279 1         4 $buffer = '';
280 1         3 $bflag = PBF_DRAIN;
281 1         3 $rv = undef;
282 1         4 Paranoid::ERROR =
283             pdebug( 'block is over PIOMAXLNSIZE', PDLEVEL1 );
284             } else {
285 2         6 $rv = 0;
286 2         5 $buffer = $content;
287 2         5 $bflag = PBF_NORMAL;
288             }
289             }
290 61         226 pdebug( 'ending buffer flag: (%s)', PDLEVEL4, $bflag );
291 61         170 pdebug( 'ending buffer: (%s)', PDLEVEL4, $buffer );
292              
293             } else {
294 5         10 $rv = 0;
295             }
296             }
297              
298             # Set PTRUE_ZERO if needed
299 69 100 100     398 $rv = PTRUE_ZERO if defined $rv and $rv == 0;
300              
301             # Save the buffer
302 69         162 $buffers{$file}[PBFLAG] = $bflag;
303 69         130 $buffers{$file}[PBBUFF] = $buffer;
304              
305             # Chomp if necessary
306 69 100 100     404 pchomp(@$aref) if $doChomp and scalar @$aref;
307              
308 69         227 pdebug( 'returning %s lines', PDLEVEL2, scalar @$aref );
309              
310 69         202 pOut();
311 69         166 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
312              
313 69         269 return $rv;
314             }
315              
316             }
317              
318             sub nlsip {
319              
320             # Purpose: Wrapper for sip that enables non-locking reads
321             # Returns: Return value from sip
322             # Usage: $nlines = nlsip($file, @lines);
323              
324 0     0 1 0 my $file = shift;
325 0         0 my $aref = shift;
326 0         0 my $doChomp = shift;
327              
328 0         0 return sip( $file, @$aref, $doChomp, 1 );
329             }
330              
331             sub tailf ($\@;$$$) {
332              
333             # Purpose: Augments sip's tailing abilities by seeking to
334             # the end (or, optionally, backwards)
335             # Returns: Number of lines tailed
336             # Usage: $nlines = tail($filename, @lines);
337             # Usage: $nlines = tail($filename, @lines, $chomp);
338             # Usage: $nlines = tail($filename, @lines, $lnOffset);
339              
340 9     9 1 21 my $file = shift;
341 9         17 my $aref = shift;
342 9   50     39 my $doChomp = shift || 0;
343 9   100     24 my $offset = shift || -10;
344 9         12 my $noLocks = shift;
345 9         15 my ( $rv, $ofsb, @lines );
346              
347 9         31 pdebug( 'entering w/(%s)(%s)(%s)(%s)',
348             PDLEVEL1, $file, $aref, $doChomp, $offset );
349 9         27 pIn();
350              
351 9         29 @$aref = ();
352              
353             # Check to see if we've already opened this file
354 9 100       21 if ( _chkBuffer($file) ) {
355              
356             # Offset is only used on the initial open
357 6         10 $offset = 0;
358              
359             } else {
360              
361             # TODO: At some point we might want to honor positive offsets to mimic
362             # the behavior of UNIX tail
363              
364             # Calculate how far back we need to go from the end
365 3         8 $ofsb = $offset * ( PIOMAXLNSIZE +1 );
366 3 50       12 Paranoid::ERROR =
367             pdebug( 'WARNING: called with a positive line offset', PDLEVEL1 )
368             unless $ofsb < 0;
369              
370             # Open the file and move the cursor
371 3 50       10 pseek( $file, $ofsb, SEEK_END ) if popen( $file, O_RDONLY );
372              
373             }
374              
375             # If $offset is set we have trailing lines to handle
376 9 100       22 if ($offset) {
377              
378             # Consume everything to the end of the file
379 3         5 do {
380 11 50       379 $noLocks
381             ? nlsip( $file, @lines, $doChomp )
382             : sip( $file, @lines, $doChomp );
383 11         122 push @$aref, @lines;
384             } while scalar @lines;
385              
386             # Trim list to the request size
387 3 50       25 if ( scalar @$aref > abs $offset ) {
388 3         25 splice @$aref, 0, @$aref - abs $offset;
389             }
390 3         8 $rv = scalar @$aref;
391 3 50       8 $rv = PTRUE_ZERO unless $rv;
392              
393             } else {
394              
395             # Do a single sip
396 6 50       20 $rv =
397             $noLocks
398             ? nlsip( $file, @$aref, $doChomp )
399             : sip( $file, @$aref, $doChomp );
400             }
401              
402 9         26 pOut();
403 9         26 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
404              
405 9         51 return $rv;
406             }
407              
408             sub nltailf ($\@;$$$) {
409              
410             # Purpose: Wrapper for sip that enables non-locking reads
411             # Returns: Return value from sip
412             # Usage: $nlines = nlsip($file, @lines);
413              
414 0     0 1 0 my $file = shift;
415 0         0 my $aref = shift;
416 0         0 my $doChomp = shift;
417 0         0 my $offset = shift;
418              
419 0         0 return tailf( $file, @$aref, $doChomp, $offset, 1 );
420             }
421              
422             sub slurp ($\@;$$) {
423              
424             # Purpose: Reads a file into memory
425             # Returns: Number of lines read/undef
426             # Usage: $nlines = slurp($filename, @lines;
427             # Usage: $nlines = slurp($filename, @lines, 1);
428              
429 42     42 1 551 my $file = shift;
430 42         67 my $aref = shift;
431 42   100     119 my $doChomp = shift || 0;
432 42         84 my $noLocks = shift;
433 42         73 my $rv = 1;
434 42         65 my @fstat;
435              
436 42         153 pdebug( 'entering w/(%s)(%s)(%s)', PDLEVEL1, $file, $aref, $doChomp );
437 42         129 pIn();
438              
439             # Start sipping
440 42         136 $rv = sip( $file, @$aref, $doChomp, $noLocks );
441 42 100       159 if ( ref $file eq 'GLOB' ) {
442 2 100       23 @fstat = stat $file if fileno $file;
443             } else {
444 40         777 @fstat = stat $file;
445             }
446 42 100 100     319 if ( scalar @fstat and $fstat[STAT_SIZ] > PIOMAXFSIZE ) {
447 1         6 Paranoid::ERROR = pdebug( 'file size exceeds PIOMAXFSIZE', PDLEVEL1 );
448 1         3 $rv = undef;
449             }
450              
451             # Count lins if sip never complained
452 42 100       149 $rv = scalar @$aref if defined $rv;
453              
454             # Close everything out
455 42         163 piolClose($file);
456              
457 42         127 pOut();
458 42         152 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
459              
460 42         174 return $rv;
461             }
462              
463             sub nlslurp ($\@;$$) {
464              
465             # Purpose: Performs a non-locking slurp
466             # Returns: Number of lines/undef
467             # Usage: $nlines = nlslurp($filename, @lines);
468             # Usage: $nlines = nlslurp($filename, @lines, 1);
469              
470 0     0 1   my $file = shift;
471 0           my $aref = shift;
472 0   0       my $doChomp = shift || 0;
473              
474 0           return slurp( $file, @$aref, $doChomp, 1 );
475             }
476              
477             1;
478              
479             __END__