File Coverage

blib/lib/File/Tail.pm
Criterion Covered Total %
statement 209 318 65.7
branch 69 164 42.0
condition 28 46 60.8
subroutine 24 40 60.0
pod 14 26 53.8
total 344 594 57.9


line stmt bran cond sub pod time code
1             package File::Tail;
2              
3 3     3   4355 use strict;
  3         3  
  3         80  
4 3     3   8 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $Is_Win32);
  3         3  
  3         262  
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.1';
15              
16              
17             # Preloaded methods go here.
18              
19 3     3   1165 use FileHandle;
  3         20243  
  3         10  
20             #use IO::Seekable; # does not define SEEK_SET in 5005.02
21 3     3   1706 use File::stat;
  3         17396  
  3         16  
22 3     3   123 use Carp;
  3         3  
  3         98  
23 3     3   1336 use Time::HiRes qw ( time sleep ); #import hires microsecond timers
  3         3276  
  3         10  
24              
25             sub SEEK_SET () {0;}
26             sub SEEK_CUR () {1;}
27             sub SEEK_END () {2;}
28              
29              
30             sub interval {
31 112     112 1 106 my $object=shift @_;
32 112 100       154 if (@_) {
33 18         22 $object->{interval}=shift;
34 18 100       79 $object->{interval}=$object->{maxinterval} if
35             $object->{interval}>$object->{maxinterval};
36             }
37 112         63902341 $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 41     41 1 60 my $self=shift;
54 41 100       107 $self->{adjustafter}=shift if @_;
55 41         107 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 10 my($self, $mode) = @_;
66 9         11 my($prev) = $self->{errormode};
67            
68 9 50       15 if (@_ >= 2) {
69             ## Set the error mode.
70 9 50       15 defined $mode or $mode = '';
71 9 50       22 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         22 $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       25 $self->{copy}=shift if @_;
137 9         10 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 23     23 1 29 my $self=shift;
154 23 50       53 $self->{nowait}=shift if @_;
155 23         69 return $self->{nowait};
156             }
157              
158             sub method {
159 9     9 0 11 my $self=shift;
160 9 50       19 $self->{method}=shift if @_;
161 9         7 return $self->{method};
162             }
163              
164             sub input {
165 19     19 0 19 my $self=shift;
166 19 100       55 $self->{input}=shift if @_;
167 19         26 return $self->{input};
168             }
169              
170             sub maxinterval {
171 18     18 1 15 my $self=shift;
172 18 100       29 $self->{maxinterval}=shift if @_;
173 18         23 return $self->{maxinterval};
174             }
175              
176             sub resetafter {
177 9     9 1 7 my $self=shift;
178 9 50       34 $self->{resetafter}=shift if @_;
179 9         8 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   9051 my($this) = $_[0];
220 14 50 33     182 close($this->{"handle"}) if (defined($this) && defined($this->{'handle'}));
221             # undef $_[0];
222 14         48 return;
223             }
224              
225             sub CLOSE {
226 7     7   223 &DESTROY(@_);
227             }
228              
229             sub new {
230 9     9 1 843 my ($pkg)=shift @_;
231 9   33     39 $pkg=ref($pkg) || $pkg;
232 9 50       20 unless ($pkg) {
233 0         0 $pkg="File::Tail";
234             }
235 9         8 my %params;
236 9 50       21 if ($#_ == 0) {
237 0         0 $params{"name"}=$_[0];
238             } else {
239 9 50       25 if (($#_ % 2) != 1) {
240 0         0 croak "Odd number of parameters for new";
241 0         0 return;
242             }
243 9         45 %params=@_;
244             }
245 9         15 my $object = {};
246 9         13 bless $object,$pkg;
247 9 50       24 unless (defined($params{'name'})) {
248 0         0 croak "No file name given. Pass filename as \"name\" parameter";
249 0         0 return;
250             }
251 9         23 $object->input($params{'name'});
252 9         26 $object->copy($params{'cname'});
253 9   50     39 $object->method($params{'method'} || "tail");
254 9         13 $object->{buffer}="";
255 9   50     24 $object->maxinterval($params{'maxinterval'} || 60);
256 9   100     26 $object->interval($params{'interval'} || 10);
257 9   50     21 $object->adjustafter($params{'adjustafter'} || 10);
258 9   100     27 $object->errmode($params{'errmode'} || "die");
259 9   33     29 $object->resetafter($params{'resetafter'} ||
260             ($object->maxinterval*$object->adjustafter));
261 9   50     27 $object->{"debug"}=($params{'debug'} || 0);
262 9   100     20 $object->{"tail"}=($params{'tail'} || 0);
263 9   50     25 $object->{"nowait"}=($params{'nowait'} || 0);
264 9   50     33 $object->{"maxbuf"}=($params{'maxbuf'} || 16384);
265 9   100     24 $object->{"name_changes_callback"}=($params{'name_changes'} || undef);
266 9 50       18 if (defined $params{'reset_tail'}) {
267 0         0 $object->{"reset_tail"} = $params{'reset_tail'};
268             } else {
269 9         10 $object->{"reset_tail"} = -1;
270             }
271 9   50     37 $object->{'ignore_nonexistant'}=($params{'ignore_nonexistant'} || 0);
272 9         12 $object->{"lastread"}=0;
273 9         18 $object->{"sleepcount"}=0;
274 9         12 $object->{"lastcheck"}=0;
275 9         8 $object->{"lastreset"}=0;
276 9         24 $object->{"nextcheck"}=time();
277 9 50       20 if ($object->{"method"} eq "tail") {
278 9         20 $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         94 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         38 $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END);
292 12 100       37 unless ($object->{"tail"}) {
    100          
293 2         11 $object->{endpos}=$object->{curpos}=
294             sysseek($object->{handle},0,SEEK_END);
295             } elsif ($object->{"tail"}<0) {
296 4         14 $object->{endpos}=sysseek($object->{handle},0,SEEK_END);
297 4         12 $object->{curpos}=sysseek($object->{handle},0,SEEK_SET);
298             } else {
299 6         6 my $crs=0;
300 6         9 my $maxlen=sysseek($object->{handle},0,SEEK_END);
301 6         13 while ($crs<$object->{"tail"}+1) {
302 6         12 my $avlen=length($object->{"buffer"})/($crs+1);
303 6 50       12 $avlen=80 unless $avlen;
304 6         6 my $calclen=$avlen*$object->{"tail"};
305 6 50       14 $calclen+=1024 if $calclen<=length($object->{"buffer"});
306 6 50       19 $calclen=$maxlen if $calclen>$maxlen;
307 6         37 $object->{curpos}=sysseek($object->{handle},-$calclen,SEEK_END);
308 6         33 sysread($object->{handle},$object->{"buffer"},
309             $calclen);
310 6 50       11 $object->{"buffer"} =~ s/\015\012/\n/g if $Is_Win32;
311 6         9 $object->{curpos}=sysseek($object->{handle},0,SEEK_CUR);
312 6         11 $crs=$object->{"buffer"}=~tr/\n//;
313 6 50       13 last if ($calclen>=$maxlen);
314             }
315 6         9 $object->{curpos}=sysseek($object->{handle},0,SEEK_CUR);
316 6         9 $object->{endpos}=sysseek($object->{handle},0,SEEK_END);
317 6 100       13 if ($crs>$object->{"tail"}) {
318 4         5 my $toskip=$crs-$object->{"tail"};
319 4         3 my $pos;
320 4         5 $pos=index($object->{"buffer"},"\n");
321 4         6 while (--$toskip) {
322 6         9 $pos=index($object->{"buffer"},"\n",$pos+1);
323             }
324 4         8 $object->{"buffer"}=substr($object->{"buffer"},$pos+1);
325             }
326             }
327 12         18 $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 11 my $object=shift @_;
342 11         26 $object->{lastreset} = time();
343              
344 11         11 my $st;
345              
346 11         12 my $oldhandle=$object->{handle};
347 11         63 my $newhandle=FileHandle->new;
348              
349 11         302 my $newname;
350 11 100 100     41 if ($oldhandle && $$object{'name_changes_callback'}) {
351 1         4 $newname=$$object{'name_changes_callback'}();
352             } else {
353 10         17 $newname=$object->input;
354             }
355              
356 11 50       306 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         27 binmode($newhandle);
374            
375 11 100       20 if (defined($oldhandle)) {
376             # If file has not been changed since last OK read do not do anything
377 2         10 $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 100       368 if ($st->mtime<=int($object->{'lastread'})) {
388 1 50       19 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         14 $object->{handle}=$newhandle;
397 2         4 $object->position;
398 2         29 $object->{lastread} = $st->mtime;
399 2         77 close($oldhandle);
400             } else { # This is the first time we are opening this file
401 9         26 $st=stat($newhandle);
402 9         956 $object->{handle}=$newhandle;
403 9         18 $object->position;
404 9         151 $object->{lastread}=$st->mtime; # for better estimate on initial read
405             }
406            
407             }
408              
409              
410             sub checkpending {
411 23     23 0 50 my $object=shift @_;
412              
413 23         50 my $old_lastcheck = $object->{lastcheck};
414 23         60 $object->{"lastcheck"}=time;
415 23 50       88 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 23         178 $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END);
423 23 100 100     269 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         9 $object->reset_pointers;
428 2         16 $object->{"endpos"}=sysseek($object->{handle},0,SEEK_END);
429             }
430              
431 23 100       77 if ($object->{"endpos"}-$object->{curpos}) {
432 6         15 sysseek($object->{handle},$object->{curpos},SEEK_SET);
433 6         24 readin($object,$object->{"endpos"}-$object->{curpos});
434             }
435 23         110 return ($object->{"endpos"}-$object->{curpos});
436             }
437              
438             sub predict {
439 29     29 0 130 my $object=shift;
440 29         138 my $crs=$object->{"buffer"}=~tr/\n//; # Count newlines in buffer
441 29         474 my @call=caller(1);
442 29 100       138 return 0 if $crs;
443 23         123 my $ttw=$object->{"nextcheck"}-time();
444 23 50       64 return $ttw if $ttw>0;
445 23 50       80 if (my $len=$object->checkpending) {
446 0         0 readin($object,$len);
447 0         0 return 0;
448             }
449 23 100       74 if ($object->{"sleepcount"}>$object->adjustafter) {
450 2         6 $object->{"sleepcount"}=0;
451 2         8 $object->interval($object->interval*10);
452             }
453 23         33 $object->{"sleepcount"}++;
454 23         81 $object->{"nextcheck"}=time()+$object->interval;
455 23         44 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         11 my ($object,$len)=@_;
512 7 50       21 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       20 $len=$object->{"maxbuf"} if ($len>$object->{"maxbuf"});
518 7         10 my $nlen=$len;
519 7         18 while ($nlen>0) {
520 7         58 $len=sysread($object->{handle},$object->{"buffer"},
521             $nlen,length($object->{"buffer"}));
522 7 50       15 $object->{"buffer"} =~ s/\015\012/\n/g if $Is_Win32;
523              
524 7 50       17 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         12 $nlen=$nlen-$len;
530             }
531 7         20 $object->{curpos}=sysseek($object->{handle},0,SEEK_CUR);
532            
533 7         14 $crs=$object->{"buffer"}=~tr/\n//;
534            
535 7 50       60 if ($crs) {
536 7         14 my $tmp=time;
537 7 50       22 $object->{lastread}=$tmp if $object->{lastread}>$tmp; #???
538 7         28 $object->interval(($tmp-($object->{lastread}))/$crs);
539 7         10 $object->{lastread}=$tmp;
540             }
541 7         11 return ($crs);
542             }
543              
544             sub read {
545 13     13 1 1667 my $object=shift @_;
546 13         17 my $len;
547 13         27 my $pending=$object->{"endpos"}-$object->{"curpos"};
548 13         25 my $crs=$object->{"buffer"}=~m/\n/;
549 13   66     44 while (!$pending && !$crs) {
550 6         12 $object->{"sleepcount"}=0;
551 6         15 while ($object->predict) {
552 23 50       66 if ($object->nowait) {
553 0 0       0 if (wantarray) {
554 0         0 return ();
555             } else {
556 0         0 return "";
557             }
558             }
559 23 50       43 sleep($object->interval) if ($object->interval>0);
560             }
561 6         32 $pending=$object->{"endpos"}-$object->{"curpos"};
562 6         98 $crs=$object->{"buffer"}=~m/\n/;
563             }
564            
565 13 100 66     83 if (!length($object->{"buffer"}) || index($object->{"buffer"},"\n")<0) {
566 1         2 readin($object,$pending);
567             }
568 13 100       35 unless (wantarray) {
569 6         29 my $str=substr($object->{"buffer"},0,
570             1+index($object->{"buffer"},"\n"));
571 6         20 $object->{"buffer"}=substr($object->{"buffer"},
572             1+index($object->{"buffer"},"\n"));
573 6         25 return $str;
574             } else {
575 7         7 my @str;
576 7         46 while (index($object->{"buffer"},"\n")>-1) {
577 25         41 push(@str,substr($object->{"buffer"},0,
578             1+index($object->{"buffer"},"\n")));
579 25         51 $object->{"buffer"}=substr($object->{"buffer"},
580             1+index($object->{"buffer"},"\n"));
581              
582             }
583 7         26 return @str;
584             }
585             }
586              
587             1;
588              
589             __END__