File Coverage

blib/lib/File/Tail.pm
Criterion Covered Total %
statement 212 321 66.0
branch 69 166 41.5
condition 28 49 57.1
subroutine 24 40 60.0
pod 14 26 53.8
total 347 602 57.6


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