File Coverage

blib/lib/File/Tail.pm
Criterion Covered Total %
statement 209 318 65.7
branch 68 164 41.4
condition 28 46 60.8
subroutine 24 40 60.0
pod 14 26 53.8
total 343 594 57.7


line stmt bran cond sub pod time code
1             package File::Tail;
2              
3 3     3   4689 use strict;
  3         3  
  3         98  
4 3     3   12 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $Is_Win32);
  3         3  
  3         316  
5              
6             $Is_Win32 = ($^O =~ /win32/i) ? 1 : 0;
7              
8             require Exporter;
9              
10             @ISA = qw(Exporter);
11             # Items to export into callers namespace by default. Note: do not export
12             # names by default without a very good reason. Use EXPORT_OK instead.
13             # Do not simply export all your public functions/methods/constants.
14             $VERSION = '1.2';
15              
16              
17             # Preloaded methods go here.
18              
19 3     3   1246 use FileHandle;
  3         23032  
  3         17  
20             #use IO::Seekable; # does not define SEEK_SET in 5005.02
21 3     3   2187 use File::stat;
  3         17951  
  3         18  
22 3     3   138 use Carp;
  3         5  
  3         138  
23 3     3   1598 use Time::HiRes qw ( time sleep ); #import hires microsecond timers
  3         3491  
  3         11  
