File Coverage

blib/lib/Paranoid/IO/Line.pm
Criterion Covered Total %
statement 203 216 93.9
branch 59 66 89.3
condition 18 22 81.8
subroutine 25 28 89.2
pod 8 8 100.0
total 313 340 92.0


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