File Coverage

MultiTail.pm
Criterion Covered Total %
statement 333 522 63.7
branch 90 236 38.1
condition 18 78 23.0
subroutine 32 42 76.1
pod 6 29 20.6
total 479 907 52.8


line stmt bran cond sub pod time code
1             package MultiTail;
2             #
3             # Copyright (c) 1997 Stephen G. Miano. All rights reserved.
4             # This program is free software; you can redistribute it and/or
5             # modify it under the same terms as Perl itself.
6             #
7             # Following are other pachages needed for MultiTail
8             #
9 1     1   4416 use Carp;
  1         35  
  1         126  
10 1     1   6 use strict;
  1         2  
  1         42  
11 1     1   1918 use File::stat qw(:FIELDS);
  1         17906  
  1         8  
12 1     1   257 use File::Basename;
  1         2  
  1         94  
13 1     1   925 use FileHandle;
  1         13435  
  1         5  
14 1     1   1339 use DirHandle;
  1         558  
  1         32  
15 1     1   6 use vars qw( $AUTOLOAD $LastScan $True $False $DEBUG $VERSION $GMT $TC $FileAttributeChanged );
  1         1  
  1         1178  
16             #
17             # Define sub
18             #
19             sub new;
20             sub update_attribute;
21             sub getparams;
22             sub GetParams;
23             sub CheckAttributes;
24             sub CreateFileDataStructure;
25             sub CreateListOfFiles;
26             sub OpenFileToTail;
27             sub read;
28             sub print;
29             sub printpat;
30             sub printexceptpat;
31             sub printstat;
32             sub Patterns;
33             sub Prefix;
34             sub RemoveDups;
35             sub CheckIfArrayOrFile;
36             sub ExceptPatterns;
37             sub OpenUpdateFiles;
38             sub UpdateStat;
39             sub Time;
40             sub version;
41             sub debug;
42             sub close_all_files;
43             sub FileState;
44             sub SetFileState;
45             sub PosFileMark;
46             sub printfilestates;
47             sub match_closure;
48             #
49             $True=1;
50             $False=0;
51             $GMT=$False;
52             $TC=$False;
53             $LastScan=time;
54             $DEBUG=$False;
55             $VERSION=0.5;
56             $FileAttributeChanged=$False;
57             my $pattern_sub;
58             my $exceptpattern_sub;
59             my %Attribute=();
60             my @File_Data_Structure;
61             my @StatArray = qw { dev ino mode nlink uid gid rdev size
62             atime mtime ctime blksize blocks }; # Stat ids
63             #
64             # Code added for MSWin32
65             #
66             my $MSWin32 = ( $^O eq "MSWin32" ) ? $True : $False;
67             #
68             ########################################################################
69             #
70             # Creating a new PTAIL object
71             #
72             ########################################################################
73             #
74             sub new {
75 1     1 0 11036 my $class=shift;
76 1         206 my(%argvs)=@_;
77 1         23 my $args = getparams(\%argvs);
78 1         6 my $self;
79             my $rds;
80             #
81             # set default vars
82             #
83 1         3 my %Default=();
84 1         2 $Default{'Files'}=$False;
85 1         3 $Default{'MaxAge'}=10;
86 1         3 $Default{'NumLines'}=10;
87 1         2 $Default{'OutputPrefix'}=$False;
88 1         2 $Default{'Pattern'}=$False;
89 1         2 $Default{'ExceptPattern'}=$False;
90 1         1 $Default{'ScanForFiles'}=$False;
91 1         3 $Default{'RemoveDuplicate'}=$False;
92 1         1 $Default{'Function'}=$False;
93 1         2 $Default{'Debug'}=$False;
94             #
95 1         25 foreach my $keys ( keys %$args ) {
96 10 100       31 $args->{$keys}=$Default{$keys} unless $args->{$keys};
97             }
98             #
99 1         5 $DEBUG=$args->{'Debug'};
100 1 50       9 if ( $args->{'Files'} ) {
101 1         9 $rds=CreateFileDataStructure( CreateListOfFiles($args->{'Files'})),
102             }
103             # convert hash array to constant var
104             #
105             %Attribute = (
106 1         25 Files => $args->{'Files'},
107             MaxAge => $args->{'MaxAge'},
108             NumLines => $args->{'NumLines'},
109             OutputPrefix => $args->{'OutputPrefix'},
110             Pattern => $args->{'Pattern'},
111             ExceptPattern => $args->{'ExceptPattern'},
112             ScanForFiles => $args->{'ScanForFiles'},
113             RemoveDuplicate => $args->{'RemoveDuplicate'},
114             Function=> $args->{'Function'},
115             Debug => $args->{'Debug'},
116             FileArray => $rds,
117             );
118 1         2 $self=\%Attribute;
119 1         3 bless $self, $class;
120 1         9 CheckAttributes($self);
121 1         12 return $self;
122              
123             }
124             #
125             ########################################################################
126             #
127             # Update Object Attribute
128             #
129             ########################################################################
130             #
131             sub update_attribute {
132 0     0 1 0 my ($rFD)=shift;
133 0         0 my(%argvs)=@_;
134 0         0 my $args = getparams(\%argvs);
135             #
136 0         0 foreach my $keys ( keys %$args ) {
137 0 0       0 if ( $keys eq "Files" ) {
138 0         0 $FileAttributeChanged=$True;
139             }
140 0 0       0 $rFD->{$keys}=$args->{$keys} if $args->{$keys};
141             }
142             }
143             #
144             ########################################################################
145             #
146             # Front end to Getparams function for MultiTail
147             #
148             ########################################################################
149             #
150             sub getparams {
151 1     1 0 6 my($rargvs)=@_;
152 1         3 my ($MaxAge,$NumLines, $OutputPrefix,$Pattern,
153             $ExceptPattern,$Debug,$ScanForFiles,$RemoveDuplicate,
154             $Function,$Files);
155 1         36 my $args =
156             GetParams $rargvs,
157             { MaxAge => \$MaxAge,
158             NumLines => \$NumLines,
159             OutputPrefix => \$OutputPrefix,
160             Pattern => \$Pattern,
161             ExceptPattern => \$ExceptPattern,
162             Debug => \$Debug,
163             ScanForFiles => \$ScanForFiles,
164             RemoveDuplicate => \$RemoveDuplicate,
165             Function => \$Function,
166             Files => \$Files,
167             },
168             [qw(MaxAge NumLines OutputPrefix Pattern ExceptPattern Debug
169             ScanForFiles RemoveDuplicate Function Files)];
170             #
171             }
172             #
173             ########################################################################
174             #
175             # Check each Attributes for Min and Max Values
176             #
177             # Exit Codes
178             # 1001 - MaxAge is less the zero
179             # 1002 - NumLines is less the zero
180             # 1003 - OutputPrefix must one ( )
181             # 1004 - Pattern must is not a file or ARRAY
182             # 1005 - ExceptPattern is not a file or ARRAY
183             # 1006 - Debug is not 0 or 1
184             # 1007 - ScanForFiles is less the zero
185             # 1008 - RemoveDuplicate is not 0 or 1
186             # 1009 - Function is not ref to fuction
187             # 1010 - File attribute not set
188             #
189             ########################################################################
190             #
191             sub CheckAttributes {
192 1     1 0 2 my($args)=shift;
193 1         2 local($_);
194 1         9 for ( keys %$args ) {
195 11 100       46 /MaxAge/ and do { # /MaxAge/ must be >= zero
196 1 50       5 if ( $args->{MaxAge} < 0 ) {
197 0         0 print STDOUT "ERROR: MultiTail object MaxAge must be >= zero\n";
198 0         0 exit 1001;
199             }
200 1         1 next;
201             };
202 10 100       35 /NumLines/ and do { # /NumLines/ must be >= zero
203 1 50       4 if ( $args->{NumLines} < 0 ) {
204 0         0 print STDOUT "ERROR: MultiTail object NumLines must be >= zero\n";
205 0         0 exit 1002;
206             }
207 1         3 next;
208             };
209 9 100       28 /OutputPrefix/ and do { # /OutputPrefix/ must be a file or ARRAY
210 1 50       10 unless ( $args->{OutputPrefix} =~ /^(p|f|t|tg|tc|pt|ptg|ptc|ft|ftg|ftc|tp|tpg|tpc|tf|tfg|tfc)$/){
211 0 0       0 next if ! $args->{OutputPrefix};
212 0         0 print STDOUT "ERROR: MultiTail object OutputPrefix must ARRAY of file\n";
213 0         0 exit 1003;
214             }
215 1         2 next;
216             };
217 8 100       23 /Pattern/ and do { # /Pattern/ must be a file or ARRAY
218 2 50       8 next if ! $args->{Pattern};
219 1     1   7 no strict 'refs';
  1         2  
  1         1331  
220 2 50 33     9 if ( ref($args->{Pattern}) ne "ARRAY" and ! -f $args->{Pattern} ) {
221 0         0 print STDOUT "ERROR: MultiTail object Pattern must ARRAY or file\n";
222 0         0 exit 1004;
223             }
224 2         7 next;
225             };
226 6 50       13 /ExceptPattern/ and do { # /ExceptPattern/ must be a file or ARRAY
227 0 0       0 next if ! $args->{ExceptPattern};
228 0 0 0     0 if ( ref($args->{ExceptPattern}) ne "ARRAY" and ! -f $args->{ExceptPattern} ) {
229 0         0 print STDOUT "ERROR: MultiTail object ExceptPattern must ARRAY or file\n";
230 0         0 exit 1005;
231             }
232 0         0 next;
233             };
234 6 100       15 /Debug/ and do { # /Debug/ must be 0 or 1
235 1 50       8 unless ( $args->{Debug} =~ /^[0|1]$/ ) {
236 0         0 print STDOUT "ERROR: MultiTail object must 0 or 1\n";
237 0         0 exit 1006;
238             }
239 1         11 next;
240             };
241 5 100       12 /ScanForFiles/ and do { # /ScanForFiles/ must be 0 or 1
242 1 50       5 if ( $args->{ScanForFiles} < 0 ) {
243 0         0 print STDOUT "ERROR: MultiTail object ScanForFiles must be >= zero\n";
244 0         0 exit 1007;
245             }
246 1         2 next;
247             };
248 4 100       11 /RemoveDuplicate/ and do { # /RemoveDuplicate/ must be 0 or 1
249 1 50       7 unless ( $args->{RemoveDuplicate} =~ /^[0|1]$/ ) {
250 0         0 print STDOUT "ERROR: MultiTail object RemoveDuplicate must 0 or 1\n";
251 0         0 exit 1008;
252             }
253 1         3 next;
254             };
255 3 100       13 /Function/ and do { # /Function/ must be a function
256 1 50       6 if ( ref($args->{Function}) ne "CODE" ) {
257 0 0       0 next if ! $args->{Function};
258 0         0 print STDOUT "ERROR: MultiTail object Function must be a function\n";
259 0         0 exit 1009;
260             }
261 1         2 next;
262             };
263 2 100       10 /Files/ and do { # All attributes have default except for Files
264 1 50       3 next if ! $args->{Pattern};
265 1 50       21 unless ( $args->{Files} ) {
266 0         0 print STDOUT "ERROR: MultiTail object must have attribute Files\n";
267 0         0 exit 1010;
268             }
269 1         2 next;
270             };
271             }
272             }
273             #
274             ########################################################################
275             #
276             # Get params past to object MultiTail
277             #
278             ########################################################################
279             #
280             sub GetParams {
281 1 50   1 0 451 my $argvref = shift or croak "Missing required argument.\n";
282 1 50       156 my $params = shift or croak "Missing required parameters hash.\n";
283 1 50       12 my $arglist = shift or croak "Missing required arglist array.\n";
284 1         3 my %args;
285 1         3 my ($param, $var);
286 1 50       14 if (ref($argvref) eq 'HASH') {
287 1         3 my $href = $argvref;
288 1         13 %args = %$href; # initialize result with input hash
289 1         10 foreach $param (keys %$href) { # for each named argument...
290             # Is this a known parameter?
291 6 50       35 if (exists($params->{$param})) {
292 6         12 $var = $params->{$param};
293 6   33     52 while ($var ne '' && ref($var) eq '') { # indirect refs?
294 0         0 $var = $params->{$param = $var};
295             }
296 6 50       24 if ($var ne '') {
297 6         24 $$var = $href->{$param}; # assign the param's variable
298 6         310 $args{$param} = $$var; # make sure canonical param gets defined
299 6         19 next; # go to the next parameter
300             }
301             }
302 0 0       0 if (!exists($params->{$param})) {
303 0         0 croak "Unknown parameter: \"$param\"\n";
304             }
305             }
306             } else { # use args in the order given for variables
307 0         0 my $i;
308 0         0 for ($i = 0; $i <= $#$arglist; $i++) {
309 0         0 $param = $arglist->[$i]; # get the next argument
310 0         0 $var = $params->{$param}; # get it's variable
311 0 0       0 next unless defined($var);
312 0   0     0 while ($var ne '' && ref($var) eq '') {
313 0         0 $var = $params->{$param = $var};
314             }
315 0 0       0 if ($var ne '') {
    0          
316 0 0       0 $$var = $i <= $#$argvref ? $argvref->[$i] : '';
317 0         0 $args{$param} = $$var; # assign to the hash
318             } elsif (!exists($params->{$param})) {
319 0         0 croak "Unknown parameter: \"$param\" for argument $i.\n";
320             }
321             }
322             }
323             # Now, make sure all variables get initialized
324 1         8 foreach $param (keys %$params) {
325 10         18 $var = $params->{$param};
326 10   33     79 while ($var ne '' && ref($var) eq '') {
327 0         0 $var = $params->{$param = $var};
328             }
329 10 100 66     329 if ($var ne '' && !exists($args{$param})) {
330 4         13 $$var = $args{$param} = undef;
331             }
332             }
333 1         6 \%args; # return the HASH ref
334             }
335             #
336             ########################################################################
337             #
338             # Will open all file and create a file data Structure
339             #
340             ########################################################################
341             #
342             sub CreateFileDataStructure {
343 1     1 0 3 my($File_Array)=@_;
344 1         2 my $BFILE;
345             my $fh;
346 0         0 my $Exist;
347 0         0 my $rhash;
348 1         2 my $Pos=0;
349 1         2 my $online=$False;
350 1         3 my %FileHash=();
351             #
352 1 50       5 if ( %Attribute ) {
353 0         0 $online=$True;
354 0         0 foreach my $FH ( @{$Attribute{'FileArray'}} ) {
  0         0  
355 0         0 $FileHash{$FH->{'name'}} = 1;
356             }
357             }
358 1         6 foreach my $FILE ( @$File_Array ) {
359             #
360             # if not run by fuction new check if file is already being monitored
361 1 50       5 if ( %Attribute ) {
362 0 0       0 next if $FileHash{$FILE};
363             }
364             #
365             # stat file
366             #
367 1 50       5 $Exist = ( stat($FILE) ? $True : $False);
368 1         477 $BFILE=basename($FILE);
369             {
370 1     1   7 no strict 'refs';
  1         2  
  1         1906  
  1         2  
371 1         167 %$FILE = (
372             'name' => $FILE,
373             'basename' => $BFILE,
374             'fh' => $fh,
375             'stat' => {
376             'dev' => $st_dev,
377             'ino' => $st_ino,
378             'mode' => $st_mode,
379             'nlink' => $st_nlink,
380             'uid' => $st_uid,
381             'gid' => $st_gid,
382             'rdev' => $st_rdev,
383             'size' => $st_size,
384             'atime' => $st_atime,
385             'mtime' => $st_mtime,
386             'ctime' => $st_ctime,
387             'blksize' => $st_blksize,
388             'blocks' => $st_blocks
389             },
390             'open' => $False,
391             'exist' => $Exist,
392             'read' => $False,
393             'online' => $online,
394             'pos' => $Pos,
395             'FileState' => 0,
396             'LastState' => 0,
397             'LastMtime' => $st_mtime,
398             'OpenTime' => 0,
399             'LineArray' => [],
400             'PatternLineArray' => [],
401             'ExceptPatternLineArray' => []
402             );
403 1         4 $rhash=\%$FILE;
404 1         8 $$rhash{'LastState'} = FileState($rhash);
405 1         3 $$rhash{'FileState'} = $$rhash{'LastState'};
406 1         7 push(@File_Data_Structure,\%$FILE);
407             }
408             }
409 1         3 return \@File_Data_Structure;
410             }
411             #
412             ########################################################################
413             #
414             # Create a array of text file from dirs and filename
415             # Checks files for dups (links) and absolute path
416             #
417             ########################################################################
418             #
419             sub CreateListOfFiles {
420 1     1 0 3 my($rArrayOfFileNames)=@_;
421             #
422             # Check if dir and expand
423             #
424 1         3 my @RegFileArray=();
425 1         2 my @FileArray=();
426 1         3 my @ReturnFileArray=();
427 1         1 my @result=();
428 1         2 my $file;
429             my %path_file;
430             #
431             # Expand all reg file names
432             #
433 1         8 foreach my $FILE ( @$rArrayOfFileNames ) {
434 1         110 @result = glob($FILE);
435 1         5 push(@RegFileArray,@result);
436             }
437             #
438             # check for dir and expand
439             #
440 1         6 foreach my $FILE ( @RegFileArray ) {
441 1 50       509 if ( -d $FILE ) {
442 0 0       0 print STDOUT "Dir $FILE is being expanded\n" if $DEBUG;
443 0         0 my $d = new DirHandle "$FILE";
444 0 0       0 if(defined $d ) {
445 0         0 while(defined($_=$d->read)) {
446 0         0 $file="${FILE}/$_";
447 0 0       0 if ( -T $file ) {
448 0         0 push(@FileArray,$file);
449             }
450             }
451             }
452             }
453             else {
454 1         7 push(@FileArray,$FILE);
455             }
456             }
457             #
458             # Checks files for dups (links) and absolute path
459             #
460 1         4 foreach my $FILE ( @FileArray ) {
461             #
462             # check for absolute path
463             #
464 1 50 33     30 unless ( $FILE =~ m#^/# || $MSWin32 ) {
465 0 0       0 print STDOUT "File $FILE is not absolute path ... will not be used\n" if $DEBUG;
466 0         0 next;
467             }
468 1 50 33     33 unless ( ( $FILE =~ m#^\w:# || $FILE =~ m#^//# ) || ! $MSWin32 ) {
      33        
469 0 0       0 print STDOUT "File $FILE is not NT absolute path ... will not be used\n" if $DEBUG;
470 0         0 next;
471             }
472             #
473             # stat file
474             #
475 1 50       19 next unless stat($FILE);
476             #
477             # Check if any two file names point to the same file
478             # Checking for links (Note: There are not links in NT)
479             #
480 1         435 my $key = "$st_dev $st_ino";
481 1 50 33     6 if ( exists $path_file{$key} && ! $MSWin32 ) {
482 0 0       0 print STDOUT "Warning: $FILE is linked to $path_file{$key}\n" if $DEBUG;
483 0 0       0 print STDOUT "File $path_file{$key} ... will not be used\n" if $DEBUG;
484 0         0 next;
485             }
486 1         2 $path_file{$key}=$FILE;
487             #
488             # Check if text file
489 1 50       112 if ( -T $FILE ) {
490 1         6 push(@ReturnFileArray,$FILE);
491             }
492             else {
493 0 0       0 print STDOUT "$FILE is not a text file , will not be used\n" if $DEBUG;
494             }
495             }
496             #
497 1         12 return \@ReturnFileArray;
498             }
499              
500             ########################################################################
501             #
502             # Read all new data from file
503             #
504             ########################################################################
505             #
506             # Read all new data from files
507             #
508             sub read {
509             #
510 1     1 1 14 my ($rFD)=shift;
511 1         3 my @TotalArray=(); # Used with attribute Fuction
512 1         3 my $PresentTime=time;
513             #
514             # Check if file dir should be rescanned
515             # $LastScan is in sec
516             # $rFD->{'ScanForFiles'} is in minutes
517             #
518 1 50 33     22 if (( $rFD->{'ScanForFiles'} and
      33        
519             ($LastScan + ($rFD->{'ScanForFiles'}*60)) < $PresentTime) or
520             $FileAttributeChanged ) {
521 0 0       0 print STDOUT "Scanning for new files\n" if $DEBUG;
522 0         0 $rFD->{'FileArray'} =
523             CreateFileDataStructure( CreateListOfFiles($rFD->{'Files'}));
524             #
525 0         0 $LastScan = $PresentTime;
526 0         0 $FileAttributeChanged=$False;
527             }
528             #
529             # This is for DEBUG
530 1 50       4 if ( $DEBUG ) {
531 0         0 print STDOUT "DEBUG list of file to be checked\n";
532 0         0 foreach my $FH ( @{$rFD->{'FileArray'}} ) {
  0         0  
533 0         0 print $FH->{'name'} . "\n";
534             }
535             }
536             #
537             # Check stat of files
538             #
539 1         8 OpenUpdateFiles($rFD);
540             #
541             #
542 1         2 foreach my $FH ( @{$rFD->{'FileArray'}} ) {
  1         4  
543             # reset array to remove last read data
544 1         1 @{$FH->{'LineArray'}}=();
  1         3  
545             #
546 1 50 33     8 if ( $FH->{'exist'} and $FH->{'open'} ) {
547 1 50       4 if ( defined $FH->{'fh'} ) {
548 1         30 @{$FH->{'LineArray'}} = $FH->{'fh'}->getlines;
  1         148  
549 1         6 $FH->{'pos'}=$FH->{'fh'}->getpos;
550             }
551 1 50       6 if ($FH->{'stat'}{mtime} < ($PresentTime - ($rFD->{'MaxAge'} * 60))) {
552 1         6 $FH->{'fh'}->close;
553 1         17 $FH->{'open'} = $False;
554             }
555             }
556 1         4 $FH->{'LastState'} = FileState;
557             }
558             #
559             # Run Pattern function if object Pattern attribute was set
560 1 50       13 Patterns($rFD) if $rFD->{'Pattern'};
561             #
562             # Run ExceptPatterns function if object ExceptPatterns attribute was set
563 1 50       5 ExceptPatterns($rFD) if $rFD->{'ExceptPattern'};
564             #
565             # Remove deplicate line from arrays
566 1 50       5 RemoveDups($rFD) if $rFD->{'RemoveDuplicate'};
567             #
568             # create a Prefix Array if object OutputPrefix attribute was set
569 1 50       7 Prefix($rFD,0) if $rFD->{'OutputPrefix'};
570             #
571             # Run custom function Pass complete array to custom user fuction
572 1 50       4 if ( $rFD->{'Function'} ) {
573 1         2 foreach my $FH ( @{$rFD->{'FileArray'}} ) {
  1         3  
574 1         2 push(@TotalArray,@{$FH->{LineArray}});
  1         4  
575             }
576 1         3 &{$rFD->{'Function'}}(\@TotalArray)
  1         9  
577             }
578             #
579 1         42 return($rFD);
580             }
581             #
582             ########################################################################
583             #
584             # print out line in from file array (Mostly for help with )
585             #
586             ########################################################################
587             #
588             sub print {
589 1     1 1 6 my($rFD)=shift;
590 1         1 foreach my $FH ( @{$rFD->{FileArray}} ) {
  1         3  
591 1         1 foreach my $LINE ( @{$FH->{LineArray}} ) {
  1         2  
592 5         141 print $LINE;
593             }
594             }
595             }
596             #
597             ########################################################################
598             #
599             # Print out lines from pattern file array (Mostly for help with )
600             #
601             ########################################################################
602             #
603             sub printpat {
604 0     0 0 0 my($rFD)=shift;
605             #print STDOUT Data::Dumper->Dump($rFD) if $DEBUG;
606 0         0 foreach my $FH ( @$rFD{'FileArray'} ) {
607 0         0 foreach my $LINE ( @{$FH->{PatternLineArray}} ) {
  0         0  
608 0         0 print $LINE;
609             }
610             }
611             }
612             #
613             ########################################################################
614             #
615             # print out line from pattern file except array (Mostly for help with )
616             #
617             ########################################################################
618             #
619             sub printexceptpat {
620 0     0 0 0 my($rFD)=shift;
621             #print STDOUT Data::Dumper->Dump($rFD) if $DEBUG;
622 0         0 foreach my $FH ( @$rFD{'FileArray'} ) {
623 0         0 foreach my $LINE ( @{$FH->{ExceptPatternLineArray}} ) {
  0         0  
624 0         0 print $LINE;
625             }
626             }
627             }
628             #
629             ########################################################################
630             #
631             # Print out stat output for each file (Mostly for help with )
632             #
633             ########################################################################
634             #
635             sub printstat {
636 0     0 0 0 my($rFD)=shift;
637 0         0 foreach my $FH ( @$rFD{'FileArray'} ) {
638 0         0 print "Stat ouput for file $FH->{name}\n";
639 0         0 print "------------------------------------------------\n";
640 0         0 foreach my $stat_id ( @StatArray ) {
641 0         0 print "$stat_id = $FH->{'stat'}{$stat_id}\n";
642             }
643             }
644             }
645             #
646             ########################################################################
647             #
648             # Print out All file states
649             # (See note in MultiTail.pm for function OpenUpdateFiles)
650             #
651             ########################################################################
652             #
653             sub printfilestates {
654 0     0 0 0 my($FH)=@_;
655 0         0 my $vector=pack("b4",0);
656 0         0 my $open; my $read; my $exist;my $online;
  0         0  
  0         0  
657              
658 0 0       0 if ( $FH->{'FileState'} != $FH->{'LastState'} ) {
659 0         0 print STDOUT "The State of file $FH->{name} has changed\n";
660 0         0 print STDOUT "-Old state\n";
661 0         0 vec($vector,0,4)=$FH->{'LastState'};
662 0         0 ($online,$read,$open,$exist) = split(//, unpack("b4", $vector));
663 0         0 print STDOUT "\tExist = $exist Open = $open Read = $read Online = $online \n";
664 0         0 print STDOUT "-New State\n";
665 0         0 vec($vector,0,4)=$FH->{'FileState'};
666 0         0 ($online,$read,$open,$exist) = split(//, unpack("b4", $vector));
667 0         0 print STDOUT "\tExist = $exist Open = $open Read = $read Online = $online \n";
668             }
669             else {
670 0         0 print STDOUT "No Change in state for file $FH->{name}\n";
671             }
672             }
673             ########################################################################
674             #
675             # Check if arg is an arrayof pattern or filename of patterns
676             # Return ref to array of patterns
677             #
678             ########################################################################
679             sub CheckIfArrayOrFile {
680 2     2 0 3 my($r_listofpatterns)=@_;
681             #
682 2         3 my @patterns=();
683 2         3 my $patternfile;
684             my $patfh;
685             #
686             # check if list of pattern is an array of a file
687             #
688 2 50       7 if ( ref($r_listofpatterns) eq "ARRAY" ) {
689 2         5 @patterns=@$r_listofpatterns;
690             }
691             else {
692             #
693             # check if it is a file
694             #
695             {
696 1     1   8 no strict 'refs';
  1         3  
  1         1850  
  0         0  
697 0 0       0 if ( -f $r_listofpatterns ) {
698 0         0 $patternfile=$r_listofpatterns;
699             #
700             # open pattern file
701             #
702 0 0       0 stat($patternfile) || croak "Could not open pattern file\n";
703 0         0 $patfh = new FileHandle "$patternfile", "r";
704 0         0 while(<$patfh>) {
705 0         0 chomp;
706 0 0       0 next if /^#/; # Remove line of comments
707 0 0       0 next if /^\s*$/; # Remove Blank lines
708 0         0 push (@patterns,$_);
709             }
710 0         0 $patfh->close;
711             }
712             else {
713 0         0 croak "Argv for sub Pattern was not an Array or File $patternfile was not found\n";
714             }
715             }
716             }
717             #
718 2         12 return \@patterns;
719             }
720             ########################################################################
721             #
722             # Add prefix define by OutputPrefix option to data array
723             #
724             # List of what is Object Options. Default is False
725             # GMT = Greenwich time ZONE
726             #
727             # p => path name of the input file
728             # f => file name of the input file
729             # t => time in HHMMSS
730             # tg => time in HHMMSS GMT
731             # tc => time in MM/DD/YYYY HH:MM:SS
732             # pt => path and time
733             # ptg => path and time GMT
734             # ptc => path and time complete
735             # ft => file and time
736             # ftg => file and time GMT
737             # ftc => file and time complete
738             # tp => time and path
739             # tpg => time and path GMT
740             # tpc => time complete and path
741             # tf => time and file
742             # tfg => time and file GMT
743             # tfc => time complete and file
744             #
745             ########################################################################
746             sub Prefix {
747 1     1 0 2 my($rFD)=shift;
748 1         2 my($ArrayType)=@_;
749             #
750 1         2 my @TempArray=();
751 1         1 my $InArray="LineArray";
752 1         2 my $OutArray="LineArray";
753 1         2 my $r=$rFD;
754 1         1 my $TmpOutputPrefix;
755             #
756             # Check for GMT
757             #
758 1         3 $TmpOutputPrefix = $rFD->{'OutputPrefix'};
759 1 50       5 if ( $rFD->{'OutputPrefix'} =~ /g/ ) {
760 0         0 $TmpOutputPrefix =~ s/g//;
761 0         0 $GMT=$True;
762             }
763 1 50       5 if ( $rFD->{'OutputPrefix'} =~ /c/ ) {
764 0         0 $TmpOutputPrefix =~ s/c//;
765 0         0 $TC=$True;
766             }
767 1         2 foreach my $FH ( @{$rFD->{'FileArray'}} ) {
  1         7  
768 1         2 foreach my $LINE ( @{$FH->{$InArray}} ) {
  1         4  
769 5 50       11 $TmpOutputPrefix eq "p" &&
770             push(@TempArray,"$FH->{'name'} : $LINE");
771 5 50       9 $TmpOutputPrefix eq "f" &&
772             push(@TempArray,"$FH->{'basename'} : $LINE");
773 5 50       77 $TmpOutputPrefix eq "t" &&
774             push(@TempArray,Time($r) . " : $LINE");
775 5 50       12 $TmpOutputPrefix eq "pt" &&
776             push(@TempArray,"$FH->{'name'} " . Time($r) . " : $LINE");
777 5 50       8 $TmpOutputPrefix eq "tp" &&
778             push(@TempArray,Time($r) . " $FH->{'name'} : $LINE");
779 5 50       20 $TmpOutputPrefix eq "ft" &&
780             push(@TempArray,"$FH->{'basename'} " . Time($r) . " : $LINE");
781 5 50       13 $TmpOutputPrefix eq "tf" &&
782             push(@TempArray,Time($r) . " $FH->{'basename'} : $LINE");
783             }
784 1         2 @{$FH->{$OutArray}} = @TempArray;
  1         5  
785 1         3 @TempArray=();
786             }
787 1         3 return($rFD);
788             }
789             ########################################################################
790             #
791             # open a ( file or Array ) of patterns and check for lines matching
792             # the pattern
793             #
794             # Returns Data Structure with
795             #
796             ########################################################################
797             sub Patterns {
798 1     1 0 3 my($rFD)=shift;
799             #
800 1 50       9 $pattern_sub = match_closure(CheckIfArrayOrFile($rFD->{'Pattern'})) unless defined($pattern_sub);
801 1         3 foreach my $FH ( @{$rFD->{FileArray}} ) {
  1         3  
802 1         2 @{$FH->{PatternLineArray}}=();
  1         2  
803 1         2 foreach my $LINE ( @{$FH->{LineArray}} ) {
  1         2  
804 10 100       105 push(@{$FH->{PatternLineArray}},$LINE) if ( $pattern_sub->($LINE) );
  9         22  
805             }
806 1         2 @{$FH->{LineArray}}=@{$FH->{PatternLineArray}};
  1         5  
  1         3  
807             }
808 1         2 return($rFD);
809             }
810             #
811             ########################################################################
812             #
813             # open a ( file or Array ) of paterns and return excepticheck them against lines
814             # from file array
815             #
816             ########################################################################
817             #
818             sub ExceptPatterns {
819 1     1 0 2 my($rFD)=shift;
820             #
821 1 50       5 $exceptpattern_sub = match_closure(CheckIfArrayOrFile($rFD->{'ExceptPattern'})) unless defined($exceptpattern_sub);
822 1         3 foreach my $FH ( @{$rFD->{FileArray}} ) {
  1         4  
823 1         1 @{$FH->{ExceptPatternLineArray}}=();
  1         3  
824 1         2 foreach my $LINE ( @{$FH->{LineArray}} ) {
  1         3  
825 9 100       112 push(@{$FH->{ExceptPatternLineArray}},$LINE) unless ( $exceptpattern_sub->($LINE) );
  8         22  
826             }
827 1         3 @{$FH->{LineArray}}=@{$FH->{ExceptPatternLineArray}};
  1         6  
  1         2  
828             }
829             #
830 1         2 return($rFD);
831             }
832             #
833             ########################################################################
834             #
835             # create closures fuction with eval for efficiency in patterm matching
836             #
837             ########################################################################
838             sub match_closure {
839 2     2 0 3 my($array) = shift;
840 2         2 my $out;
841 2         5 $out = join ('|', @$array);
842 2         270 eval 'sub { $_[0] =~ /($out)/o; }'
843             }
844             #
845             ########################################################################
846             #
847             # Remove Duplicate line from file array
848             #
849             ########################################################################
850             #
851             sub RemoveDups {
852 1     1 0 1 my($rFD)=shift;
853 1         2 my %Mark;
854             #
855 1         1 foreach my $FH ( @{$rFD->{FileArray}} ) {
  1         6  
856             #
857 1         3 undef(%Mark);
858 1         2 grep($Mark{$_}++, @{$FH->{LineArray}});
  1         8  
859 1         3 @{$FH->{LineArray}}=(keys(%Mark));
  1         4  
860 1         5 undef(%Mark);
861             }
862 1         2 return($rFD);
863             }
864             ########################################################################
865             #
866             # Open and Close files as needed for tailing
867             # Should be done before ptail->read
868             #
869             # All file are in a combination of four states.
870             #
871             # 1. Exist = True (File exist at this time )
872             # = False ( File does not exist at this time )
873             #
874             # 2. Open = True (File is open at this time )
875             # = False ( File is not open at this time )
876             #
877             # 3. Read = True (File has been read since open )
878             # = False ( File has not been read since open )
879             #
880             # 4. online = True (File exist now or existed once during this process)
881             # = False (File has never existed during this process)
882             #
883             # The table below list the states that a file can be in
884             # and what action should be taken.
885             #
886             # BV = Binary Value
887             # ____________________________________________________________________
888             # | BV || Exist | Open | Read | OnLine || Action
889             # |____||_______|______|______|________||______________________________|
890             # |----||-------|------|------|--------||------------------------------|
891             # | 0 || F | F | F | F || 1) Skip file
892             # | || | | | ||
893             # |----||-------|------|------|--------||------------------------------|
894             # | 1 || F | F | F | T || 1) Skip file
895             # | || | | | ||
896             # |----||-------|------|------|--------||------------------------------|
897             # | 2 || F | F | T | F || 1) (read = False)
898             # | || | | | || 2) Skip file
899             # |----||-------|------|------|--------||------------------------------|
900             # | 3 || F | F | T | T || 1) (read = False)
901             # | || | | | || 2) Skip file
902             # |----||-------|------|------|--------||------------------------------|
903             # | 4 || F | T | F | F || 1) Close File (open = False)
904             # | || | | | || 2) Skip file
905             # |----||-------|------|------|--------||------------------------------|
906             # | 5 || F | T | F | T || 1) Close File (open = False)
907             # | || | | | || 2) Skip file
908             # |----||-------|------|------|--------||------------------------------|
909             # | 6 || F | T | T | F || 1) Check if file has changed
910             # | || | | | || 2) if file has not changed for
911             # | || | | | || MaxAge, close file
912             # |----||-------|------|------|--------||------------------------------|
913             # | 7 || F | T | T | T || 1) Close File (open =False)
914             # | || | | | || 2) Take offline
915             # | || | | | || (online = False)
916             # | || | | | || 3) (read = False)
917             # |----||-------|------|------|--------||------------------------------|
918             # | 8 || T | F | F | F || 1) Open File (open = True)
919             # | || | | | || 2) Put online (online = True)
920             # | || | | | || 3) Start reading from location
921             # | || | | | || NumLines (read = True)
922             # |----||-------|------|------|--------||------------------------------|
923             # | 9 || T | F | F | T || 1) Open File (open = True)
924             # | || | | | || 2) Start reading from top
925             # | || | | | || (read = True)
926             # |----||-------|------|------|--------||------------------------------|
927             # | 10 || T | F | T | F || 1) Open File (open = True)
928             # | || | | | || 2) Start reading from last pos
929             # | || | | | || 3) Put online (online = True)
930             # |----||-------|------|------|--------||------------------------------|
931             # | 11 || T | F | T | T || 1) Open File (open = True)
932             # | || | | | || 2) Start reading from last pos
933             # |----||-------|------|------|--------||------------------------------|
934             # | 12 || T | T | F | F || 1) Put online (online = True)
935             # | || | | | || 2) Start reading from top
936             # |----||-------|------|------|--------||------------------------------|
937             # | 13 || T | T | F | T || 1) Start reading from last pos
938             # | || | | | || (read = True)
939             # |----||-------|------|------|--------||------------------------------|
940             # | 14 || T | T | T | F || 1) Put online (online = True)
941             # | || | | | || 2) Start reading from top
942             # |----||-------|------|------|--------||------------------------------|
943             # | 15 || T | T | T | T || 1) Start reading from last pos
944             # | || | | | ||
945             # ---------------------------------------------------------------------
946             #
947             ########################################################################
948             sub OpenUpdateFiles {
949             #
950 1     1 0 3 my($rFD)=shift;
951 1         2 my $FS;
952             #
953 1         2 foreach my $FH ( @{$rFD->{'FileArray'}} ) {
  1         7  
954             #
955             # check if file exist and update stat
956 1         8 $FS = UpdateStat($FH);
957             #
958             SWITCH: {
959             #
960 1 50       2 ($FS==0) && do {
  1         5  
961 0         0 last SWITCH;
962             };
963 1 50 33     23 ($FS==2 || $FS==4 || $FS==6 ) && do {
      33        
964 0         0 SetFileState($FH,0);
965 0         0 last SWITCH;
966             };
967 1 50 33     38 ($FS==1 || $FS==3 || $FS==5 || $FS==7 ) && do {
      33        
      33        
968 0         0 SetFileState($FH,1);
969 0         0 last SWITCH;
970             };
971 1 50 33     6 ($FS==8 || $FS==14 ) && do {
972 1         28 $FH->{'fh'} = new FileHandle "$FH->{name}", "r";
973 1         593 $FH->{'OpenTime'} = time;
974 1 50       5 if ( defined($FH->{'fh'}) ) {
975 1         9 SetFileState($FH,15);
976 1         6 PosFileMark($FH);
977             }
978 1         3 last SWITCH;
979             };
980 0 0       0 ($FS==9 ) && do {
981 0         0 $FH->{'fh'} = new FileHandle "$FH->{name}", "r";
982 0         0 $FH->{'OpenTime'} = time;
983 0 0       0 if ( defined($FH->{'fh'}) ) {
984 0         0 SetFileState($FH,15);
985             }
986 0         0 last SWITCH;
987             };
988 0 0       0 ($FS==12 ) && do {
989 0         0 OpenFileToTail($rFD,$FH);
990 0 0       0 if ( $FH->{'open'} ) {
991 0         0 SetFileState($FH,15);
992             }
993 0         0 last SWITCH;
994             };
995 0 0 0     0 ($FS==10 || $FS==11 || $FS==13 || $FS==15 ) && do {
      0        
      0        
996 0         0 OpenFileToTail($rFD,$FH);
997 0 0       0 if ( $FH->{'open'} ) {
998 0         0 SetFileState($FH,15);
999 0         0 $FH->{'fh'}->setpos($FH->{'pos'});
1000             }
1001 0         0 last SWITCH;
1002             };
1003             }
1004             # get current state of file
1005 1         9 $FH->{'FileState'}=FileState($FH);
1006 1 50       6 printfilestates($FH) if $DEBUG;
1007             }
1008             }
1009             #
1010             ########################################################################
1011             #
1012             # Open file if it exist and has been change
1013             #
1014             ########################################################################
1015             #
1016             sub OpenFileToTail {
1017 0     0 0 0 my($rFD)=shift;
1018 0         0 my($FH)=@_;
1019 0         0 my $PresentTime=time;
1020             #
1021             # return if file is already open
1022             #
1023             #
1024             # check if file has been changed in last MaxAge mins
1025 0 0 0     0 if ( ($FH->{'LastMtime'} == $FH->{'stat'}{mtime}) &&
      0        
1026             ($FH->{'stat'}{mtime} < ($PresentTime - ($rFD->{'MaxAge'} * 60))) &&
1027             $FH->{'open'} ) {
1028 0 0       0 $FH->{'fh'}->close if defined($FH->{'fh'});
1029 0         0 $FH->{'OpenTime'} = 0;
1030 0         0 $FH->{'open'}=$False;
1031 0         0 return;
1032             }
1033 0 0       0 if ( $FH->{'LastMtime'} == $FH->{'stat'}{mtime} ) {
1034 0         0 return;
1035             }
1036 0 0 0     0 if ( ! $FH->{'open'} && $FH->{'LastMtime'} < $FH->{'stat'}{mtime} ) {
1037 0         0 $FH->{'fh'} = new FileHandle "$FH->{name}", "r";
1038 0         0 $FH->{'open'}=$True;
1039 0         0 $FH->{'OpenTime'} = $PresentTime;
1040             }
1041             }
1042             #
1043             ########################################################################
1044             #
1045             # Update stat in Data_Structure
1046             #
1047             ########################################################################
1048             #
1049             sub UpdateStat {
1050 1     1 0 6 my($FH)=@_;
1051 1         2 my $tmps;
1052             #
1053             {
1054 1     1   7 no strict 'refs';
  1         3  
  1         90  
  1         2  
1055 1         4 $FH->{'LastMtime'} = $FH->{'stat'}{'mtime'};
1056             }
1057 1 50       6 $FH->{'exist'} = (stat($FH->{'name'}) ? $True : $False);
1058 1 50       617 $FH->{'read'} = $False unless $FH->{exist};
1059             #
1060 1         4 foreach my $stat_id ( @StatArray ) {
1061 13         68 $tmps="st_". $stat_id;
1062             {
1063 1     1   4 no strict 'refs';
  1         2  
  1         859  
  13         16  
1064 13         49 $FH->{'stat'}{$stat_id} = $$tmps;
1065             }
1066             }
1067             # get current state of file
1068 1         9 $FH->{'LastStat'} = $FH->{'FileState'};
1069 1         4 $FH->{'FileState'} = FileState($FH);
1070             #
1071             # this will return the FileState
1072             }
1073             #
1074             ########################################################################
1075             #
1076             # Return the time as a HHMMSS string. $opt_g decides timezone.
1077             #
1078             ########################################################################
1079             #
1080             sub Time {
1081 5     5 0 6 my($rFD)=shift;
1082 5 50       9 return scalar localtime if $TC;
1083 5 50       239 my($sec,$min,$hour)=($GMT ? gmtime : localtime);
1084 5         23 sprintf("%02d%02d%02d", $hour,$min,$sec);
1085             }
1086             #
1087             ########################################################################
1088             #
1089             # Return the version number of the MultiTail Package
1090             #
1091             ########################################################################
1092             #
1093             sub version {
1094 0     0 1 0 return $VERSION;
1095             }
1096             #
1097             ########################################################################
1098             #
1099             # Turn on output on for MultiTail package
1100             #
1101             ########################################################################
1102             #
1103             sub debug {
1104 0     0 1 0 my($rFD)=shift;
1105 0 0       0 if ( $rFD->{'Debug'} ) {
1106 0         0 $DEBUG=0;
1107             }
1108             else {
1109 0         0 $DEBUG=1;
1110             }
1111 0         0 $rFD->{'Debug'}=$DEBUG;
1112             }
1113             #
1114             ########################################################################
1115             #
1116             # Close all file that are being tailed
1117             #
1118             ########################################################################
1119             #
1120             sub close_all_files{
1121 0     0 1 0 my ($rFD)=shift;
1122 0         0 foreach my $FH ( @{$rFD->{'FileArray'}} ) {
  0         0  
1123 0 0       0 next if ! defined $FH->{fh};
1124 0 0       0 print "Closing file $FH->{name} ...\n" if $DEBUG;
1125 0         0 $FH->{fh}->close;
1126             }
1127             }
1128             #
1129             ########################################################################
1130             #
1131             # Return the state of a file in Number terms
1132             #
1133             # Exist = 8
1134             # Open = 4
1135             # Read = 2
1136             # Online = 1
1137             #
1138             ########################################################################
1139             #
1140             sub FileState{
1141 4     4 0 13 my($FH)=@_;
1142 4         7 my $vector=pack("b4",0);
1143             #
1144 4 100       95 if( defined($FH->{'online'}) ) {
1145 3         17 vec($vector,0,1)=$FH->{'online'};
1146 3         11 vec($vector,1,1)=$FH->{'read'};
1147 3         14 vec($vector,2,1)=$FH->{'open'};
1148 3         6 vec($vector,3,1)=$FH->{'exist'};
1149 3         19 vec($vector,0,8);
1150             }
1151             }
1152             ########################################################################
1153             #
1154             # Set the new file state in the Data Structure
1155             #
1156             # Exist = 8
1157             # Open = 4
1158             # Read = 2
1159             # Online = 1
1160             #
1161             ########################################################################
1162             #
1163             sub SetFileState{
1164 1     1 0 105 my($FH,$NewState)=@_;
1165 1         3 my $vector=pack("b4",0);
1166             #
1167              
1168 1 50       3 if ( $NewState > 15 ) { $NewState=15; }
  0         0  
1169              
1170 1         4 vec($vector,0,4)=$NewState;
1171 1         16 ($FH->{'online'},$FH->{'read'},$FH->{'open'},$FH->{'exist'}) =
1172             split(//, unpack("b4", $vector));
1173 1         4 $FH->{'FileState'}=$NewState;
1174             }
1175             #
1176             ########################################################################
1177             #
1178             # Set file seek position to number of line in opt_n
1179             #
1180             ########################################################################
1181             #
1182              
1183             sub PosFileMark {
1184 1     1 0 2 my($FH)=@_;
1185             #
1186 1         2 my $line;
1187             my $pos;
1188 1         2 my @lines=();
1189 1         3 my $fh=$FH->{'fh'};
1190 1         2 my $CharALine=120;
1191 1         2 my $seekbacklines=$Attribute{'NumLines'};
1192 1         3 my $seekback=$CharALine * ($seekbacklines +1);
1193             #
1194 1 50       5 if ( $seekbacklines <= 0 ) { # Move to end of file
1195 0         0 seek($fh,0,2);
1196 0         0 $FH->{'pos'}=$fh->getpos;
1197 0         0 return;
1198             }
1199             else {
1200 1         7 seek($fh, -$seekback, 2);
1201             }
1202             #
1203             # remove line part;
1204             #
1205 1         32 $line=$fh->getline;
1206             #
1207             # get all line to end of file
1208             #
1209 1         63 @lines=$fh->getlines;
1210 1         134 splice(@lines,0,$#lines-$seekbacklines + 1);
1211 1         5 foreach $line ( @lines ) {
1212 10         12 $pos += length $line;
1213             }
1214             #
1215             # seek to pos in file;
1216             #
1217 1         7 seek($fh, -$pos, 2);
1218             #
1219 1         8 $FH->{'pos'}=$fh->getpos;
1220             #
1221             }
1222             #
1223             ########################################################################
1224             #
1225             # Autoload sub-classes
1226             #
1227             ########################################################################
1228             #
1229             sub AUTOLOAD {
1230 0     0     my($rFD)=shift;
1231 0   0       my $type = ref($rFD) || croak "$rFD is not and object";
1232 0           my $attribute = $AUTOLOAD;
1233 0           $attribute =~ s/.*://;
1234 0 0         unless ( exists $rFD->{$attribute} ) {
1235 0           croak "Can't access $attribute field in oject $rFD";
1236             }
1237 0 0         if ( $attribute eq "Files" ) {
1238 0           $FileAttributeChanged=$True;
1239             }
1240 0           CheckAttributes($rFD);
1241 0 0         if (@_) {
1242 0           return $rFD->{$attribute} = shift;
1243             } else {
1244 0           return $rFD->{$attribute};
1245             }
1246             }
1247             #
1248             #
1249             ########################################################################
1250             # POD
1251             ########################################################################
1252             1;
1253             __END__