24              
25             sub SEEK_SET () {0;}
26             sub SEEK_CUR () {1;}
27             sub SEEK_END () {2;}
28              
29              
30             sub interval {
31 164     164 1 154 my $object=shift @_;
32 164 100       275 if (@_) {
33 20         28 $object->{interval}=shift;
34 20 100       75 $object->{interval}=$object->{maxinterval} if
35             $object->{interval}>$object->{maxinterval};
36             }
37 164         62614505 $object->{interval};
38             }
39              
40             sub logit {
41 0     0 0 0 my $object=shift;
42 0         0 my @call=caller(1);
43 0 0       0 print # STDERR
44             # time()." ".
45             "\033[7m".
46             $call[3]." ".$object->{"input"}." ".join("",@_).
47             "\033[0m".
48             "\n"
49             if $object->debug;
50             }
51              
52             sub adjustafter {
53 53     53 1 70 my $self=shift;
54 53 100       167 $self->{adjustafter}=shift if @_;
55 53         145 return $self->{adjustafter};
56             }
57              
58             sub debug {
59 0     0 1 0 my $self=shift;
60 0 0       0 $self->{"debug"}=shift if @_;
61 0         0 return $self->{"debug"};
62             }
63              
64             sub errmode {
65 9     9 1 14 my($self, $mode) = @_;
66 9         17 my($prev) = $self->{errormode};
67            
68 9 50       23 if (@_ >= 2) {
69             ## Set the error mode.
70 9 50       16 defined $mode or $mode = '';
71 9 50       29 if (ref($mode) eq 'CODE') {
    50          
72 0         0 $self->{errormode} = $mode;
73             } elsif (ref($mode) eq 'ARRAY') {
74 0 0       0 unless (ref($mode->[0]) eq 'CODE') {
75 0         0 croak 'bad errmode: first item in list must be a code ref';
76 0         0 $mode = 'die';
77             }
78 0         0 $self->{errormode} = $mode;
79             } else {
80 9         30 $self->{errormode} = lc $mode;
81             }
82             }
83 9         12 $prev;
84             }
85              
86             sub errmsg {
87 0     0 0 0 my($self, @errmsgs) = @_;
88 0         0 my($prev) = $self->{errormsg};
89            
90 0 0       0 if (@_ > 0) {
91 0         0 $self->{errormsg} = join '', @errmsgs;
92             }
93            
94 0         0 $prev;
95             } # end sub errmsg
96            
97            
98             sub error {
99 0     0 0 0 my($self, @errmsg) = @_;
100             my(
101 0         0 $errmsg,
102             $func,
103             $mode,
104             @args,
105             );
106            
107 0 0       0 if (@_ >= 1) {
108             ## Put error message in the object.
109 0         0 $errmsg = join '', @errmsg;
110 0         0 $self->{"errormsg"} = $errmsg;
111            
112             ## Do the error action as described by error mode.
113 0         0 $mode = $self->{"errormode"};
114 0 0       0 if (ref($mode) eq 'CODE') {
    0          
    0          
    0          
115 0         0 &$mode($errmsg);
116 0         0 return;
117             } elsif (ref($mode) eq 'ARRAY') {
118 0         0 ($func, @args) = @$mode;
119 0         0 &$func(@args);
120 0         0 return;
121             } elsif ($mode eq "return") {
122 0         0 return;
123             } elsif ($mode eq "warn") {
124 0         0 carp $errmsg;
125             } else { # die
126 0         0 croak $errmsg;
127             }
128             } else {
129 0         0 return $self->{"errormsg"} ne '';
130             }
131             } # end sub error
132              
133              
134             sub copy {
135 9     9 0 11 my $self=shift;
136 9 50       37 $self->{copy}=shift if @_;
137 9         14 return $self->{copy};
138             }
139              
140             sub tail {
141 0     0 1 0 my $self=shift;
142 0 0       0 $self->{"tail"}=shift if @_;
143 0         0 return $self->{"tail"};
144             }
145              
146             sub reset_tail {
147 0     0 1 0 my $self=shift;
148 0 0       0 $self->{reset_tail}=shift if @_;
149 0         0 return $self->{reset_tail};
150             }
151              
152             sub nowait {
153 35     35 1 39 my $self=shift;
154 35 50       81 $self->{nowait}=shift if @_;
155 35         95 return $self->{nowait};
156             }
157              
158             sub method {
159 9     9 0 8 my $self=shift;
160 9 50       33 $self->{method}=shift if @_;
161 9         13 return $self->{method};
162             }
163              
164             sub input {
165 19     19 0 23 my $self=shift;
166 19 100       66 $self->{input}=shift if @_;
167 19         39 return $self->{input};
168             }
169              
170             sub maxinterval {
171 18     18 1 17 my $self=shift;
172 18 100       44 $self->{maxinterval}=shift if @_;
173 18         31 return $self->{maxinterval};
174             }
175              
176             sub resetafter {
177 9     9 1 10 my $self=shift;
178 9 50       43 $self->{resetafter}=shift if @_;
179 9         13 return $self->{resetafter};
180             }
181              
182             sub ignore_nonexistant {
183 0     0 1 0 my $self=shift;
184 0 0       0 $self->{ignore_nonexistant}=shift if @_;
185 0         0 return $self->{ignore_nonexistant};
186             }
187              
188             sub name_changes {
189 0     0 1 0 my $self=shift;
190 0 0       0 $self->{name_changes_callback}=shift if @_;
191 0         0 return $self->{name_changes_callback};
192             }
193              
194             sub TIEHANDLE {
195 0     0   0 my $ref=new(@_);
196             }
197              
198             sub READLINE {
199 0     0   0 $_[0]->read();
200             }
201              
202             sub PRINT {
203 0     0   0 $_[0]->error("PRINT makes no sense in File::Tail");
204             }
205              
206             sub PRINTF {
207 0     0   0 $_[0]->error("PRINTF makes no sense in File::Tail");
208             }
209              
210             sub READ {
211 0     0   0 $_[0]->error("READ not implemented in File::Tail -- use READLINE () instead");
212             }
213              
214             sub GETC {
215 0     0   0 $_[0]->error("GETC not (yet) implemented in File::Tail -- use READLINE () instead");
216             }
217              
218             sub DESTROY {
219 14     14   752 my($this) = $_[0];
220 14 50 33     191 close($this->{"handle"}) if (defined($this) && defined($this->{'handle'}));
221             # undef $_[0];
222 14         69 return;
223             }
224              
225             sub CLOSE {
226 7     7   1489 &DESTROY(@_);
227             }
228              
229             sub new {
230 9     9 1 1670 my ($pkg)=shift @_;
231 9   33     47 $pkg=ref($pkg) || $pkg;
232 9 50       24 unless ($pkg) {
233 0         0 $pkg="File::Tail";
234             }
235 9         11 my %params;
236 9 50       29 if ($#_ == 0) {
237 0         0 $params{"name"}=$_[0];
238             } else {
239 9 50       31 if (($#_ % 2) != 1) {
240 0         0 croak "Odd number of parameters for new";
241 0         0 return;
242             }
243 9         50 %params=@_;
244             }
245 9         16 my $object = {};
246 9         18 bless $object,$pkg;
247 9 50       26 unless (defined($params{'name'})) {
248 0         0 croak "No file name given. Pass filename as \"name\" parameter";
249 0         0 return;
250             }
251 9         27 $object->input($params{'name'});
252 9         34 $object->copy($params{'cname'});
253 9   50     58 $object->method($params{'method'} || "tail");
254 9         14 $object->{buffer}="";
255 9   50     36 $object->maxinterval($params{'maxinterval'} || 60);
256 9   100     44 $object->interval($params{'interval'} || 10);
257 9   50     38 $object->adjustafter($params{'adjustafter'} || 10);
258 9   100     36 $object->errmode($params{'errmode'} || "die");
259 9   33     40 $object->resetafter($params{'resetafter'} ||
260             ($object->maxinterval*$object->adjustafter));
261 9   50     37 $object->{"debug"}=($params{'debug'} || 0);
262 9   100     27 $object->{"tail"}=($params{'tail'} || 0);
263 9   50     34 $object->{"nowait"}=($params{'nowait'} || 0);
264 9   50     41 $object->{"maxbuf"}=($params{'maxbuf'} || 16384);
265 9   100     36 $object->{"name_changes_callback"}=($params{'name_changes'} || undef);
266 9 50       30 if (defined $params{'reset_tail'}) {
267 0         0 $object->{"reset_tail"} = $params{'reset_tail'};
268             } else {
269 9         19 $object->{"reset_tail"} = -1;
270             }
271 9   50     42 $object->{'ignore_nonexistant'}=($params{'ignore_nonexistant'} || 0);
272 9         15 $object->{"lastread"}=0;
273 9         13 $object->{"sleepcount"}=0;
274 9         16 $object->{"lastcheck"}=0;
275 9         12 $object->{"lastreset"}=0;
276 9         27 $object->{"nextcheck"}=time();
277 9 50       26 if ($object->{"method"} eq "tail") {
278 9         24 $object->reset_pointers;
279             }
280             # $object->{curpos}=0; # ADDED 25May01: undef warnings when
281             # $object->{endpos}=0; # starting up on a nonexistant file
282 9         120 return $object;
283             }
284              
285             # Sets position in file when first opened or after that when reset:
286             # Sets {endpos} and {curpos} for current {handle} based on {tail}.
287             # Sets {tail} to value of {reset_tail}; effect is that first call
288             # uses {tail} and subsequent calls use {reset_tail}.
289             sub position {
290 12     12 0 13 my $object=shift;
291 12         48 $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END);
292 12 100       47 unless ($object->{"tail"}) {
    100          
293 2         6 $object->{endpos}=$object->{curpos}=
294             sysseek($object->{handle},0,SEEK_END);
295             } elsif ($object->{"tail"}<0) {
296 4         15 $object->{endpos}=sysseek($object->{handle},0,SEEK_END);
297 4         13 $object->{curpos}=sysseek($object->{handle},0,SEEK_SET);
298             } else {
299 6         10 my $crs=0;
300 6         11 my $maxlen=sysseek($object->{handle},0,SEEK_END);
301 6         24 while ($crs<$object->{"tail"}+1) {
302 6         15 my $avlen=length($object->{"buffer"})/($crs+1);
303 6 50       16 $avlen=80 unless $avlen;
304 6         10 my $calclen=$avlen*$object->{"tail"};
305 6 50       28 $calclen=length($object->{tail})+1024 if $calclen<=length($object->{"tail"});
306 6 50       26 $calclen=$maxlen if $calclen>$maxlen;
307 6         56 $object->{curpos}=sysseek($object->{handle},-$calclen,SEEK_END);
308 6         47 sysread($object->{handle},$object->{"buffer"},
309             $calclen);
310 6 50       20 $object->{"buffer"} =~ s/\015\012/\n/g if $Is_Win32;
311 6         15 $object->{curpos}=sysseek($object->{handle},0,SEEK_CUR);
312 6         15 $crs=$object->{"buffer"}=~tr/\n//;
313 6 50       18 last if ($calclen>=$maxlen);
314             }
315 6         12 $object->{curpos}=sysseek($object->{handle},0,SEEK_CUR);
316 6         13 $object->{endpos}=sysseek($object->{handle},0,SEEK_END);
317 6 100       17 if ($crs>$object->{"tail"}) {
318 4         6 my $toskip=$crs-$object->{"tail"};
319 4         4 my $pos;
320 4         8 $pos=index($object->{"buffer"},"\n");
321 4         9 while (--$toskip) {
322 6         14 $pos=index($object->{"buffer"},"\n",$pos+1);
323             }
324 4         12 $object->{"buffer"}=substr($object->{"buffer"},$pos+1);
325             }
326             }
327 12         26 $object->{"tail"}=$object->{"reset_tail"};
328             }
329              
330             # Tries to open or reopen the file; failure is an error unless
331             # {ignore_nonexistant} is set.
332             #
333             # For a new file (ie, first time opened) just does some book-keeping
334             # and calls position for initial position setup. Otherwise does some
335             # checks whether file has been replaced, and if so changes to the new
336             # file. (Calls position for reset setup).
337             #
338             # Always updates {lastreset} to current time.
339             #
340             sub reset_pointers {
341 11     11 0 19 my $object=shift @_;
342 11         23 $object->{lastreset} = time();
343              
344 11         12 my $st;
345              
346 11         17 my $oldhandle=$object->{handle};
347 11         78 my $newhandle=FileHandle->new;
348              
349 11         421 my $newname;
350 11 100 100     53 if ($oldhandle && $$object{'name_changes_callback'}) {
351 1         5 $newname=$$object{'name_changes_callback'}();
352             } else {
353 10         26 $newname=$object->input;
354             }
355              
356 11 50       360 unless (open($newhandle,"<$newname")) {
357 0 0       0 if ($object->{'ignore_nonexistant'}) {
358             # If we have an oldhandle, leave endpos and curpos to what they
359             # were, since oldhandle will still be the "current" handle elsewhere,
360             # eg, checkpending. This also allows tailing a file which is removed
361             # but still being written to.
362 0 0       0 if (!$oldhandle) {
363 0         0 $object->{'endpos'}=0;
364 0         0 $object->{'curpos'}=0;
365             }
366 0         0 return;
367             }
368 0         0 $object->error("Error opening ".$object->input.": $!");
369 0 0       0 $object->{'endpos'}=0 unless defined($object->{'endpos'});
370 0 0       0 $object->{'curpos'}=0 unless defined($object->{'curpos'});
371 0         0 return;
372             }
373 11         35 binmode($newhandle);
374            
375 11 100       30 if (defined($oldhandle)) {
376             # If file has not been changed since last OK read do not do anything
377 2         13 $st=stat($newhandle);
378             # lastread uses fractional time, stat doesn't. This can cause false
379             # negatives.
380             # If the file was changed the same second as it was last read,
381             # we only reopen it if it's length has changed. The alternative is that
382             # sometimes, files would be reopened needlessly, and with reset_tail
383             # set to -1, we would see the whole file again.
384             # Of course, if the file was removed the same second as when it was
385             # last read, and replaced (within that second) with a file of equal
386             # length, we're out of luck. I don't see how to fix this.
387 2 50       377 if ($st->mtime<=int($object->{'lastread'})) {
388 2 50       60 if ($st->size==$object->{"curpos"}) {
389 0         0 $object->{lastread} = $st->mtime;
390 0         0 return;
391             } else {
392             # will continue further to reset
393             }
394             } else {
395             }
396 2         26 $object->{handle}=$newhandle;
397 2         14 $object->position;
398 2         52 $object->{lastread} = $st->mtime;
399 2         58 close($oldhandle);
400             } else { # This is the first time we are opening this file
401 9         35 $st=stat($newhandle);
402 9         1282 $object->{handle}=$newhandle;
403 9         25 $object->position;
404 9         205 $object->{lastread}=$st->mtime; # for better estimate on initial read
405             }
406            
407             }
408              
409              
410             sub checkpending {
411 35     35 0 48 my $object=shift @_;
412              
413 35         74 my $old_lastcheck = $object->{lastcheck};
414 35         84 $object->{"lastcheck"}=time;
415 35 50       105 unless ($object->{handle}) {
416 0         0 $object->reset_pointers;
417 0 0       0 unless ($object->{handle}) { # This try did not open the file either
418 0         0 return 0;
419             }
420             }
421            
422 35         198 $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END);
423 35 100 100     369 if ($object->{"endpos"}<$object->{curpos}) { # file was truncated
    100          
424 1         4 $object->position;
425             } elsif (($object->{curpos}==$object->{"endpos"})
426             && (time()-$object->{lastread})>$object->{'resetafter'}) {
427 2         11 $object->reset_pointers;
428 2         12 $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END);
429             }
430              
431 35 100       127 if ($object->{"endpos"}-$object->{curpos}) {
432 6         11 sysseek($object->{handle},$object->{curpos},SEEK_SET);
433 6         21 readin($object,$object->{"endpos"}-$object->{curpos});
434             }
435 35         155 return ($object->{"endpos"}-$object->{curpos});
436             }
437              
438             sub predict {
439 41     41 0 112 my $object=shift;
440 41         138 my $crs=$object->{"buffer"}=~tr/\n//; # Count newlines in buffer
441 41         457 my @call=caller(1);
442 41 100       176 return 0 if $crs;
443 35         171 my $ttw=$object->{"nextcheck"}-time();
444 35 50       103 return $ttw if $ttw>0;
445 35 50       112 if (my $len=$object->checkpending) {
446 0         0 readin($object,$len);
447 0         0 return 0;
448             }
449 35 100       151 if ($object->{"sleepcount"}>$object->adjustafter) {
450 4         6 $object->{"sleepcount"}=0;
451 4         14 $object->interval($object->interval*10);
452             }
453 35         52 $object->{"sleepcount"}++;
454 35         123 $object->{"nextcheck"}=time()+$object->interval;
455 35         73 return ($object->interval);
456             }
457              
458             sub bitprint {
459 0 0   0 0 0 return "undef" unless defined($_[0]);
460 0         0 return unpack("b*",$_[0]);
461             }
462              
463             sub select {
464 0 0   0 1 0 my $object=shift @_ if ref($_[0]);
465 0         0 my ($timeout,@fds)=splice(@_,3);
466 0 0       0 $object=$fds[0] unless defined($object);
467 0         0 my ($savein,$saveout,$saveerr)=@_;
468 0         0 my ($minpred,$mustreturn);
469 0 0       0 if (defined($timeout)) {
470 0         0 $minpred=$timeout;
471 0         0 $mustreturn=time()+$timeout;
472             } else {
473 0         0 $minpred=$fds[0]->predict;
474             }
475 0         0 foreach (@fds) {
476 0         0 my $val=$_->predict;
477 0 0       0 $minpred=$val if $minpred>$val;
478             }
479 0         0 my ($nfound,$timeleft);
480 0         0 my @retarr;
481 0 0 0     0 while (defined($timeout)?(!$nfound && (time()<$mustreturn)):!$nfound) {
482             # Restore bitmaps in case we called select before
483 0         0 splice(@_,0,3,$savein,$saveout,$saveerr);
484              
485              
486 0         0 ($nfound,$timeleft)=select($_[0],$_[1],$_[2],$minpred);
487              
488              
489 0 0       0 if (defined($timeout)) {
490 0         0 $minpred=$timeout;
491             } else {
492 0         0 $minpred=$fds[0]->predict;
493             }
494 0         0 undef @retarr;
495 0         0 foreach (@fds) {
496 0         0 my $val=$_->predict;
497 0 0       0 $nfound++ unless $val;
498 0 0       0 $minpred=$val if $minpred>$val;
499 0 0       0 push(@retarr,$_) unless $val;
500             }
501             }
502 0 0       0 if (wantarray) {
503 0         0 return ($nfound,$timeleft,@retarr);
504             } else {
505 0         0 return $nfound;
506             }
507             }
508              
509             sub readin {
510 7     7 0 6 my $crs;
511 7         10 my ($object,$len)=@_;
512 7 50       23 if (length($object->{"buffer"})) {
513             # this means the file was reset AND a tail -n was active
514 0         0 $crs=$object->{"buffer"}=~tr/\n//; # Count newlines in buffer
515 0 0       0 return $crs if $crs;
516             }
517 7 50       18 $len=$object->{"maxbuf"} if ($len>$object->{"maxbuf"});
518 7         8 my $nlen=$len;
519 7         19 while ($nlen>0) {
520 7         52 $len=sysread($object->{handle},$object->{"buffer"},
521             $nlen,length($object->{"buffer"}));
522 7 50       14 $object->{"buffer"} =~ s/\015\012/\n/g if $Is_Win32;
523              
524 7 50       14 last if $len==0; # Some busy filesystems return 0 sometimes,
525             # and never give anything more from then on if
526             # you don't give them time to rest. This return
527             # allows File::Tail to use the usual exponential
528             # backoff.
529 7         21 $nlen=$nlen-$len;
530             }
531 7         19 $object->{curpos}=sysseek($object->{handle},0,SEEK_CUR);
532            
533 7         14 $crs=$object->{"buffer"}=~tr/\n//;
534            
535 7 50       46 if ($crs) {
536 7         19 my $tmp=time;
537 7 50       22 $object->{lastread}=$tmp if $object->{lastread}>$tmp; #???
538 7         26 $object->interval(($tmp-($object->{lastread}))/$crs);
539 7         10 $object->{lastread}=$tmp;
540             }
541 7         15 return ($crs);
542             }
543              
544             sub read {
545 13     13 1 1009 my $object=shift @_;
546 13         14 my $len;
547 13         30 my $pending=$object->{"endpos"}-$object->{"curpos"};
548 13         35 my $crs=$object->{"buffer"}=~m/\n/;
549 13   66     49 while (!$pending && !$crs) {
550 6         9 $object->{"sleepcount"}=0;
551 6         18 while ($object->predict) {
552 35 50       85 if ($object->nowait) {
553 0 0       0 if (wantarray) {
554 0         0 return ();
555             } else {
556 0         0 return "";
557             }
558             }
559 35 50       64 sleep($object->interval) if ($object->interval>0);
560             }
561 6         25 $pending=$object->{"endpos"}-$object->{"curpos"};
562 6         53 $crs=$object->{"buffer"}=~m/\n/;
563             }
564            
565 13 100 66     82 if (!length($object->{"buffer"}) || index($object->{"buffer"},"\n")<0) {
566 1         3 readin($object,$pending);
567             }
568 13 100       33 unless (wantarray) {
569 6         28 my $str=substr($object->{"buffer"},0,
570             1+index($object->{"buffer"},"\n"));
571 6         19 $object->{"buffer"}=substr($object->{"buffer"},
572             1+index($object->{"buffer"},"\n"));
573 6         29 return $str;
574             } else {
575 7         8 my @str;
576 7         33 while (index($object->{"buffer"},"\n")>-1) {
577 25         46 push(@str,substr($object->{"buffer"},0,
578             1+index($object->{"buffer"},"\n")));
579 25         61 $object->{"buffer"}=substr($object->{"buffer"},
580             1+index($object->{"buffer"},"\n"));
581              
582             }
583 7         32 return @str;
584             }
585             }
586              
587             1;
588              
589             __END__