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.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 - 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   29202 use 5.008;
  33         127  
35              
36 33     33   175 use strict;
  33         90  
  33         752  
37 33     33   176 use warnings;
  33         59  
  33         1200  
38 33     33   176 use vars qw($VERSION @EXPORT @EXPORT_OK %EXPORT_TAGS);
  33         104  
  33         2303  
39 33     33   229 use base qw(Exporter);
  33         77  
  33         3378  
40 33     33   256 use Fcntl qw(:DEFAULT :seek :flock :mode);
  33         66  
  33         16884  
41 33     33   263 use Paranoid qw(:all);
  33         78  
  33         3550  
42 33     33   250 use Paranoid::Debug qw(:all);
  33         65  
  33         5635  
43 33     33   7241 use Paranoid::IO qw(:all);
  33         84  
  33         5598  
44 33     33   258 use Paranoid::Input qw(:all);
  33         78  
  33         6233  
45              
46             ($VERSION) = ( q$Revision: 2.09 $ =~ /(\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   260 use constant STAT_INO => 1;
  33         75  
  33         2795  
53 33     33   217 use constant STAT_SIZ => 7;
  33         65  
  33         1648  
54 33     33   213 use constant PDEFLNSZ => 2048;
  33         66  
  33         1546  
55              
56 33     33   471 use constant PBFLAG => 0;
  33         122  
  33         1987  
57 33     33   207 use constant PBBUFF => 1;
  33         71  
  33         1719  
58              
59 33     33   191 use constant PBF_DRAIN => 0;
  33         66  
  33         1701  
60 33     33   182 use constant PBF_NORMAL => 1;
  33         65  
  33         2267  
61 33     33   225 use constant PBF_DELETE => -1;
  33         79  
  33         62658  
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 6976 $mlnsz;
80             }
81              
82             # Manage buffers: $buffers{$name} => [$flag, $content ];
83             my %buffers;
84              
85 18     18   87 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   219 my $file = shift;
95 102         168 my $rv = 0;
96 102         216 my ( $fh, $fpos, @fstat, @fhstat );
97              
98 102         343 pdebug( 'entering w/(%s)', PDLEVEL3, $file );
99 102         315 pIn();
100              
101             # Check to see if we can get a valid file handle
102 102 100       430 if ( defined( $fh = popen( $file, O_RDONLY ) ) ) {
103 98         1778 @fhstat = stat $fh;
104 98         612 $fpos = ptell($fh);
105              
106 98 100 66     702 if ( @fhstat and $fpos < $fhstat[STAT_SIZ] ) {
107              
108             # Still have content to read, continue on
109 82         279 pdebug( 'still have content to drain', PDLEVEL3 );
110 82         174 $rv = 1;
111              
112             } else {
113              
114             # Check the file system to see if we're still
115             # operating on the same file
116 16         410 @fstat = stat $file;
117              
118 16 100       72 if ( scalar @fstat ) {
119              
120             # Check inode
121 14 100       45 if ( $fhstat[STAT_INO] != $fstat[STAT_INO] ) {
122 2         12 pdebug( 'file was replaced with a new file',
123             PDLEVEL3 );
124             } else {
125 12 100       35 if ( $fstat[STAT_SIZ] < $fpos ) {
126 2         12 pdebug( 'file was truncated', PDLEVEL3 );
127             } else {
128 10         42 pdebug( 'file is unchanged', PDLEVEL3 );
129 10         24 $rv = 1;
130             }
131             }
132              
133             } else {
134 2         12 pdebug( 'file was deleted', PDLEVEL3 );
135             }
136             }
137             } else {
138 4         12 pdebug( 'invalid/non-existent file', PDLEVEL3 );
139             }
140              
141 102         344 pOut();
142 102         282 pdebug( 'leaving w/rv: %s', PDLEVEL3, $rv );
143              
144 102         407 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 66     66 1 210 my $file = shift;
155              
156 66         353 delete $buffers{$file};
157              
158 66         387 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 102     102 1 1373 my $file = shift;
170 102         191 my $aref = shift;
171 102         188 my $doChomp = shift;
172 102         164 my $noLocks = shift;
173 102         192 my $rv = 1;
174 102         216 my ( $buffer, $bflag, $in, $content, $bread, $irv, @tmp, $line );
175              
176 102         333 pdebug( 'entering w/(%s)(%s)(%s)', PDLEVEL1, $file, $aref, $doChomp );
177 102         334 pIn();
178              
179 102         373 @$aref = ();
180              
181             # Check the file
182 102 100       455 piolClose($file) unless _chkStat($file);
183              
184             # Get/initialize buffer
185 102 100       349 if ( exists $buffers{$file} ) {
186 42         103 $bflag = $buffers{$file}[PBFLAG];
187 42         116 $buffer = $buffers{$file}[PBBUFF];
188             } else {
189 60         249 $buffers{$file} = [ PBF_NORMAL, '' ];
190 60         130 $buffer = '';
191 60         316 $bflag = PBF_NORMAL;
192             }
193              
194             # Read what we can
195 102         303 $content = '';
196 102         240 $bread = 0;
197 102         386 while ( $bread < PIOMAXFSIZE ) {
198 427 50       1475 $irv = $noLocks ? pnlread( $file, $in ) : pread( $file, $in );
199 427 100       1215 if ( defined $irv ) {
200 421         928 $bread += $irv;
201 421         1951 $content .= $in;
202 421 100       1246 last if $irv < PIOBLKSIZE;
203             } else {
204 6         17 $rv = undef;
205 6         18 last;
206             }
207             }
208              
209             # Post processing
210 102 100       513 if ($rv) {
211              
212 96 100       559 if ( length $content ) {
213              
214             # Add the buffer
215 86         973 $content = "$buffer$content";
216              
217             # Process buffer drain conditions
218 86         545 pdebug( 'starting buffer flag: (%s)', PDLEVEL4, $bflag );
219 86         295 pdebug( 'starting buffer: (%s)', PDLEVEL4, $buffer );
220 86 100 100     569 if ( !$bflag and $content =~ /@{[NEWLINE_REGEX]}/so ) {
  2         48  
221 8         30 pdebug( 'draining to next newline', PDLEVEL4 );
222 8         457 $content =~ s/^.*?@{[NEWLINE_REGEX]}//so;
  2         344  
223 8         24 $bflag = PBF_NORMAL;
224 8         19 $buffer = '';
225             }
226              
227             # Check for newlines
228 86 100       650 if ( $content =~ /@{[NEWLINE_REGEX]}/so ) {
  22         469  
229              
230             # Split lines along newline boundaries
231 80         3998 @tmp = split m/(@{[NEWLINE_REGEX]})/so, $content;
  22         3167  
232 80         444 while ( scalar @tmp > 1 ) {
233 3330 100       6463 if ( length $tmp[0] > PIOMAXLNSIZE ) {
234 2         11 splice @tmp, 0, 2;
235 2         6 $line = undef;
236             } else {
237 3328         8965 $line = join '', splice @tmp, 0, 2;
238             }
239 3330         9505 push @$aref, $line;
240             }
241              
242             # Check for undefined lines
243 80         207 $rv = scalar @$aref;
244 80         284 @$aref = grep {defined} @$aref;
  3330         6314  
245 80 100       303 if ( $rv != scalar @$aref ) {
246 2         11 Paranoid::ERROR =
247             pdebug( 'found %s lines over PIOMAXLNSIZE',
248             PDLEVEL1, $rv - @$aref );
249 2         6 $rv = undef;
250             }
251              
252             # Check for an unterminated line at the end and
253             # buffer appropriately
254 80 100       245 if ( scalar @tmp ) {
255              
256             # Content left over, update the buffer
257 26 100       98 if ( length $tmp[0] > PIOMAXLNSIZE ) {
258 8         23 $buffer = '';
259 8         24 $bflag = PBF_DRAIN;
260 8         19 $rv = undef;
261 8         25 Paranoid::ERROR =
262             pdebug( 'buffer is over PIOMAXLNSIZE',
263             PDLEVEL1 );
264             } else {
265 18         57 $buffer = $tmp[0];
266 18         54 $bflag = PBF_NORMAL;
267             }
268             } else {
269              
270             # Nothing left over, make sure the buffer is empty
271 54         154 $buffer = '';
272 54         125 $bflag = PBF_NORMAL;
273             }
274              
275             } else {
276              
277             # Check buffered block for PIOILNSIZE limit
278 6 100       29 if ( length $content > PIOMAXLNSIZE ) {
279 2         9 $buffer = '';
280 2         6 $bflag = PBF_DRAIN;
281 2         6 $rv = undef;
282 2         8 Paranoid::ERROR =
283             pdebug( 'block is over PIOMAXLNSIZE', PDLEVEL1 );
284             } else {
285 4         12 $rv = 0;
286 4         12 $buffer = $content;
287 4         12 $bflag = PBF_NORMAL;
288             }
289             }
290 86         581 pdebug( 'ending buffer flag: (%s)', PDLEVEL4, $bflag );
291 86         259 pdebug( 'ending buffer: (%s)', PDLEVEL4, $buffer );
292              
293             } else {
294 10         38 $rv = 0;
295             }
296             }
297              
298             # Set PTRUE_ZERO if needed
299 102 100 100     607 $rv = PTRUE_ZERO if defined $rv and $rv == 0;
300              
301             # Save the buffer
302 102         268 $buffers{$file}[PBFLAG] = $bflag;
303 102         286 $buffers{$file}[PBBUFF] = $buffer;
304              
305             # Chomp if necessary
306 102 100 100     595 pchomp(@$aref) if $doChomp and scalar @$aref;
307              
308 102         394 pdebug( 'returning %s lines', PDLEVEL2, scalar @$aref );
309              
310 102         402 pOut();
311 102         302 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
312              
313 102         500 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 18     18 1 48 my $file = shift;
341 18         42 my $aref = shift;
342 18   50     88 my $doChomp = shift || 0;
343 18   100     61 my $offset = shift || -10;
344 18         33 my $noLocks = shift;
345 18         39 my ( $rv, $ofsb, @lines );
346              
347 18         74 pdebug( 'entering w/(%s)(%s)(%s)(%s)',
348             PDLEVEL1, $file, $aref, $doChomp, $offset );
349 18         54 pIn();
350              
351 18         82 @$aref = ();
352              
353             # Check to see if we've already opened this file
354 18 100       56 if ( _chkBuffer($file) ) {
355              
356             # Offset is only used on the initial open
357 12         29 $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 6         22 $ofsb = $offset * ( PIOMAXLNSIZE +1 );
366 6 50       21 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 6 50       27 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 18 100       52 if ($offset) {
377              
378             # Consume everything to the end of the file
379 6         13 do {
380 22 50       91 $noLocks
381             ? nlsip( $file, @lines, $doChomp )
382             : sip( $file, @lines, $doChomp );
383 22         216 push @$aref, @lines;
384             } while scalar @lines;
385              
386             # Trim list to the request size
387 6 50       34 if ( scalar @$aref > abs $offset ) {
388 6         77 splice @$aref, 0, @$aref - abs $offset;
389             }
390 6         13 $rv = scalar @$aref;
391 6 50       20 $rv = PTRUE_ZERO unless $rv;
392              
393             } else {
394              
395             # Do a single sip
396 12 50       50 $rv =
397             $noLocks
398             ? nlsip( $file, @$aref, $doChomp )
399             : sip( $file, @$aref, $doChomp );
400             }
401              
402 18         71 pOut();
403 18         46 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
404              
405 18         131 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 48     48 1 935 my $file = shift;
430 48         99 my $aref = shift;
431 48   100     188 my $doChomp = shift || 0;
432 48         97 my $noLocks = shift;
433 48         99 my $rv = 1;
434 48         92 my @fstat;
435              
436 48         216 pdebug( 'entering w/(%s)(%s)(%s)', PDLEVEL1, $file, $aref, $doChomp );
437 48         186 pIn();
438              
439             # Start sipping
440 48         205 $rv = sip( $file, @$aref, $doChomp, $noLocks );
441 48 100       190 if ( ref $file eq 'GLOB' ) {
442 4 100       59 @fstat = stat $file if fileno $file;
443             } else {
444 44         1316 @fstat = stat $file;
445             }
446 48 100 100     407 if ( scalar @fstat and $fstat[STAT_SIZ] > PIOMAXFSIZE ) {
447 2         13 Paranoid::ERROR = pdebug( 'file size exceeds PIOMAXFSIZE', PDLEVEL1 );
448 2         6 $rv = undef;
449             }
450              
451             # Count lins if sip never complained
452 48 100       411 $rv = scalar @$aref if defined $rv;
453              
454             # Close everything out
455 48         221 piolClose($file);
456              
457 48         144 pOut();
458 48         163 pdebug( 'leaving w/rv: %s', PDLEVEL1, $rv );
459              
460 48         224 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__